C3P0数据源
C3P0是目前最流行的开源数据库连接池之一,它实现了DataSource数据源接口,支持JDBC2和JDBC3的标准规范,易于扩展并且性能优越,著名的开源框架Hibernate和 Spring使用的都是该数据源。我们在使用C3P0数据源开发时,需要了解C3P0中DataSource接口的实现类ComboPooledDataSource,它是C3P0的核心类,提供了数据源对象的相关方法,具体如表1所示。
表1 ComboPooledDataSource类的常用方法
方法名称 | 功能描述 |
---|---|
void setDriverClass() | 设置连接数据库的驱动名称 |
void setJdbcUrl() | 设置连接数据库的路径 |
void setUser() | 设置数据库的登陆账号 |
void setPassword() | 设置数据库的登录密码 |
void setMaxPoolSize() | 设置数据库连接池最大的连接数目 |
void setMinPoolSize() | 设置数据库连接池最小的连接数目 |
void setInitialPoolSize() | 设置数据库连接池初始化的连接数目 |
Connection getConnection() | 从数据库连接池中获取一个连接 |
通过表1发现,发现C3P0和DBCP数据源所提供的方法大部分功能相同,都包含了设置数据库连接信息的方法和数据库连接池初始化的方法,以及DataSource接口中的getConnection()方法。
当使用C3P0数据源时,首先得创建数据源对象,创建数据源对象可以使用ComboPooledDataSource类,该类有两个构造方法,分别是ComboPooledDataSource()和ComboPooledDataSource(String configName)。接下来,通过两个案例分别讲解上述构造方法是如何创建数据源对象的,具体如下:
1、通过ComboPooledDataSource**类直接创建数据源对象**
使用ComboPooledDataSource类直接创建一个数据源对象,手动给数据源对象设置属性值,然后获取数据库连接对象,具体步骤如下:
(1)在工程chapter02中导入mysql-connector-java-5.0.8-bin.jar、c3p0-0.9.1.2.jar两个jar包,然后在cn.itcast.example包下创建一个Example04类,如例1所示。
例1 Example04.java
1 package cn.itcast.example;
2 import java.sql.SQLException;
3 import javax.sql.DataSource;
4 import com.mchange.v2.c3p0.ComboPooledDataSource;
5 public class Example04 {
6 public static DataSource ds = null;
7 // 初始化C3P0数据源
8 static {
9 ComboPooledDataSource cpds = new ComboPooledDataSource();
10 // 设置连接数据库需要的配置信息
11 try {
12 cpds.setDriverClass("com.mysql.jdbc.Driver");
13 cpds.setJdbcUrl("jdbc:mysql://localhost:3306/chapter02");
14 cpds.setUser("root");
15 cpds.setPassword("itcast");
16 // 设置连接池的参数
17 cpds.setInitialPoolSize(5);
18 cpds.setMaxPoolSize(15);
19 ds = cpds;
20 } catch (Exception e) {
21 throw new ExceptionInInitializerError(e);
22 }
23 }
24 public static void main(String[] args) throws SQLException {
25 // 获取数据库连接对象
26 System.out.println(ds.getConnection());
27 }
28 }
程序的运行结果如图1所示。
图1 运行结果
从图1中可以看出,C3P0数据源对象成功获取到了数据库连接对象。
2、通过读取配置文件创建数据源对象
通过ComboPooledDataSource(String configName)构造方法读取c3p0-config.xml配置文件,创建数据源对象,然后获取数据库连接对象,具体步骤如下:
(1) 在src根目录下创建一个c3p0-config.xml文件,如例2所示。
例2 c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="user">root</property>
<property name="password">itcast</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
jdbc:mysql://localhost:3306/chapter02</property>
<property name="checkoutTimeout">30000</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<named-config name="itcast">
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
jdbc:mysql://localhost:3306/chapter02</property>
<property name="user">root</property>
<property name="password">itcast</property>
</named-config>
</c3p0-config>
在例2中,c3p0-config.xml配置了两套数据源,<default-config>…< /default-config >中的信息是默认配置,在没有指定配置时默认使用该配置创建c3p0数据源对象;<named-config>…</ named-config >中的信息是自定义配置,一个配置文件中可以有零个或多个的自定义配置,当用户需要使用自定义配置时,调用ComboPooledDataSource(String configName)方法,传入<named-config>节点中name属性的值即可创建C3P0数据源对象。这种设置的好处是,当程序在后期更换数据源配置时,只需要修改构造方法中对应的name值即可。
(2) 在cn.itcast.example包下创建一个Example05类,如例3所示。
例3 Example05.java
1 package cn.itcast.example;
2 import java.sql.SQLException;
3 import javax.sql.DataSource;
4 import com.mchange.v2.c3p0.ComboPooledDataSource;
5 public class Example05 {
6 public static DataSource ds = null;
7 // 初始化C3P0数据源
8 static {
9 // 使用c3p0-config.xml配置文件中的named-config节点中name属性的值
10 ComboPooledDataSource cpds = new ComboPooledDataSource("itcast");
11 ds = cpds;
12 }
13 public static void main(String[] args) throws SQLException {
14 System.out.println(ds.getConnection());
15 }
16 }
程序的运行结果如图2所示。
图2 运行结果
从图2中可以看出,C3P0数据源对象成功获取到了数据库连接对象。需要注意的是,在使用ComboPooledDataSource(String configName)方法创建对象时必须遵循以下两点:
● 配置文件名称必须为c3p0-config.xml,并且位于该项目的src根目录下。
● 当传入的configName值为空或者不存在时,则会使用默认的配置方式创建数据源。
通过前两个小节的学习,熟悉了两种常用数据源的配置方法,并从各自的API中可以看出,数据源的实现类会增加一些设置数据库连接池对象属性的方法,这也是不同数据源实现类的本质区别。
多学一招:自定义一个类实现数据库连接池
通过DBCP和C3P0的学习,相信大家对数据源有了一定的了解,为了让大家更好地掌握数据源的工作原理,接下来,通过自定义一个类模拟实现数据库连接池,具体步骤如下:
(1)在chapter02工程中,创建cn.itcast.utils包,并新建一个JDBCUtils工具类,它的实现方式和第一章中提到的JDBCUtils工具类完全相同,这里就不再列出代码。接着创建cn.itcast.mydatasource包,并在该包下创建一个JdbcPool类,如例4所示。
例4 JdbcPool.java
1 package cn.itcast.mydatasource;
2 import java.io.PrintWriter;
3 import java.sql.Connection;
4 import java.sql.SQLException;
5 import java.sql.SQLFeatureNotSupportedException;
6 import java.util.LinkedList;
7 import java.util.logging.Logger;
8 import javax.sql.DataSource;
9 import cn.itcast.utils.JDBCUtils;
10 //自定义数据库连接池类需要实现DataSource接口,重写DataSource的方法
11 public class JdbcPool implements DataSource {
12 // 自定义连接池
13 private static LinkedList<Connection> pool =
14 new LinkedList<Connection>();
15 // 初始化连接池
16 static {
17 try {
18 for (int i = 0; i < 10; i++) {
19 pool.add(getNewCon());// 初始化在连接池中添加10个连接对象
20 }
21 } catch (Exception e) {
22 throw new ExceptionInInitializerError(e);// 抛出初始化错误对象
23 }
24 }
25 //封装获取连接方法
26 public static Connection getNewCon() throws SQLException{
27 //传统创建数据库连接对象的方法
28 return JDBCUtils.getConnection();
29 }
30 //从连接池中获取一个连接对象
31 @Override
32 public Connection getConnection() throws SQLException {
33 // 返回删除的LinkedList集合中的第一个连接对象
34 Connection connection = pool.removeFirst();
35 // 包装设计模式,包装Connection对象,重写close()方法
36 MyConnection con = new MyConnection(connection, pool);
37 return con;
38 }
39 //获取数据库连接池
40 public static LinkedList<Connection> getPool() {
41 return pool;
42 }
43 ...
44 .. //省略DataSource的其他重写方法
45 .
46 }
在例4中,定义了一个连接池pool用于保存Connection对象(连接池本质是一个集合),由于频繁的增删操作特性,所以选用LinkedList比较好。在类第一次加载的时候,自动创建10个连接对象存放在LinkedList中。当调用getConnection()方法时,会在LinkedList集合中获取一个对象并返回。这里除了使用LinkedList的removeFirst()方法每次获取第一个被删除对象以外,还可以使用JDK1.6新特性中LinkedList推出的pop()方法,比removeFirst()更加高效。
(2)在例4中第36行,MyConnection是自定义类,它实现了Connection接口,采用了包装设计模式,重写了Connection对象的close()方法,让数据库连接对象调用close()方式时,不是释放资源、关闭连接,而是把对象返回到连接池中。下面我们实现MyConnection类,在cn.itcast.mydatasource包下创建MyConnection类,如例5所示:
例5 MyConnection.java
1 package cn.itcast.mydatasource;
2 import java.sql.*;
3 import java.util.Map;
4 import java.util.Properties;
5 import java.util.LinkedList;
6 import java.util.concurrent.Executor;
7 //自定义一个Connection对象,其他方法都调用原本Connection的方法,重写close方法
8 public class MyConnection implements Connection{
9 private Connection con;
10 private LinkedList<Connection> pool;
11 //MyConnection的构造函数的参数包括连接对象和连接池对象
12 public MyConnection (Connection con,LinkedList<Connection> pool){
13 this.con = con ;
14 this.pool = pool;
15 }
16 //不能真的关闭连接,而是把连接重新放回连接池中
17 @Override
18 public void close() throws SQLException {
19 this.pool.addFirst(this.con);
20 }
21 ...
22 .. //省略Connection的其他重写方法
23 .
24 }
经过MyConnection包装后的Connection对象,调用close()方法释放资源时,就会执行close()方法,把连接对象返回到LinkedList集合中。
(3)在cn.itcast.example包下创建JdbcPoolTest类,用来测试自定义数据源是否创建成功,如例6所示:
例6 JdbcPoolTest.java
1 package cn.itcast.example;
2 import java.sql.Connection;
3 import java.sql.SQLException;
4 import java.util.LinkedList;
5 import javax.sql.DataSource;
6 import cn.itcast.mydatasource.JdbcPool;
7 public class JdbcPoolTest {
8 public static void main(String[] args) throws SQLException {
9 // 获取数据源对象
10 DataSource ds = new JdbcPool();
11 // 获取连接池对象
12 LinkedList<Connection> pool = JdbcPool.getPool();
13 // 输出连接池中连接的个数
14 System.out.println("初始化时连接池中的连接对象个数是:" + pool.size());
15 // 获取一个数据库连接对象
16 Connection conn = ds.getConnection();
17 // 输出连接池中连接的个数
18 System.out.println("获取一个连接对象时,连接池中的连接对象个数是:"
19 + pool.size());
20 // 返还数据库连接对象
21 conn.close();
22 // 输出连接池中连接的个数
23 System.out.println("返还数据库连接后,连接池中的连接对象个数是:"
24 + pool.size());
25 }
26 }
程序运行结果如图3所示:
图3 运行结果
从图3中可以看出,当数据源初始化时,连接池中连接对象个数是10;当从数据源中获取一个连接对象后,连接池中连接对象个数变成了9;当conn对象执行close()方法后,连接池中连接对象个数又变成了10。由此可见,自定义类JdbcPool实现了数据库连接池,具备管理连接池中对象的功能。