上一章链接:JDBC——MySQL
数据库连接池是为了提高性能而出现的,先了解连接池的相关概念再往下深入。
目录
数据库连接池
引语
概念
连接池的几个参数
DBCP连接池
C3P0连接池
JNDI
概念
作用
配置格式
代码用例
dbUtils
原理
两大核心类
例子
后话
在Java开发中,使用JDBC操作数据库无非就是那四个步骤:①加载驱动类、②连接数据库、③操作数据库、④关闭数据库。而在编写代码过后,发现步骤①②④是一样的,只有③操作数据库不一样,那么这样就会造成性能降低的问题出现。因此,数据库连接池就应势而生。
数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对连接池中的连接进行申请,使用,释放。
上面的比较官方,先得提及池技术。池技术可以优化服务器应用程序的性能,提高执行效率和降低开销。池就类似于现实世界的工具箱,当要拆卸风扇时,需要用到工具箱内的螺丝刀,用完后放回工具箱内以下次使用。这就是池的作用。
因此数据库连接池的基本思想就是为数据库连接建立一个缓冲池,在缓冲池内预先存入一定数量的连接。当需要建立数据库连接时,只需要从缓冲池内拿出一个连接,使用后再放置回缓冲池里,这样就大大地减少了性能的损耗。
下面用图来附加理解连接池:
initialSize:连接池初始的连接对象数。
minIdle:连接池中最小的空闲连接数,当连接数小于此值时,连接池会创建连接补充连接池。
maxIdle:连接池中最大的空闲连接数,当没有数据库连接时表示保持待命状态的连接数目。
maxActive:连接池支持的最大连接数,即最多只有N个连接对象(N为任意数)。
maxWait:连接池中的连接用完时,下一个请求的等待时间。
这里以工厂代池为例:工厂规模只能容纳40个工人(maxActive),初始只有4人(initialSize),而最小的空闲工人数(minIdle)为5人,此时就需要再去招收1个工人回来。当招募完毕后,为了保证工厂的收益,最多只能空闲20人(maxIdle),即当空闲人数大于20人时,要裁人至20个空闲工人。当40个工人都在工作时,突然增加了一个工作,由于工厂的规模限制,无法再招收工人,那么只能等待工厂中的工人完成手中工作,而此时等待的时间即为maxWait,如果超过等待时间则放弃这个工作。
dbcp连接池是Java数据库连接池的其中一种,若要使用连接池需导入Apache开发的两个jar包:commons-dbcp和commons-pool。
以下为演示其用法:
public void func1() throws SQLException {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/student");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setMaxActive(30);
dataSource.setMinIdle(4);
dataSource.setMaxWait(1000);
Connection con = dataSource.getConnection();
System.out.println(con.getClass().getName());
}
结果如下所示:
在这之中需要说明的是,连接池内部使用了四大参数(即驱动类名称、路径、用户名以及密码)创建了MySQL驱动提供的连接对象Connection。而在连接池使用这个Connection对象进行了装饰,对其中的close()方法进行了更改,用于将当前连接归还给连接池。(这里涉及到了装饰者模式的概念)
C3P0相比于DBCP连接池有自动回收空闲连接功能。若要使用C3P0连接池也需要导入jar包:c3p0-0.9.2.1。
以下为演示代码:
public void func2() throws SQLException, PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/student");
ds.setUser("root");
ds.setPassword("123456");
ds.setInitialPoolSize(10); // 初始化连接数
ds.setAcquireIncrement(5); // 设置增量为5
ds.setMinPoolSize(5); // 设置最小连接数
ds.setMaxPoolSize(20); // 设置最大连接数
Connection con = ds.getConnection();
System.out.println(con);
con.close(); // 归还连接到连接池
}
C3P0亦可以指定使用配置文件,但是配置文件的名称必须是c3p0-config.xl,并必须放在类的路径下。其会寻找配置文件。
配置文件格式如下所示:
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/student
root
123456
10
5
4
20
用例代码如下所示:
@Test
public void func3() throws SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource();
Connection con = ds.getConnection();
System.out.println(con);
}
需要说明的一点:在配置文件中,除了默认配置default-config以外,还有一个自定义命名标签named-config。而在ComboPooledDataSource类的构造方法里有一个带参的构造方法来加载配置文件指定的配置。
JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),是J2EE非常重要的一个规范之一。
JDNI的作用是在服务器上配置资源,然后通过统一的方式来获取配置的资源。实际上,这个作用就降低了程序和数据库之间的耦合性。
配置JNDI资源需要到
name:指定资源的名称,该名称用于获取资源
factory:用来创建资源的工厂,固定值
type:资源的类型
_+:表示资源的属性,资源存在什么名字的属性即配置什么名字的属性的值。如连接池需要配置driveClass等属性。
现在以配置连接池为例,如下所示:
该配置放置于tomcat根目录下的conf文件夹下的server.xml内,也可以单独创建一个和项目名一样的xml在conf下的Catalina的localhost文件夹下。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
Context cxt;
Connection con = null;
try {
cxt = new InitialContext();
DataSource dataSource = (DataSource) cxt.lookup(
"java:comp/env/jdbc/dataSource");
con = dataSource.getConnection();
System.out.println("JNDI:" + con);
} catch (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(con != null)
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Context是javax.naming.Context代表了资源的上下文环境,初始化上下文环境后,通过lookup()方法查找资源。其中,固定资源入口是java:comp/env,而后续的目录则是对应Resource中配置的name值,返回获取的是一个资源对象。
dbUitls是Java中对数据库操作的一个实用工具,其里面封装了对JDBC的操作,简化了大量重复使用的代码,以提高效率。在介绍以及讲述功能使用之前,先讲述一下原理。
为了便于理解以及记忆,需要知道dbUtils中内部是如何封装对JDBC的操作。
首先先准备好一个连接池的小工具,将其命名为jdbcUtils,代码如下所示:
public class JdbcUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
public static ComboPooledDataSource getDataSource() {
return dataSource;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
接着再来创建一个类叫做QR,里面包含了对数据库的操作,代码如下所示:
public class QR {
private DataSource dataSource;
public QR(DataSource dataSource) {
this.dataSource = dataSource;
}
public int update(String sql, Object... params) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(sql);
initParams(pstmt, params); // 对模板的参数初始化
return pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
} catch (SQLException e1) {
throw new RuntimeException(e1);
}
}
}
private void initParams(PreparedStatement pstmt,
Object[] params) throws SQLException{
for(int i = 0; i < params.length; i++)
pstmt.setObject(i+1, params[i]);
}
public T query(String sql, RsHandler rh, Object... params) {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(sql);
initParams(pstmt, params);
rs = pstmt.executeQuery();
return (T) rh.handle(rs);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
} catch (SQLException e1) {
throw new RuntimeException(e1);
}
}
}
}
再来定义一个接口,命名为RsHandler,其用于对返回的结果集的处理方式,代码如下所示:
public interface RsHandler {
public T handle(ResultSet rs) throws SQLException;
}
最后写一个调用例子,测试其效果,代码如下所示:
@Test
public void fun1() throws SQLException {
Student stu = new Student("100", "zhangsan", 18);
addStu(stu);
System.out.println(loadStu("100"));
}
public void addStu(Student stu) {
QR qr = new QR(JdbcUtils.getDataSource());
String sql = "insert into message values(?, ?, ?)";
Object[] params = {stu.getNumber(), stu.getName(), stu.getAge()};
qr.update(sql, params);
}
public Student loadStu(String number) throws SQLException{
QR qr = new QR(JdbcUtils.getDataSource());
String sql = "select * from message where number = ?";
Object[] params = {number};
Student stu = (Student) qr.query(sql, new RsHandler() {
@Override
public Student handle(ResultSet rs) throws SQLException {
if( !rs.next() ) return null;
Student stu = new Student();
stu.setNumber(rs.getString("number"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
return stu;
}
}, params);
return stu;
}
结果如下所示:
而dbutils内部即是这样实现的,只是名字 稍微有些不同。
QueryRunner类:提供对SQL语句操作的API
ResultSetHandler类:接口类,需要实现将结果集转成对象模型的操作。
在DbUtils中分别实现了ResultSetHandler类来处理各种不同情况。第一种是将单行记录转换成Bean对象(即BeanHandler)
// @Test
public void fun2() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from message where number = ?";
Object[] params = {1001};
Student stu = qr.query(sql, new BeanHandler(Student.class), params);
System.out.println(stu);
}
显示结果如下所示:
第二种是将多行记录转换成List对象(即BeanListHandler)
@Test
public void fun3() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from message";
List list = qr.query(sql, new BeanListHandler(Student.class));
System.out.println(list);
}
结果如下所示:
第三种是将一行记录转换成一个Map对象(即MapHandler)
@Test
public void fun4() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from message where number = ?";
Object[] params = {1001};
Map map = qr.query(sql, new MapHandler(), params);
System.out.println(map);
}
结果如下所示:
第四种是将每行记录转换成Map然后保存到List里(即MapListHandler)
@Test
public void fun5() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from message";
List
结果如下所示:
第五种则适合统计时或者单行单列(即ScalarHandler)
@Test
public void fun6() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select count(*) from message";
Object obj = qr.query(sql, new ScalarHandler());
System.out.println(obj);
}
结果如下所示:
'
JDBC前半部分到此为止,先停一会
'