[数据库连接池二]Java数据库连接池--C3P0和JDNI.
前言:
上一篇文章中讲了 DBCP 的用法以及实现原理, 这一篇再来说下 C3P0 和 JDNI 的用法.
1.1、C3P0 数据源
C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0 数据源在项目开发中使用得比较多。
- dbcp 没有自动回收空闲连接的功能
- c3p0 有自动回收空闲连接功能
1.2、在应用程序中加入 C3P0 连接池
1. 导入相关 jar 包
c3p0-0.9.2-pre1.jar、mchange-commons-0.2.jar,如果操作的是 Oracle 数据库,那么还需要导入 c3p0-oracle-thin-extras-0.9.2-pre1.jar
2、在类目录下加入 C3P0 的配置文件:c3p0-config.xml
c3p0-config.xml的配置信息如下:
<?xml version="1.0" encoding="UTF-8"?> <!-- c3p0-config.xml 必须位于类路径下面 private static ComboPooledDataSource ds; static{ try {ds = new ComboPooledDataSource("MySQL"); } catch (Exception e) {throw new ExceptionInInitializerError(e); } } --><c3p0-config>
<!--
C3P0 的缺省 (默认) 配置,
如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource();”这样写就表示使用的是 C3P0 的缺省 (默认) 配置信息来创建数据源
-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
<property name="user">root</property>
<property name="password">XDP</property><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="acquireIncrement"</span><span style="color: rgba(0, 0, 255, 1)">></span>5<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="initialPoolSize"</span><span style="color: rgba(0, 0, 255, 1)">></span>10<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="minPoolSize"</span><span style="color: rgba(0, 0, 255, 1)">></span>5<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="maxPoolSize"</span><span style="color: rgba(0, 0, 255, 1)">></span>20<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">default-config</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> C3P0的命名配置, 如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");”这样写就表示使用的是name是MySQL的配置信息来创建数据源 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">named-config </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="MySQL"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="driverClass"</span><span style="color: rgba(0, 0, 255, 1)">></span>com.mysql.jdbc.Driver<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="jdbcUrl"</span><span style="color: rgba(0, 0, 255, 1)">></span>jdbc:mysql://localhost:3306/jdbcstudy<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="user"</span><span style="color: rgba(0, 0, 255, 1)">></span>root<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="password"</span><span style="color: rgba(0, 0, 255, 1)">></span>XDP<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="acquireIncrement"</span><span style="color: rgba(0, 0, 255, 1)">></span>5<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="initialPoolSize"</span><span style="color: rgba(0, 0, 255, 1)">></span>10<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="minPoolSize"</span><span style="color: rgba(0, 0, 255, 1)">></span>5<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="maxPoolSize"</span><span style="color: rgba(0, 0, 255, 1)">></span>20<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">named-config</span><span style="color: rgba(0, 0, 255, 1)">></span>
</c3p0-config>
如下图所示:
C3P0 使用实例: C3P0Utils.java
1 public class C3P0Utils { 2 private static DataSource dataSource = new ComboPooledDataSource(); 3 4 //从池中获取一个连接 5 public static Connection getConnection() throws SQLException{ 6 return dataSource.getConnection(); 7 } 8 9 public static void closeAll(ResultSet rs,Statement stmt,Connection conn){ 10 if(rs!=null){ 11 try { 12 rs.close(); 13 } catch (SQLException e) { 14 e.printStackTrace(); 15 } 16 } 17 18 if(stmt!=null){ 19 try { 20 stmt.close(); 21 } catch (SQLException e) { 22 e.printStackTrace(); 23 } 24 } 25 26 if(conn!=null){ 27 try { 28 conn.close();//关闭 29 } catch (SQLException e) { 30 e.printStackTrace(); 31 } 32 } 33 } 34 }
测试类:UserDaoImpl.java
1 public class UserDaoImpl { 2 @Test 3 public void testInsert(){ 4 Connection conn = null; 5 PreparedStatement ps = null; 6 try { 7 conn = C3P0Utils.getConnection(); 8 ps = conn.prepareStatement(""); 9 //... 10 System.out.println(conn.getClass().getName()); 11 } catch (SQLException e) { 12 e.printStackTrace(); 13 }finally{ 14 C3P0Utils.closeAll(null, ps, conn); 15 16 } 17 } 18 }
可以看出只要将配置文件 c3p0-config.xml 放在 classpath 中,或 classes 目录中, 然后通过 private static DataSource dataSource = new ComboPooledDataSource(); 就可以自动读取到, 确实很方便易用.
到了这里我们来看一下这里的源码:
private static DataSource dataSource = new ComboPooledDataSource();
ComboPooledDataSource.java
1 // not reassigned post-ctor; mutable elements protected by their own locks 2 // when (very rarely) necessery, we sync this -> wcpds -> dmds 3 4 // note that serialization of these guys happens via out superclass 5 // we just have to make sure they get properly reset on deserialization 6 transient DriverManagerDataSource dmds; 7 transient WrapperConnectionPoolDataSource wcpds; 8 9 public ComboPooledDataSource() 10 { this( true );} 11 12 public ComboPooledDataSource( boolean autoregister ) 13 { 14 super(autoregister); 15 16 // System.err.println("...Initializing ComboPooledDataSource."); 17 18 dmds = new DriverManagerDataSource(); 19 wcpds = new WrapperConnectionPoolDataSource(); 20 21 wcpds.setNestedDataSource(dmds); 22 23 try 24 { this.setConnectionPoolDataSource(wcpds); } 25 catch (PropertyVetoException e) 26 { 27 logger.log(MLevel.WARNING, "Hunh??? This can't happen. We haven't set up any listeners to veto the property change yet!", e); 28 throw new RuntimeException("Hunh??? This can't happen. We haven't set up any listeners to veto the property change yet!" + e); 29 } 30 31 // set things up in case there are future changes to our ConnectionPoolDataSource 32 // 33 setUpPropertyEvents(); 34 }
具体更多细节 还请大家看下源码了, 这里我就不继续往后扩展了. 源码下载地址: http://nchc.dl.sourceforge.net/sourceforge/c3p0/c3p0-0.9.1.2.src.zip
2.1 , JDNI
JNDI(Java Naming and Directory Interface),Java 命名和目录接口,它对应于 J2SE 中的 javax.naming 包,
这 套 API 的主要作用在于:它可以把 Java 对象放在一个容器中(JNDI 容器),并为容器中的 java 对象取一个名称,以后程序想获得 Java 对象,只需 通过名称检索即可。其核心 API 为 Context,它代表 JNDI 容器,其 lookup 方法为检索容器中对应名称的对象。
Tomcat 服务器创建的数据源是以 JNDI 资源的形式发布的,所以说在 Tomat 服务器中配置一个数据源实际上就是在配置一个 JNDI 资源,通过查看 Tomcat 文档,我们知道使用如下的方式配置 tomcat 服务器的数据源:
1 <Context> 2 <Resource name="jdbc/datasource" auth="Container" 3 type="javax.sql.DataSource" username="root" password="123456" 4 driverClassName="com.mysql.jdbc.Driver" 5 url="jdbc:mysql://localhost:3306/datest" 6 maxActive="8" maxIdle="4"/> 7 </Context>
服务器创建好数据源之后,我们的应用程序又该怎么样得到这个数据源呢,Tomcat 服务器创建好数据源之后是以 JNDI 的形式绑定到一个 JNDI 容器中的,我们可以把 JNDI 想象成一个大大的容器,我们可以往这个容器中存放一些对象,一些资源,JNDI 容器中存放的对象和资源都会有一个独一无二的名称,应用程序想从 JNDI 容器中获取资源时,只需要告诉 JNDI 容器要获取的资源的名称,JNDI 根据名称去找到对应的资源后返回给应用程序。我们平时做 javaEE 开发时,服务器会为我们的应用程序创建很多资源,比如 request 对象,response 对象,服务器创建的这些资源有两种方式提供给我们的应用程序使用:第一种是通过方法参数的形式传递进来,比如我们在 Servlet 中写的 doPost 和 doGet 方法中使用到的 request 对象和 response 对象就是服务器以参数的形式传递给我们的。第二种就是 JNDI 的方式,服务器把创建好的资源绑定到 JNDI 容器中去,应用程序想要使用资源时,就直接从 JNDI 容器中获取相应的资源即可。
对于上面的 name="jdbc/datasource" 数据源资源,在应用程序中可以用如下的代码去获取
1 Context initCtx = new InitialContext(); 2 Context envCtx = (Context) initCtx.lookup("java:comp/env"); 3 dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
此种配置下,数据库的驱动 jar 文件需放置在 tomcat 的 lib 下
2.2、配置 Tomcat 数据源
1、在 Web 项目的 WebRoot 目录下的 META-INF 目录创建一个 context.xml 文件
如下图所示:
2、在 context.xml 文件配置 tomcat 服务器的数据源
1 <Context> 2 <Resource 3 name="jdbc/datasource" 4 auth="Container" 5 type="javax.sql.DataSource" 6 username="root" 7 password="XDP" 8 driverClassName="com.mysql.jdbc.Driver" 9 url="jdbc:mysql://localhost:3306/jdbcstudy" 10 maxActive="8" 11 maxIdle="4"/> 12 </Context>
3、将数据库的驱动 jar 文件需放置在 tomcat 的 lib 下
4、在获取数据库连接的工具类 (如 jdbcUtils) 的静态代码块中获取 JNDI 容器中的数据源
1 public class JdbcUtils_JNDI { 2 3 private static DataSource ds = null; 4 //在静态代码块中创建数据库连接池 5 static{ 6 try{ 7 //初始化 JNDI 8 Context initCtx = new InitialContext(); 9 //得到 JNDI 容器 10 Context envCtx = (Context) initCtx.lookup("java:comp/env"); 11 //从 JNDI 容器中检索 name 为 jdbc/datasource 的数据源 12 ds = (DataSource)envCtx.lookup("jdbc/datasource"); 13 }catch (Exception e) { 14 throw new ExceptionInInitializerError(e); 15 } 16 } 17 18 public static Connection getConnection() throws SQLException{ 19 //从数据源中获取数据库连接 20 return ds.getConnection(); 21 } 22 23 24 public static void release(Connection conn,Statement st,ResultSet rs){ 25 if(rs!=null){ 26 try{ 27 //关闭存储查询结果的 ResultSet 对象 28 rs.close(); 29 }catch (Exception e) { 30 e.printStackTrace(); 31 } 32 rs = null; 33 } 34 if(st!=null){ 35 try{ 36 //关闭负责执行 SQL 命令的 Statement 对象 37 st.close(); 38 }catch (Exception e) { 39 e.printStackTrace(); 40 } 41 } 42 43 if(conn!=null){ 44 try{ 45 //将 Connection 连接对象还给数据库连接池 46 conn.close(); 47 }catch (Exception e) { 48 e.printStackTrace(); 49 } 50 } 51 } 52 }
写一个 Servlet 测试 JNDI 数据源
1 public class JNDITest extends HttpServlet { 2 3 public void doGet(HttpServletRequest request, HttpServletResponse response) 4 throws ServletException, IOException { 5 Connection conn = null; 6 PreparedStatement st = null; 7 ResultSet rs = null; 8 try{ 9 //获取数据库连接 10 conn = JdbcUtils_JNDI.getConnection(); 11 String sql = "insert into test1(name) values(?)"; 12 st = conn.prepareStatement(sql); 13 st.setString(1, "gacl"); 14 st.executeUpdate(); 15 //获取数据库自动生成的主键 16 rs = st.getGeneratedKeys(); 17 if(rs.next()){ 18 System.out.println(rs.getInt(1)); 19 } 20 }catch (Exception e) { 21 e.printStackTrace(); 22 }finally{ 23 //释放资源 24 JdbcUtils_JNDI.release(conn, st, rs); 25 } 26 } 27 28 public void doPost(HttpServletRequest request, HttpServletResponse response) 29 throws ServletException, IOException { 30 doGet(request, response); 31 } 33 }