全面掌握apache-commons-dbcp之一:学会使用dbcp

开始使用dbcp

  • 开发环境及版本:
  1. Java Version:JDK 1.6
  2. IDE:Eclipse 4.3
  3. dbcp:dbcp 1.4

首先打开eclipse,首先新建一个Maven的pom项目,配置一些常用的插件。具体的pom.xml的如下:


    4.0.0
    com.xg.testproject
    mydbcp
    0.0.1-SNAPSHOT
    pom
    
    
        clean compile 
        
            
            
                org.apache.maven.plugins
                maven-clean-plugin
                3.0.0
            
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.2
                
                    1.6
                    1.6
                    UTF8
                
            
        

        
        
            
                src/main/java
                true
                
                    **/*.xml
                    **/*.properties
                
            
            
                src/main/resources
                true
            
        
    

    
    

下载和编译dbcp源码

为了方便查看和学习,在dbcp使用阶段,我选择自己编辑源码,这样做首先我们可以清晰看到dbcp在设计实现的时候,依赖了哪些包,同时也方便我们dubug断点查看它的工作流程,为稍后详细的分析dbcp的设计和使用逻辑打下基础。

dbcp 1.4的源码可以在官网找到:

全面掌握apache-commons-dbcp之一:学会使用dbcp_第1张图片
下载dbcp 1.4

下载完成后解压,得到dbcp的源码文件,解压的目录结构如下:
全面掌握apache-commons-dbcp之一:学会使用dbcp_第2张图片
解压后的文件目录

在这些文件中,我们仅需要关注src目录和pom.xml文件。其他文件夹,像doc和xdocs都是项目的文档,可以不用理会,剩下的文件一部分是checkstyle,findbugs这类工具的配置,一部分是项目自身的描述文件,各位有兴趣的可以单独去研究一下,这里就不一一介绍了。

接下来,按照下述步骤,将代码迁移到我们自己的项目中:

  1. 新建项目:
    选中我们项目的pom.xml,鼠标右键-》Maven-》New Maven Model Project,得到如下界面:


    全面掌握apache-commons-dbcp之一:学会使用dbcp_第3张图片
    新建Maven项目

    输入model名称(我自己新建的项目名称是dbcp),点击Finish按钮,完成项目创建。

  2. 迁移源码:将src/java下的内容,整个copy到dbcp项目下的src/java目录下;
  3. 添加依赖:打开下载下来的pom.xml,将标签的内的内容
    
        
        
            commons-pool
            commons-pool
            1.5.4
        
        
        
            org.apache.geronimo.specs
            geronimo-jta_1.1_spec
            1.1.1
            true
        
        
        
            tomcat
            naming-common
            5.0.28
            test
        
        
            tomcat
            naming-java
            5.0.28
            test
        
        
        
            commons-logging
            commons-logging
            1.1.1
            test
        
        
        
            org.apache.geronimo.components
            geronimo-transaction
            2.0.1
            test
        
    

copy到dbcp项目的pom.xml的内。

  1. 编译项目:选中我们新建的项目,鼠标右键-》Maven-》Update Project

按照上述步骤,如果没有看到报错,就证明dbcp的源码已经编译成功了。

总结一下dbcp的配置

在当前项目基础上,再新建一个jar包,并引入我们刚刚自己新建编译的dbcp源码包(以后统一简称为本地dbcp包),此时,我们就可以着手实现使用dbcp连接池的应用了。像所有的连接池一样,在使用之前,我们要先来看一下都有哪些配置项。在dbcp官网的api页面上,各个配置项都有详细的描述,我们就结合官网的内容,简单总结一下。

按照功能类型,dbcp的配置项可以大致分为以下几类:

  1. 核心基础配置
  • username
  • password
  • url
  • driverClassName

有点JDBC编程基础的人一看就知道上面这几个参数分别是干啥用的了,没有它们,“连接池”中的“连接”,就无从谈起了。它们是dbcp的基本配置,其他属性都有默认值,唯独这四个属性一定要由用户指定,否则就dbcp就无法使用。话说回来,一个连接池不论性能好坏,功能多少,它最重要的作用,就是得到一个java.sql.Connection对象。

  1. connection属性的配置
  • connectionProperties:连接属性,以键值对的形式将下面这些具体的属性串起来
  • defaultAutoCommit:是否默认自动提交
  • defaultReadOnly:是否为只读
  • defaultTransactionIsolation:默认的事务隔离级别
  • defaultCatalog:connection的默认路径

这部分配置的作用都是标记从池中获取的connection应该具备的属性,而它们是否生效,就要看具体的JDBC驱动是不是支持了。

  1. 池属性的配置
  • initialSize
  • maxActive
  • maxIdle
  • minIdle
  • maxWait

又是一组关键的配置,这组配置的作用控制连接池的容量。

一个稳定的连接池,其对系统资源的占用应该是稳定在一个固定的范围内的,maxActive、maxIdle和minIdle的这三个参数的作用正在于此。首先,maxActive的意思是,池中最多可容纳的活着的连接数量,它是整个连接池的边界,当池中活着的连接达到这个数值时,dbcp将不会再新建connection,而是等待获取其他线程释放的。maxIdle的意思是连接池最多可以保持的连接数,应用场景就是dbcp会定时回收池中那些空闲连接(已激活但未使用的连接),直到池中的数量减少到maxIdle为止。minIdle是maxIdle的反义词,及连接池中最少保持的连接。maxWait定义了获取连接的等待时间,如果超过这个时间则抛出异常(默认配置),initialSize则定义了dbcp在初始化时,新建的连接数量。

  1. 高可用属性的配置
  • connectionInitSqls
  • testOnBorrow
  • testOnReturn
  • validationQuery
  • validationQueryTimeout

在连接池的使用过程中保持高可用,是一个优秀的连接池必不可少的素质,那如何做到这一点呢,dbcp给出的答案就在上面这些配置项上。

connectionInitSqls是在一个connection被创建之后调用的一组sql,这组sql可用来记录日志,也可以用来初始化一些数据,总之,经过connectionInitSqls之后的connection一定是正常可用的。testOnBorrow和testOnReturn的关注点则在connection从池中“取出”和“归还”,这两个关键的动作上,当他们被设置为true时,在取出和归还connection时,都需要完成校验,如果校验不通过,这个connection将被销毁。校验的sql由validationQuery定义,且定义的sql语句必须是查询语句,而且查询至少一列。validationQueryTimeout定义的是校验查询时长,如果超过这个时间,则认定为校验失败。

除了上述配置,dbcp在运行时还在内部维护了一个“清理器”(Eviction),主要用于销毁那些已被创建,但长时间未被使用的连接,Eviction在运行的时候,会用到下列属性:

  • testWhileIdle:清楚一个连接时是否需要校验
  • timeBetweenEvictionRunsMillis:Eviction运行的时间周期
  • numTestsPerEvictionRun:Eviction在运行时一次处理几个连接

PreparedStatements是可以缓存是,尤其在一些支持游标的数据库中(Oracle、SQL Server、DB2、Sybase),启用PreparedStatements缓存和不启用直接的性能可能相差一个数量级。dbcp配置PreparedStatements缓存主要用到以下两个配置。

  • poolPreparedStatements:是否缓存PreparedStatements
  • maxOpenPreparedStatements:缓存PreparedStatements的最大个数

普通项目使用dbcp

介绍完dbcp的配置项,我们就可以实际动手,开始使用它了。

  • 新建一个普通的java项目:新建项目的方式和刚刚新建dbcp源码项目的方法一样。
  • 添加依赖和配置文件:

项目建好之后,首先需要引入必须的依赖,打开项目的pom.xml,将下面的内容copy到标签内:

        
        
            junit
            junit
            4.10
            test
           
        
        
        
            com.xg.testproject
            dbcp
            0.0.1-SNAPSHOT
           
        
        
            
            com.oracle    
            ojdbc6    
            11.2.0.4
        

在项目的../src/main/resources/路径下,新建一个properties类型的文件dbcp.properties,文件内容如下:

# dbcp配置 
# 参考说明:http://commons.apache.org/proper/commons-dbcp/configuration.html
username=scott
password=tiger
url=jdbc:oracle:thin:@192.168.22.63:1521:orcl
driverClassName=oracle.jdbc.OracleDriver
  • 创建数据源类

直接在src目录下创建class的话不是很规范,所以我们先来建一个包:com.xg.testproject.dbcp.simple,建好之后,创建一个名为MyDBCPDataSource.java的文件,源码如下:

package com.xg.testproject.dbcp.simple;

import java.io.IOException;
import java.util.Properties;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;

public class MyDBCPDataSource {

    // 单例数据源
    public static BasicDataSource DBCPDATASOURCE = getDBCPDataSource();

    private static BasicDataSource getDBCPDataSource() {
        // 加载配置文件
        Properties properties = new Properties();
        try {
            properties.load(MyDBCPDataSource.class.getResourceAsStream("/dbcp.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        BasicDataSource dataSource = new BasicDataSource();
        // 获取配置
        if(properties.isEmpty()){
            return null;
        }
        // 基础配置
        dataSource.setUsername(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        dataSource.setUrl(properties.getProperty("url"));
        dataSource.setDriverClassName(properties.getProperty("driverClassName"));
        // 数据库连接配置,主要由数据库驱动提供
        dataSource.setDefaultAutoCommit(Boolean.parseBoolean(oracle.jdbc.OracleConnection.CONNECTION_PROPERTY_AUTOCOMMIT_DEFAULT));
        // ...
        // 连接池的相关配置,这部分的默认配置完全由apache-commons-pool组件提供
        dataSource.setInitialSize(GenericObjectPool.DEFAULT_MIN_IDLE);
        dataSource.setMaxActive(GenericObjectPool.DEFAULT_MAX_ACTIVE);
        dataSource.setMaxIdle(GenericObjectPool.DEFAULT_MAX_ACTIVE);
        dataSource.setMinIdle(GenericObjectPool.DEFAULT_MIN_IDLE);
        dataSource.setMaxWait(GenericObjectPool.DEFAULT_MAX_WAIT);
        dataSource.setTestOnBorrow(GenericObjectPool.DEFAULT_TEST_ON_BORROW);
        dataSource.setTestOnReturn(GenericObjectPool.DEFAULT_TEST_ON_RETURN);
        dataSource.setTestWhileIdle(GenericObjectPool.DEFAULT_TEST_WHILE_IDLE);
        // 其他配置见 http://commons.apache.org/proper/commons-dbcp/configuration.html
        return dataSource;
    }
}

  • 使用连接池:数据源创建好后,就可以测试使用了

新建一个AppMain.java,源码如下:

package com.xg.testproject.dbcp.simple;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.commons.dbcp.BasicDataSource;

public class AppMain {

    public static void main(String[] args) {
        
        BasicDataSource dataSource = MyDBCPDataSource.DBCPDATASOURCE;
        System.out.println("当前数据库连接池的容量" + dataSource.getNumActive());
        Connection conn = null;
        Statement sm = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            sm = conn.createStatement();
            rs = sm.executeQuery("SELECT ID FROM EASYPASSPORT_USERS ");
            while (rs.next()) {
                long id = rs.getLong(1);
                System.out.println("从数据库中得到一条记录的值" + id);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                sm.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                System.out.println("当前数据库连接池的容量" + dataSource.getNumActive());
                conn.close();
                System.out.println("当前数据库连接池的容量" + dataSource.getNumActive());
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

直接运行AppMain,可以看到程序正常运行,普通Java项目使用dbcp的例子也就到此结束

Spring集成dbcp

Spring是现行的Java应用开发标准,同样它在Java持久层方面的贡献也十分突出,但是“弱水三千只取一瓢”,我们今天仅简单介绍一下Spring如何集成dbcp并完成一些简单操作,更多Spring在Java持久层的内容,以后我会专门开专题去介绍。

  • 新建一个java项目
  • 添加Spring运行环境

为了配合dbcp的版本,Spring的版本我选择的是3.0.5(我本地仓库里恰好已经下载好了,其他3.x版本的Spring都可以),结合下图,我们可以清晰的看到需要将哪些依赖添加到项目中。


全面掌握apache-commons-dbcp之一:学会使用dbcp_第4张图片
Spring 3.x的体系架构

具体的pom.xml的内容如下:

    
        
        
            org.springframework
            spring-beans
            3.0.5.RELEASE
        
        
            org.springframework
            spring-core
            3.0.5.RELEASE
        
        
            org.springframework
            spring-context
            3.0.5.RELEASE
        
        
            org.springframework
            spring-expression
            3.0.5.RELEASE
        
        
        
          org.springframework
          spring-tx
          3.0.5.RELEASE
        
        
            org.springframework
            spring-jdbc
            3.0.5.RELEASE
        
        
        
            junit
            junit
            4.10
            test
           
        
        
            com.xg.testproject
            dbcp
            0.0.1-SNAPSHOT
           
        
            
            com.oracle    
            ojdbc6    
            11.2.0.4
               
        
        
            cglib
            cglib
            2.2.2
               
  • Spring配置

在项目的../src/main/resources/路径下,添加dbcp.properties(和之前一样)后,添加Spring的配置文件applicationContext.xml:



    
    
    
    
    
    
        
        
        
        
        
        
        
        
        
    
    
    
    
        
            
        
    
    
    
    
        
    
    

  • 自定义业务操作Bean

完成了Spring的配置后,就可以定义我们自己的的业务实现Bean了,源码如下。

package com.xg.testproject.dbcp.spring;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

public class MyDBCPSpringTest {

    private JdbcTemplate template;
    
    
    public void setTemplate(JdbcTemplate template) {
        this.template = template;
    }


    @Transactional
    public long getNum(){
        return template.queryForLong("SELECT ID FROM EASYPASSPORT_USERS ");
    }
    
    @Transactional
    public double getSal(){
        return template.queryForLong("SELECT SAL FROM EMP WHERE EMPNO = 7369 ");
    }
    
    @Transactional
    public int setSal(Double sal){
        return template.update("UPDATE EMP T SET T.SAL = " + sal + " WHERE T.EMPNO = 7369");
    }
}

定义好之后,还要将我们这个bean的配置添加到applicationContext.xml中。

    
        
            
        
    
  • 测试使用
package com.xg.testproject.dbcp.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppMain {

    public static void main(String[] args) {
    
        // 启动Spring容器,并加载配置文件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
        // 从容器中获取我们定义的业务操作bean
        MyDBCPSpringTest test = (MyDBCPSpringTest)ctx.getBean("myDBCPSpringTest");  
        // 完成业务操作
        System.out.println("从数据库中得到一条记录的值" + test.getNum());
        System.out.println("更新数据库中的一条记录");
        test.setSal(new Double("808.01"));
        System.out.println("得到刚刚更新的记录" + test.getSal());
    }
}

使用Tomcat内置的dbcp

在Tomcat 7之前,Tomcat都是默认将dbcp作为其数据库连接池的实现的,一些有相互依赖,同时规模也不是很大的应用,使用Tomcat内部集成的dbcp也不失为另一种选择。

  • Tomcat配置

要使用Tomcat集成的dbcp,应首先配置JNDI,具体做法如下。

  1. 给当前Eclipse项目添加Server Runtime Environments
    按步骤操作,Windows-》Preferences-》Server-》Runtime Environment,得到如下界面,点击Add按钮。


    全面掌握apache-commons-dbcp之一:学会使用dbcp_第5张图片
    添加Server Runtime Environments第一步

选择Apache Tomcat v6.0。


全面掌握apache-commons-dbcp之一:学会使用dbcp_第6张图片
添加Server Runtime Environments第二步

选中本地的tomcat 6路径,点击Finish。


全面掌握apache-commons-dbcp之一:学会使用dbcp_第7张图片
添加Server Runtime Environments第三步

上述步骤都完成后,就可以在本地查看Eclipse自动生成的本地Servers文件


全面掌握apache-commons-dbcp之一:学会使用dbcp_第8张图片
本地Servers镜像文件
  1. 修改tomcat的contex.xml

选中上图中的servers文件夹中的context.xml,右键-》Open,在标签内,添加如下内容:

    

至此,Tomcat的配置就完成了。

  1. 新建web项目:
    新建web的内容请参考拙作《快速搭建web项目》

  2. 在程序中使用dbcp

在新建的web中添加一个service类,用来获取获取Tomcat容器中的数据源,并完成jdbc操作。源码如下:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class MainService {

    public String userDb() throws Exception {
        // 初始化上下文环境
        Context context = new InitialContext();
        // 从上下文中获取JDBC资源,其中java:/comp/env/jdbc是通用的写法mydb是JNDI的名称
        DataSource ds = (DataSource) context.lookup("java:/comp/env/jdbc/mydb");
        // 执行sql脚本
        Connection conn = null;
        Statement sm = null;
        ResultSet rs = null;
        String rtnStr = "";
        try {
            conn = ds.getConnection();
            sm = conn.createStatement();
            rs = sm.executeQuery("SELECT EMAIL FROM EASYPASSPORT_USERS WHERE ID = 999");
            while (rs.next()) {
                rtnStr = rs.getString(1);
                System.out.println("从数据库中得到一条记录的值" + rtnStr);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                sm.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return rtnStr;
    }
}

然后新建一个servlet,用来验证service类是否生效。

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.xg.testproject.dbcp.javaee.service.MainService;

public class MainServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doPost(req, resp);
    }
    
    @Override
    protected void doPost(final HttpServletRequest req,
            final HttpServletResponse resp) throws ServletException,
            IOException {
        String rtnStr = "";
        try {
            rtnStr = new MainService().userDb();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        resp.setCharacterEncoding("UTF-8");
        PrintWriter writer;
        try {
            writer = resp.getWriter();
            writer.print(rtnStr);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            System.out.println("向客户端返回数据时发生异常!");
            e.printStackTrace();
        }
    }
}

配置web.xml。

    
    
        MainServlet
        com.xg.testproject.dbcp.javaee.servlet.MainServlet
    
    
        MainServlet
        /main.do
    

    
        DB Connection
        jdbc/mydb
        javax.sql.DataSource
        Container
     

启动Tomcat,并将当前web项目添加到容器中,打开浏览器,键入url:,可见浏览器中显示下图。


全面掌握apache-commons-dbcp之一:学会使用dbcp_第9张图片
查看运行效果

此时查看后台日志,得到:

2017-2-22 18:01:11 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8088
2017-2-22 18:01:11 org.apache.catalina.startup.Catalina load
信息: Initialization processed in 1799 ms
2017-2-22 18:01:11 org.apache.catalina.core.StandardService start
信息: Starting service Catalina
2017-2-22 18:01:11 org.apache.catalina.core.StandardEngine start
信息: Starting Servlet Engine: Apache Tomcat/6.0.35
2017-2-22 18:01:13 org.apache.coyote.http11.Http11Protocol start
信息: Starting Coyote HTTP/1.1 on http-8088
2017-2-22 18:01:14 org.apache.jk.common.ChannelSocket init
信息: JK: ajp13 listening on /0.0.0.0:8009
2017-2-22 18:01:14 org.apache.jk.server.JkMain start
信息: Jk running ID=0 time=0/164  config=null
2017-2-22 18:01:14 org.apache.catalina.startup.Catalina start
信息: Server startup in 2519 ms
从数据库中得到一条记录的值[email protected]

证明我们已经成功调用了Tomcat内部基础的dbcp。

参考

  • 连接池详解
  • Oracle JDBC Connection默认常量
  • 数据库连接池性能比对

你可能感兴趣的:(全面掌握apache-commons-dbcp之一:学会使用dbcp)