1. 首页
  2. JavaWeb学习总结

javaweb学习总结(三十九)——数据库连接池

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

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

  anshijiushujukulianjiechi_1.png

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

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

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

  anshijiushujukulianjiechi_2.png

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

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

  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

  如下图所示:

  anshijiushujukulianjiechi_3.png

  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>

  如下图所示:

  anshijiushujukulianjiechi_4.png

  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下

  anshijiushujukulianjiechi_5.png

4.2、配置Tomcat数据源

  1、在Web项目的WebRoot目录下的META-INF目录创建一个context.xml文件

  如下图所示:

  anshijiushujukulianjiechi_6.png

  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下

  anshijiushujukulianjiechi_7.png

  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 }

作者:孤傲苍狼

来源:https://www.cnblogs.com/xdp-gacl/p/4002804.html


看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
  4. JS中文网,Javascriptc中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,是给开发者用的 Hacker News,技术文章由为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。

    本文著作权归作者所有,如若转载,请注明出处

    转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com

    标题:javaweb学习总结(三十九)——数据库连接池

    链接:https://www.javajike.com/article/1120.html

« javaweb学习总结(三十七)——获得MySQL数据库自动生成的主键
在Servlet使用getServletContext()获取ServletContext对象出现java.lang.NullPointerException(空指针)异常的解决办法»

相关推荐

QR code