五十五、数据库连接池

一、 连接池概述

概念
在Java开发中,使用JDBC操作数据库的四个步骤如下:
①加载数据库驱动程序(Class.forName("数据库驱动类");)
②连接数据库
(Connection con = DriverManager.getConnection();)
③操作数据库
(PreparedStatement stat = con.prepareStatement(sql);stat.executeQuery();)
④关闭数据库,释放连接(con.close();)

也就是说,所有的用户都需要经过此四步进行操作,但是这四步之中有三步(①加载数据库驱动程序、②连接数据库、④关闭数据库,释放连接)对所有人都是一样的,而所有人只有在操作数据库上是不一样,那么这就造成了性能的损耗。

那么最好的做法是,准备出一个空间,此空间里专门保存着全部的数据库连接,以后用户用数据库操作的时候不用再重新加载驱动、连接数据库之类的,而直接从此空间中取走连接,关闭的时候直接把连接放回到此空间之中。那么此空间就可以称为连接池(保存所有的数据库连接。

连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用;简单理解为,当一辆汽车搬运东西,如果使用jdbc连接,(jdbc连接:与数据库建立连接、发送操作数据库的语句并处理结果)那么每一次都要去打开数据库,获得连接,关闭数据库。假设汽车搬运的东西是连接,那么我可不可以每一次将连接搬运多个呢?而不是jdbc那样,一次只搬运一个连接,然后就把汽车扔掉?这时候,使用连接池。


连接池

原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

规范
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
连接池主要由三部分组成:连接池的建立、连接池中连接的使用管理、连接池的关闭。
常见的连接池:DBCP、C3P0。

二、C3P0连接池的使用

1. 导入jar包

  • MySQL:
    mysql-connector-java-5.1.30-bin.jar
    c3p0-0.9.5.2.jar
    mchange-commons-java-0.2.11.jar

  • Oracle:
    c3p0-0.9.1.2.jar
    c3p0-oracle-thin-extras-0.9.1.2.jar
    mchange-commons-java-0.2.11.jar

2. 配置c3p0-config.xml

c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 第一种配置方法 获取方法DataSource ds = new ComboPooledDataSource(); <default-config> 
        </default-config> 第二种配置方法可以指定数据库 获取方法 DataSource ds = new ComboPooledDataSource("mysql"); 
        <named-config name="mysql"></named-config> -->
    <named-config name="mysql">
        <!--JDBC驱动 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <!--数据库地址 -->      
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/scott?useUnicode=true&amp;characterEncoding=utf8
        </property>
        <!--用户名。Default: null -->
        <property name="user">root</property>
        <!--密码。Default: null -->
        <property name="password">123456</property>
        <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
        <property name="initialPoolSize">3</property>
        <!--最大空闲时间,30秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
        <property name="maxIdleTime">30</property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize">50</property>
        <!--连接池中保留的最少连接数。Default: 15 -->
        <property name="minPoolSize">10</property>
        <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements  属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
            如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
        <property name="maxStatements">200</property>
    </named-config>
    
    <named-config name="oracle">
        <!--JDBC驱动 -->
        <property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
        <!--数据库地址 -->      
        <property name="jdbcUrl">jdbc:oracle:thin:@localhost:1521:orcl
        </property>
        <!--用户名。Default: null -->
        <property name="user">scott</property>
        <!--密码。Default: null -->
        <property name="password">123456</property>
        <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
        <property name="initialPoolSize">3</property>
        <!--最大空闲时间,30秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
        <property name="maxIdleTime">30</property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize">50</property>
        <!--连接池中保留的最少连接数。Default: 15 -->
        <property name="minPoolSize">10</property>
        <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements  属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
            如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
        <property name="maxStatements">200</property>
    </named-config>
</c3p0-config>

3. 简单使用

public class Test {
    static ComboPooledDataSource cpds = null;
    static {
        // 这里有个优点,写好配置文件,想换数据库,简单
        // cpds = new ComboPooledDataSource("oracle");//这是oracle数据库
        cpds = new ComboPooledDataSource("mysql");// 这是mysql数据库
    }

    /**
     * 获得数据库连接
     * 
     * @return Connection
     */
    public static Connection getConnection() {
        try {
            return cpds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 数据库关闭操作
     * 
     * @param conn
     * @param st
     * @param pst
     * @param rs
     */
    public static void close(Connection conn, PreparedStatement pst,
            ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pst != null) {
            try {
                pst.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 测试
     * 
     * @param args
     */
    public static void main(String[] args) {
        Connection conn = getConnection();
        System.out.println(conn.getClass().getName());
        Statement stmt;
        ResultSet rs = null;
        try {
            stmt = conn.createStatement();
            rs = stmt.executeQuery("select * from emp");
            while (rs.next()) {
                System.out.println(rs.getInt(1));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
        }
        }
        close(conn, null, null);
    }
}

注意:使用C3P0之后调用conn.close()方法并没有关闭连接。而是把链接放到线程池里面去了.

4.C3P0的自定义DBUtils


/**
 * 数据库连接工具
 * 
 * @author Administrator
 * 
 */
public class C3P0DBUtils {
    // 饿汉式
    private static DataSource ds = new ComboPooledDataSource("mysql");
    /**
     * 它为null表示没有事务 它不为null表示有事务 当开启事务时,需要给它赋值 当结束事务时,需要给它赋值为null
     * 并且在开启事务时,让dao的多个方法共享这个Connection
     */
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    public static DataSource getDataSource() {
        return ds;
    }

    /**
     * dao使用本方法来获取连接
     * 
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() {
        /*
         * 如果有事务,返回当前事务的con 如果没有事务,通过连接池返回新的con
         */
        Connection con = tl.get();// 获取当前线程的事务连接
        if (con != null)
            return con;
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 开启事务
     * 
     * @throws SQLException
     */
    public static void beginTransaction() {
        Connection con = tl.get();// 获取当前线程的事务连接
        if (con != null) {
            System.out.println("已经开启了事务,不能重复开启!");
        }
        try {
            con = ds.getConnection();
            con.setAutoCommit(false);// 设置为手动提交
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 给con赋值,表示开启了事务
        tl.set(con);// 把当前事务连接放到tl中
    }

    /**
     * 提交事务
     * 
     * @throws SQLException
     */
    public static void commitTransaction(){
        Connection con = tl.get();// 获取当前线程的事务连接
        if (con == null){
            System.out.println("没有事务不能提交!");
        }
        try {
            con.commit();// 提交事务
            con.close();// 关闭连接
        } catch (SQLException e) {
            e.printStackTrace();
        }
        con = null;// 表示事务结束!
        tl.remove();
    }

    /**
     * 回滚事务
     * 
     * @throws SQLException
     */
    public static void rollbackTransaction() {
        Connection con = tl.get();// 获取当前线程的事务连接
        if (con == null){
            System.out.println("没有事务不能回滚!");
        }
        try {
            con.rollback();
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        con = null;
        tl.remove();
    }

    /**
     * 释放Connection
     * 
     * @param con
     * @throws SQLException
     */
    public static void releaseConnection(Connection connection)
            throws SQLException {
        Connection con = tl.get();// 获取当前线程的事务连接
        if (connection != con) {
           // 如果参数连接,与当前事务连接不同,说明这个连接不是当前事务,可以关闭!
           // 如果参数连接没有关闭,关闭之!
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        }
    }

    /**
     * 封裝得到數據庫發送對象的方法
     */
    public static PreparedStatement preparedStatement(Connection conn,
            String sql, Object[] obj) {
        PreparedStatement pstmt = null;
        try {
            pstmt = conn.prepareStatement(sql);
            if (null != obj && obj.length > 0) {
                for (int i = 0; i < obj.length; i++) {
                    pstmt.setObject(i + 1, obj[i]);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pstmt;
    }

    /**
     * 執行SQL的方法 添加 删除 修改
     */
    public static void executeUpdate(PreparedStatement pstmt) {
        try {
            int x = pstmt.executeUpdate();
            if (x > 0) {
                System.out.println("操作数据库成功");
            } else {
                System.out.println("操作数据库失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查询的方法
     */
    public static ResultSet executeQuery(PreparedStatement pstmt) {
        ResultSet rs = null;
        try {
            rs = pstmt.executeQuery();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rs;
    }

    /**
     * 关闭的方法
     */
    public static void close(AutoCloseable closeable) {
        try {
            if (null != closeable) {
                closeable.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/**
     * 关闭的方法
     */
    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        close(rs);
        close(stmt);
        close(conn);
    }

    /**
     * 关闭的方法
     */
    public static void close(Statement stmt, Connection conn) {
        close(stmt);
        close(conn);
    }
}

三、DBCP连接池的使用

DBCP也是一个开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,tomcat内置的连接池。

1.导包

commons-dbcp-1.2.1.jar
commons-pool-1.3.jar
对应的数据库驱动包

2.配置dbcp-config.properties

#配置文件
dbcp.properties 
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/crm?useUnicode=true&amp;characterEncoding=utf8;
username=root
password=root
#初始资源
initialSize=10
#活动资源
maxActive=50
#最大空闲资源
maxIdle=20
#最小空闲资源
minIdle=5
 #资源没有的时候会让用户等待,等待一段时间后,它会填充资源;当空闲资源小于最小空闲资源时,它会往里添加资源并判断是否小于最大空闲资源
maxWait=60000
#编码字符集
characterEncoding=UTF-8
#自动提交
defaultAutoCommit=true

3.简单使用

public class DbcpJdbcUtil {
    private static DataSource ds=null;
    static{
      try{
        InputStream in=DbcpJdbcUtil.class.getResourceAsStream("dbcp.properties");
        Properties prop=new Properties();
        prop.load(in);//以上与1同
        ds=BasicDataSourceFactory.createDataSource(prop);
//工厂,创建Source
      }catch(Exception e){
        e.printStackTrac e();
      }
  }
    public static Connection getConnection() throws SQLException{
        return ds.getConnection();
    }
    public static void release(Connection conn,Statement st,ResultSet rs){
        if(rs!=null){
          try{
               rs.close();
           }catch(Exception e){
               e.printStackTrace();
          }
        rs=null;
  }
      if(st!=null){
        try{
               st.close();
        }catch(Exception e){
               e.printStackTrace();
        }
        st=null;
  }
      if(conn!=null){
       try{
               conn.close();
        }catch(Exception e){
              e.printStackTrace();
       }
       conn=null;
    }
  }

//查询功能,将结果集中第一条记录封装到一个指定的javaBean中。
  public static void main(String[] args){
       try{

              QueryRunner qr = new QueryRunner(ds);//此处需传入一个datasource对象           
              String sql="SELECT * FROM user";
              Object[] params = {};
              User u = qr.query(sql, new BeanHandler<User>(User.class), params);
            //结果集处理
              System.out.println(u);

       }catch(SQLException e){
              throw new RuntimeException(e);
       }
}

}

输出:
输出

四、自定义连接池

【连接池的原理】
在javax.sql.DataSource接口--连接池的接口

  • 功能:初始化多个连接.把多个连接放入到内存中.
  • 归还:将连接对象放回到内存中.
public class MyDataSource implements DataSource{

    // 创建一个集合List集合.
    List<Connection> list = new ArrayList<Connection>();
    
    // 初始化连接池的时候,初始化5个连接.
    public  MyDataSource() {
        for (int i = 1; i <= 5; i++) {
            Connection conn = JDBCUtils.getConnection();
            list.add(conn);
        }
    }
    
    @Override
    // 从连接池中获得连接
    public Connection getConnection() throws SQLException {
        if(list.size()==0){
            for (int i = 1; i <= 3; i++) {
                Connection conn = JDBCUtils.getConnection();
                list.add(conn);
            }
        }
        Connection conn = list.remove(0);
        return conn;
    }
    
    // 归还连接:
    public void addBack(Connection conn){
        list.add(conn);
    }
...
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容