java web学习总结(十六) -------------------数据库连接池

一、应用程序直接获取数据库连接的缺点

  用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天 10 万访问量,数据库服务器就需要创建 10 万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。如下图所示:

  

二、使用数据库连接池优化程序性能

2.1、数据库连接池的基本概念

  数据库连接是一种关键的有限的昂贵的资源, 这一点在多用户的网页应用程序中体现的尤为突出. 对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性, 影响到程序的性能指标. 数据库连接池正式针对这个问题提出来的.数据库连接池负责分配, 管理和释放数据库连接, 它允许应用程序重复使用一个现有的数据库连接, 而不是重新建立一个。如下图所示:

  

       数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的. 无论这些数据库连接是否被使用, 连接池都将一直保证至少拥有这么多的连接数量. 连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数, 当应用程序向连接池请求的连接数超过最大连接数量时, 这些请求将被加入到等待队列中.

       数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:

  1. 最小连接数: 是连接池一直保持的数据库连接, 所以如果应用程序对数据库连接的使用量不大, 将会有大量的数据库连接资源被浪费.
  2. 最大连接数: 是连接池能申请的最大连接数, 如果数据库连接请求超过次数, 后面的数据库连接请求将被加入到等待队列中, 这会影响以后的数据库操作
  3. 如果最小连接数与最大连接数相差很大: 那么最先连接请求将会获利, 之后超过最小连接数量的连接请求等价于建立一个新的数据库连接. 不过, 这些大于最小连接数的数据库连接在使用完不会马上被释放, 他将被放到连接池中等待重复使用或是空间超时后被释放.

2.2、编写数据库连接池

  编写连接池需实现 java.sql.DataSource 接口。DataSource 接口中定义了两个重载的 getConnection 方法:

  • Connection getConnection()
  • Connection getConnection(String username, String password)

  实现 DataSource 接口,并实现连接池功能的步骤:

  1. 在 DataSource 构造函数中批量创建与数据库的连接,并把创建的连接加入 LinkedList 对象中。
  2. 实现 getConnection 方法,让 getConnection 方法每次调用时,从 LinkedList 中取一个 Connection 返回给用户。
  3. 当用户使用完 Connection,调用 Connection.close() 方法时,Collection 对象应保证将自己返回到 LinkedList 中, 而不要把 conn 还给数据库。Collection 保证将自己返回到 LinkedList 中是此处编程的难点

 数据库连接池核心代码

  使用动态代理技术构建连接池中的 connection

复制代码
 1 proxyConn = (Connection) Proxy.newProxyInstance(this.getClass()
 2             .getClassLoader(), conn.getClass().getInterfaces(),
 3             new InvocationHandler() {
 4         // 此处为内部类,当 close 方法被调用时将 conn 还回池中, 其它方法直接执行
 5             public Object invoke(Object proxy, Method method,
 6                       Object[] args) throws Throwable {7                 if (method.getName().equals("close")) {8                     pool.addLast(conn);
 9                     return null;
10             }
11             return method.invoke(conn, args);
12         }
13     });
复制代码

数据库连接池编写范例:

复制代码
  1 package me.gacl.demo;
  2 
  3 import java.io.InputStream;
  4 import java.io.PrintWriter;
  5 import java.lang.reflect.InvocationHandler;
  6 import java.lang.reflect.Method;
  7 import java.lang.reflect.Proxy;
  8 import java.sql.Connection;
  9 import java.sql.DriverManager;
 10 import java.sql.SQLException;
 11 import java.util.LinkedList;
 12 import java.util.Properties;
 13 import javax.sql.DataSource;
 14 
 15 /**
 16 * @ClassName: JdbcPool
 17 * @Description: 编写数据库连接池
 18 * @author: 孤傲苍狼
 19 * @date: 2014-9-30 下午 11:07:23
 20 *
 21 */ 
 22 public class JdbcPool implements DataSource{
 23 
 24     /**
 25     * @Field: listConnections
 26     *         使用 LinkedList 集合来存放数据库链接,
 27     *        由于要频繁读写 List 集合,所以这里使用 LinkedList 存储数据库连接比较合适
 28     */ 
 29     private static LinkedList<Connection> listConnections = new LinkedList<Connection>();
 30     
 31     static{
 32         // 在静态代码块中加载 db.properties 数据库配置文件
 33         InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
 34         Properties prop = new Properties();
 35         try {36             prop.load(in);
 37             String driver = prop.getProperty("driver");
 38             String url = prop.getProperty("url");
 39             String username = prop.getProperty("username");
 40             String password = prop.getProperty("password");
 41             // 数据库连接池的初始化连接数大小
 42             int jdbcPoolInitSize =Integer.parseInt(prop.getProperty("jdbcPoolInitSize"));
 43             // 加载数据库驱动
 44             Class.forName(driver);
 45             for (int i = 0; i < jdbcPoolInitSize; i++) {46                 Connection conn = DriverManager.getConnection(url, username, password);
 47                 System.out.println("获取到了链接" + conn);
 48                 // 将获取到的数据库连接加入到 listConnections 集合中,listConnections 集合此时就是一个存放了数据库连接的连接池
 49                 listConnections.add(conn);
 50             }
 51             
 52         } catch (Exception e) {53             throw new ExceptionInInitializerError(e);
 54         }
 55     }
 56     
 57     @Override
 58     public PrintWriter getLogWriter() throws SQLException {
 59         // TODO Auto-generated method stub
 60         return null;
 61     }
 62 
 63     @Override
 64     public void setLogWriter(PrintWriter out) throws SQLException {
 65         // TODO Auto-generated method stub
 66         
 67     }
 68 
 69     @Override
 70     public void setLoginTimeout(int seconds) throws SQLException {
 71         // TODO Auto-generated method stub
 72         
 73     }
 74 
 75     @Override
 76     public int getLoginTimeout() throws SQLException {
 77         // TODO Auto-generated method stub
 78         return 0;
 79     }
 80 
 81     @Override
 82     public <T> T unwrap(Class<T> iface) throws SQLException {
 83         // TODO Auto-generated method stub
 84         return null;
 85     }
 86 
 87     @Override
 88     public boolean isWrapperFor(Class<?> iface) throws SQLException {
 89         // TODO Auto-generated method stub
 90         return false;
 91     }
 92 
 93     /* 获取数据库连接
 94      * @see javax.sql.DataSource#getConnection()
 95      */
 96     @Override
 97     public Connection getConnection() throws SQLException {
 98         // 如果数据库连接池中的连接对象的个数大于 0
 99         if (listConnections.size()>0) {
100             // 从 listConnections 集合中获取一个数据库连接
101             final Connection conn = listConnections.removeFirst();
102             System.out.println("listConnections 数据库连接池大小是" + listConnections.size());
103             // 返回 Connection 对象的代理对象
104             return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){
105                 @Override
106                 public Object invoke(Object proxy, Method method, Object[] args)
107                         throws Throwable {108                     if(!method.getName().equals("close")){109                         return method.invoke(conn, args);
110                     }else{
111                         // 如果调用的是 Connection 对象的 close 方法,就把 conn 还给数据库连接池
112                         listConnections.add(conn);
113                         System.out.println(conn + "被还给 listConnections 数据库连接池了!!");
114                         System.out.println("listConnections 数据库连接池大小为" + listConnections.size());
115                         return null;
116                     }
117                 }
118             });
119         }else {120             throw new RuntimeException("对不起,数据库忙");
121         }
122     }
123 
124     @Override
125     public Connection getConnection(String username, String password)
126             throws SQLException {
127         return null;
128     }
129 }
复制代码

 db.properties 配置文件如下:

复制代码
1 driver=com.mysql.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/jdbcStudy
3 username=root
4 password=XDP
5 
6 jdbcPoolInitSize=10
复制代码

写一个 JdbcUtil 测试数据库连接池

复制代码
 1 package me.gacl.utils;
 2 
 3 import java.sql.Connection;
 4 import java.sql.ResultSet;
 5 import java.sql.SQLException;
 6 import java.sql.Statement;
 7 import me.gacl.demo.JdbcPool;
 8 
 9 public class JdbcUtil {
10     
11     /**
12     * @Field: pool
13     *          数据库连接池
14     */ 
15     private static JdbcPool pool = new JdbcPool();
16     
17     /**
18     * @Method: getConnection
19     * @Description: 从数据库连接池中获取数据库连接对象
20     * @Anthor: 孤傲苍狼
21     * @return Connection 数据库连接对象
22     * @throws SQLException
23     */ 
24     public static Connection getConnection() throws SQLException{25         return pool.getConnection();
26     }
27     
28     /**
29     * @Method: release
30     * @Description: 释放资源,
31     * 释放的资源包括 Connection 数据库连接对象,负责执行 SQL 命令的 Statement 对象,存储查询结果的 ResultSet 对象
32     * @Anthor: 孤傲苍狼
33     *
34     * @param conn
35     * @param st
36     * @param rs
37     */ 
38     public static void release(Connection conn,Statement st,ResultSet rs){39         if(rs!=null){
40             try{
41                 // 关闭存储查询结果的 ResultSet 对象
42                 rs.close();
43             }catch (Exception e) {44                 e.printStackTrace();
45             }
46             rs = null;
47         }
48         if(st!=null){
49             try{
50                 // 关闭负责执行 SQL 命令的 Statement 对象
51                 st.close();
52             }catch (Exception e) {53                 e.printStackTrace();
54             }
55         }
56         
57         if(conn!=null){
58             try{
59                 // 关闭 Connection 数据库连接对象
60                 conn.close();
61             }catch (Exception e) {62                 e.printStackTrace();
63             }
64         }
65     }
66 }
复制代码

 三、开源数据库连接池

  现在很多 WEB 服务器 (Weblogic, WebSphere, Tomcat) 都提供了 DataSoruce 的实现,即连接池的实现。通常我们把 DataSource 的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
  也有一些开源组织提供了数据源的独立实现:

  • DBCP 数据库连接池
  • C3P0 数据库连接池

  在使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。

3.1、DBCP 数据源

  DBCP 是 Apache 软件基金组织下的开源连接池实现,要使用 DBCP 数据源,需要应用程序应在系统中增加如下两个 jar 文件:

  • Commons-dbcp.jar:连接池的实现
  • Commons-pool.jar:连接池实现的依赖库

  Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

3.2、在应用程序中加入 dbcp 连接池

  1. 导入相关 jar 包
        commons-dbcp-1.2.2.jar、commons-pool.jar
  2、在类目录下加入 dbcp 的配置文件:dbcpconfig.properties

    dbcpconfig.properties的配置信息如下:

复制代码
 1 #连接设置
 2 driverClassName=com.mysql.jdbc.Driver
 3 url=jdbc:mysql://localhost:3306/jdbcstudy
 4 username=root
 5 password=XDP
 6 
 7 #<!-- 初始化连接 -->
 8 initialSize=10
 9 
10 #最大连接数量
11 maxActive=50
12 
13 #<!-- 最大空闲连接 -->
14 maxIdle=20
15 
16 #<!-- 最小空闲连接 -->
17 minIdle=5
18 
19 #<!-- 超时等待时间以毫秒为单位 6000 毫秒 /1000 等于 60 秒 -->
20 maxWait=60000
21 
22 
23 #JDBC 驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名 =property;] 
24 #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
25 connectionProperties=useUnicode=true;characterEncoding=UTF8
26 
27 #指定由连接池所创建的连接的自动提交(auto-commit)状态。
28 defaultAutoCommit=true
29 
30 #driver default 指定由连接池所创建的连接的只读(read-only)状态。
31 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
32 defaultReadOnly=
33 
34 #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
35 #可用值为下列之一:(详情可见 javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
36 defaultTransactionIsolation=READ_UNCOMMITTED
复制代码

  如下图所示:

  

  3、在获取数据库连接的工具类 (如 jdbcUtils) 的静态代码块中创建池

复制代码
 1 package me.gacl.util;
 2 
 3 import java.io.InputStream;
 4 import java.sql.Connection;
 5 import java.sql.ResultSet;
 6 import java.sql.SQLException;
 7 import java.sql.Statement;
 8 import java.util.Properties;
 9 import javax.sql.DataSource;
10 import org.apache.commons.dbcp.BasicDataSourceFactory;
11 
12 /**
13 * @ClassName: JdbcUtils_DBCP
14 * @Description: 数据库连接工具类
15 * @author: 孤傲苍狼
16 * @date: 2014-10-4 下午 6:04:36
17 *
18 */ 
19 public class JdbcUtils_DBCP {
20     /**
21      * 在 java 中,编写数据库连接池需实现 java.sql.DataSource 接口,每一种数据库连接池都是 DataSource 接口的实现
22      * DBCP 连接池就是 java.sql.DataSource 接口的一个具体实现
23      */
24     private static DataSource ds = null;
25     // 在静态代码块中创建数据库连接池
26     static{
27         try{
28             // 加载 dbcpconfig.properties 配置文件
29             InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
30             Properties prop = new Properties();
31             prop.load(in);
32             // 创建数据源
33             ds = BasicDataSourceFactory.createDataSource(prop);
34         }catch (Exception e) {35             throw new ExceptionInInitializerError(e);
36         }
37     }
38     
39     /**
40     * @Method: getConnection
41     * @Description: 从数据源中获取数据库连接
42     * @Anthor: 孤傲苍狼
43     * @return Connection
44     * @throws SQLException
45     */ 
46     public static Connection getConnection() throws SQLException{
47         // 从数据源中获取数据库连接
48         return ds.getConnection();
49     }
50     
51     /**
52     * @Method: release
53     * @Description: 释放资源,
54     * 释放的资源包括 Connection 数据库连接对象,负责执行 SQL 命令的 Statement 对象,存储查询结果的 ResultSet 对象
55     * @Anthor: 孤傲苍狼
56     *
57     * @param conn
58     * @param st
59     * @param rs
60     */ 
61     public static void release(Connection conn,Statement st,ResultSet rs){62         if(rs!=null){
63             try{
64                 // 关闭存储查询结果的 ResultSet 对象
65                 rs.close();
66             }catch (Exception e) {67                 e.printStackTrace();
68             }
69             rs = null;
70         }
71         if(st!=null){
72             try{
73                 // 关闭负责执行 SQL 命令的 Statement 对象
74                 st.close();
75             }catch (Exception e) {76                 e.printStackTrace();
77             }
78         }
79         
80         if(conn!=null){
81             try{
82                 // 将 Connection 连接对象还给数据库连接池
83                 conn.close();
84             }catch (Exception e) {85                 e.printStackTrace();
86             }
87         }
88     }
89 }
复制代码

  测试 DBCP 数据源

复制代码
 1 package me.gacl.test;
 2 
 3 import java.sql.Connection;
 4 import java.sql.PreparedStatement;
 5 import java.sql.ResultSet;
 6 import org.junit.Test;
 7 import me.gacl.util.JdbcUtils_DBCP;
 8 
 9 public class DataSourceTest {
10     
11     @Test
12     public void dbcpDataSourceTest() {
13         Connection conn = null;
14         PreparedStatement st = null;
15         ResultSet rs = null;
16         try{
17             // 获取数据库连接
18             conn = JdbcUtils_DBCP.getConnection();
19             String sql = "insert into test1(name) values(?)";
20             st = conn.prepareStatement(sql);
21             st.setString(1, "gacl");
22             st.executeUpdate();
23             // 获取数据库自动生成的主键
24             rs = st.getGeneratedKeys();
25             if(rs.next()){26                 System.out.println(rs.getInt(1));
27             }
28         }catch (Exception e) {29             e.printStackTrace();
30         }finally{
31             // 释放资源
32             JdbcUtils_DBCP.release(conn, st, rs);
33         }
34     }
35 }
复制代码

 3.3、C3P0 数据源

  C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0 数据源在项目开发中使用得比较多。

  c3p0 与 dbcp 区别
  1. dbcp 没有自动回收空闲连接的功能
  2. c3p0 有自动回收空闲连接功能

3.4、在应用程序中加入 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的配置信息如下:

复制代码
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!--
 3 c3p0-config.xml 必须位于类路径下面
 4 private static ComboPooledDataSource ds;
 5 static{
 6     try {7         ds = new ComboPooledDataSource("MySQL");
 8     } catch (Exception e) {9         throw new ExceptionInInitializerError(e);
10     }
11 }
12 -->
13 
14 <c3p0-config>
15     <!--
16     C3P0 的缺省 (默认) 配置,
17     如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource();”这样写就表示使用的是 C3P0 的缺省 (默认) 配置信息来创建数据源
18     -->
19     <default-config>
20         <property name="driverClass">com.mysql.jdbc.Driver</property>
21         <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
22         <property name="user">root</property>
23         <property name="password">XDP</property>
24         
25         <property name="acquireIncrement">5</property>
26         <property name="initialPoolSize">10</property>
27         <property name="minPoolSize">5</property>
28         <property name="maxPoolSize">20</property>
29     </default-config>
30 
31     <!--
32     C3P0 的命名配置,
33     如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");”这样写就表示使用的是 name 是 MySQL 的配置信息来创建数据源
34     -->
35     <named-config name="MySQL">
36         <property name="driverClass">com.mysql.jdbc.Driver</property>
37         <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
38         <property name="user">root</property>
39         <property name="password">XDP</property>
40         
41         <property name="acquireIncrement">5</property>
42         <property name="initialPoolSize">10</property>
43         <property name="minPoolSize">5</property>
44         <property name="maxPoolSize">20</property>
45     </named-config>
46 
47 </c3p0-config>
复制代码

  如下图所示:

  

  3、在获取数据库连接的工具类 (如 jdbcUtils) 的静态代码块中创建池

复制代码
 1 package me.gacl.util;
 2 
 3 import java.sql.Connection;
 4 import java.sql.ResultSet;
 5 import java.sql.SQLException;
 6 import java.sql.Statement;
 7 import com.mchange.v2.c3p0.ComboPooledDataSource;
 8 
 9 /**
10 * @ClassName: JdbcUtils_C3P0
11 * @Description: 数据库连接工具类
12 * @author: 孤傲苍狼
13 * @date: 2014-10-4 下午 6:04:36
14 *
15 */ 
16 public class JdbcUtils_C3P0 {
17     
18     private static ComboPooledDataSource ds = null;
19     // 在静态代码块中创建数据库连接池
20     static{
21         try{
22             // 通过代码创建 C3P0 数据库连接池
23             /*ds = new ComboPooledDataSource();
24             ds.setDriverClass("com.mysql.jdbc.Driver");
25             ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
26             ds.setUser("root");
27             ds.setPassword("XDP");
28             ds.setInitialPoolSize(10);
29             ds.setMinPoolSize(5);
30             ds.setMaxPoolSize(20);*/
31             
32             // 通过读取 C3P0 的 xml 配置文件创建数据源,C3P0 的 xml 配置文件 c3p0-config.xml 必须放在 src 目录下
33             //ds = new ComboPooledDataSource();// 使用 C3P0 的默认配置来创建数据源
34             ds = new ComboPooledDataSource("MySQL");// 使用 C3P0 的命名配置来创建数据源
35             
36         }catch (Exception e) {37             throw new ExceptionInInitializerError(e);
38         }
39     }
40     
41     /**
42     * @Method: getConnection
43     * @Description: 从数据源中获取数据库连接
44     * @Anthor: 孤傲苍狼
45     * @return Connection
46     * @throws SQLException
47     */ 
48     public static Connection getConnection() throws SQLException{
49         // 从数据源中获取数据库连接
50         return ds.getConnection();
51     }
52     
53     /**
54     * @Method: release
55     * @Description: 释放资源,
56     * 释放的资源包括 Connection 数据库连接对象,负责执行 SQL 命令的 Statement 对象,存储查询结果的 ResultSet 对象
57     * @Anthor: 孤傲苍狼
58     *
59     * @param conn
60     * @param st
61     * @param rs
62     */ 
63     public static void release(Connection conn,Statement st,ResultSet rs){64         if(rs!=null){
65             try{
66                 // 关闭存储查询结果的 ResultSet 对象
67                 rs.close();
68             }catch (Exception e) {69                 e.printStackTrace();
70             }
71             rs = null;
72         }
73         if(st!=null){
74             try{
75                 // 关闭负责执行 SQL 命令的 Statement 对象
76                 st.close();
77             }catch (Exception e) {78                 e.printStackTrace();
79             }
80         }
81         
82         if(conn!=null){
83             try{
84                 // 将 Connection 连接对象还给数据库连接池
85                 conn.close();
86             }catch (Exception e) {87                 e.printStackTrace();
88             }
89         }
90     }
91 }
复制代码

  测试 C3P0 数据源

复制代码
 1 package me.gacl.test;
 2 
 3 import java.sql.Connection;
 4 import java.sql.PreparedStatement;
 5 import java.sql.ResultSet;
 6 import org.junit.Test;
 7 import me.gacl.util.JdbcUtils_C3P0;
 8 import me.gacl.util.JdbcUtils_DBCP;
 9 
10 public class DataSourceTest {
11     
12     @Test
13     public void c3p0DataSourceTest() {
14         Connection conn = null;
15         PreparedStatement st = null;
16         ResultSet rs = null;
17         try{
18             // 获取数据库连接
19             conn = JdbcUtils_C3P0.getConnection();
20             String sql = "insert into test1(name) values(?)";
21             st = conn.prepareStatement(sql);
22             st.setString(1, "gacl");
23             st.executeUpdate();
24             // 获取数据库自动生成的主键
25             rs = st.getGeneratedKeys();
26             if(rs.next()){27                 System.out.println(rs.getInt(1));
28             }
29         }catch (Exception e) {30             e.printStackTrace();
31         }finally{
32             // 释放资源
33             JdbcUtils_C3P0.release(conn, st, rs);
34         }
35     }
36 }
复制代码

 四、配置 Tomcat 数据源

  在实际开发中,我们有时候还会使用服务器提供给我们的数据库连接池,比如我们希望 Tomcat 服务器在启动的时候可以帮我们创建一个数据库连接池,那么我们在应用程序中就不需要手动去创建数据库连接池,直接使用 Tomcat 服务器创建好的数据库连接池即可。要想让 Tomcat 服务器在启动的时候帮我们创建一个数据库连接池,那么需要简单配置一下 Tomcat 服务器。

4.1、JNDI 技术简介

  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="XDP"
4             driverClassName="com.mysql.jdbc.Driver" 
5             url="jdbc:mysql://localhost:3306/jdbcstudy"
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 下

  

4.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 package me.gacl.util;
 2 
 3 import java.sql.Connection;
 4 import java.sql.ResultSet;
 5 import java.sql.SQLException;
 6 import java.sql.Statement;
 7 import javax.naming.Context;
 8 import javax.naming.InitialContext;
 9 import javax.sql.DataSource;
10 
11 /**
12 * @ClassName: JdbcUtils_DBCP
13 * @Description: 数据库连接工具类
14 * @author: 孤傲苍狼
15 * @date: 2014-10-4 下午 6:04:36
16 *
17 */ 
18 public class JdbcUtils_JNDI {
19     
20     private static DataSource ds = null;
21     // 在静态代码块中创建数据库连接池
22     static{
23         try{
24              // 初始化 JNDI
25             Context initCtx = new InitialContext();
26              // 得到 JNDI 容器
27             Context envCtx = (Context) initCtx.lookup("java:comp/env");
28              // 从 JNDI 容器中检索 name 为 jdbc/datasource 的数据源
29             ds = (DataSource)envCtx.lookup("jdbc/datasource");
30         }catch (Exception e) {31             throw new ExceptionInInitializerError(e);
32         }
33     }
34     
35     /**
36     * @Method: getConnection
37     * @Description: 从数据源中获取数据库连接
38     * @Anthor: 孤傲苍狼
39     * @return Connection
40     * @throws SQLException
41     */ 
42     public static Connection getConnection() throws SQLException{
43         // 从数据源中获取数据库连接
44         return ds.getConnection();
45     }
46     
47     /**
48     * @Method: release
49     * @Description: 释放资源,
50     * 释放的资源包括 Connection 数据库连接对象,负责执行 SQL 命令的 Statement 对象,存储查询结果的 ResultSet 对象
51     * @Anthor: 孤傲苍狼
52     *
53     * @param conn
54     * @param st
55     * @param rs
56     */ 
57     public static void release(Connection conn,Statement st,ResultSet rs){58         if(rs!=null){
59             try{
60                 // 关闭存储查询结果的 ResultSet 对象
61                 rs.close();
62             }catch (Exception e) {63                 e.printStackTrace();
64             }
65             rs = null;
66         }
67         if(st!=null){
68             try{
69                 // 关闭负责执行 SQL 命令的 Statement 对象
70                 st.close();
71             }catch (Exception e) {72                 e.printStackTrace();
73             }
74         }
75         
76         if(conn!=null){
77             try{
78                 // 将 Connection 连接对象还给数据库连接池
79                 conn.close();
80             }catch (Exception e) {81                 e.printStackTrace();
82             }
83         }
84     }
85 }
复制代码

  写一个 Servlet 测试 JNDI 数据源

复制代码
 1 package me.gacl.test;
 2 
 3 import java.io.IOException;
 4 import java.sql.Connection;
 5 import java.sql.PreparedStatement;
 6 import java.sql.ResultSet;
 7 import javax.servlet.ServletException;
 8 import javax.servlet.http.HttpServlet;
 9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import me.gacl.util.JdbcUtils_JNDI;
12 
13 public class JNDITest extends HttpServlet {
14 
15     public void doGet(HttpServletRequest request, HttpServletResponse response)
16             throws ServletException, IOException {
17         Connection conn = null;
18         PreparedStatement st = null;
19         ResultSet rs = null;
20         try{
21             // 获取数据库连接
22             conn = JdbcUtils_JNDI.getConnection();
23             String sql = "insert into test1(name) values(?)";
24             st = conn.prepareStatement(sql);
25             st.setString(1, "gacl");
26             st.executeUpdate();
27             // 获取数据库自动生成的主键
28             rs = st.getGeneratedKeys();
29             if(rs.next()){30                 System.out.println(rs.getInt(1));
31             }
32         }catch (Exception e) {33             e.printStackTrace();
34         }finally{
35             // 释放资源
36             JdbcUtils_JNDI.release(conn, st, rs);
37         }
38     }
39 
40     public void doPost(HttpServletRequest request, HttpServletResponse response)
41             throws ServletException, IOException {42         doGet(request, response);
43     }
44 
45 }
复制代码