数据库连接池

  • 数据库连接池

    • 数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现在的数据库连接,而不是再重新建立一个. 这项技术能明显提高对数据库操作的性能
  • DataSource接口概述

    • javax.sql.DataSource接口: 数据源(数据库连接池). Java官方提供的数据库连接池规范(接口)
    • 如果想完成数据库连接池技术, 就必须实现DataSource接口
    • 核心功能: 获取数据库连接对象: Connection getConnection()
  • 自定义数据库连接池

    • 定义一个类, 实现DataSource接口
    • 定义一个容器, 用于保存多个Connection连接对象
    • 定义静态代码块, 通过JDBC工具类获取10个连接保存到容器中
    • 重写getConnection方法, 从容器中获取一个连接并返回
    • 定义getSize方法, 用于获取窗口的大小并返回
package com.test001;

import com.lizicai.utils.JDBCUtils;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

public class MyDataSource implements DataSource {
    // 1. 准备窗口, 用于保存多个连接对象, 变成线程安全的
    private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());

    // 2. 定义代码块, 通过工具类获取10个连接对象
    static {
        for(int i=0;i<10;i++){
            Connection con = JDBCUtils.getConnection();
            pool.add(con);
        }
    }
    //3 重写getConnection() , 用于获取一个连接对象
    @Override
    public Connection getConnection() throws SQLException {
        if( pool.size()> 0){
            Connection con = pool.remove(0);
            return con;
        } else {
            throw new RuntimeException("连接数量已用尽");
        }
    }

    // 4. 定义getSize方法, 获取连接池窗口的大小
    public int getSize(){
        return pool.size();
    }
}
package com.test001;

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

public class MyDataSourceTest {
    public static void main(String[] args) throws SQLException {
        MyDataSource pool = new MyDataSource();

        System.out.println(pool.getSize());

        Connection con = pool.getConnection();

        String sql = "SELECT * FROM student";

        PreparedStatement pst = con.prepareStatement(sql);

        ResultSet rs = pst.executeQuery();

        while (rs.next()){
            System.out.println(rs.getInt("sid")+"\t"+rs.getString("NAME")
            +"\t"+rs.getInt("age")+"\t"+rs.getDate("birthday"));
        }

        rs.close();

        pst.close();

        con.close();

        System.out.println(pool.getSize());

    }
}

归还方式

  • 归还数据库连接的方式
    • 继承方式
    • 装饰设计模式
    • 我在适配器设计模式
    • 动态 代理方式

归还方式 - 继承方式

  • 继承方式归还数据库连接的思想。
    • 通过打印连接对象,发现 DriverManager 获取的连接实现类是 JDBC4Connection
    • 那我们就可以自定义一个类,继承JDBC4Connection这个类,重写closeQ 方法,完成连接对象的归还
  • 继承方式归还数据库连接的实现步骤。
    • 定义一个类,继承JDBC4Connection。
    • 定义 Connection 连接对象和连接池容器对象的成员变量。
    • 通过有参构造方法完成对成员变量的赋值。
    • 重写cose 方法,将连接对象添加到池中。
  • 继承方式日还数据库连接存任的问题。
    • 通过查看 JDBC 工具类获取连接的方法发现:我们星然白定义了一个子类,完成了归还连接的操作。但是
    • DriverManager 获取的还是JDBC4Connection这个对象,并不是我们的子类对象,而我们又不能整体去修改驱动包中类的功能,所继承这种方式行不通!

归还连接 - 装饰设计模式

  • 装饰设计模式日还数据库连接的思想。
    • 我们可以自定义一个类,实现 Connection接口。这样就具备了和 JDBC4Connection相同的行为了
    • 重写close()方法,完成连接的归还。其余的功能还调用 mysql驱动包实现类原有的方法即可
  • 装饰设计模式归还数据库连接的实现步骤。
    • 定义一个类,实现 Connection 接口
    • 定义 Connection 连接对象和连接池容器对象的成员变量
    • 通过有参构造方法完成对成员变量的赋值
    • 重写close()方法,将连接对象添加到池中
    • 剩余方法,只需要调用mysql驱动包的连接对象完成即可
    • 在自定义连接池中,将获取的连接对象通过自定义连接对象进行包装
  • 装饰设计模式归还数据库连接存在的问题。
    • 实现 Connection接口后,有大量的方法需要在自定义类中进行重写
package com.test002;

import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 1. 定义一个类, 实现Connection接口
 * 2. 定义连接对象和连接池容器对象的成员变量
 * 3. 通过有参构造方法为成员变量赋值
 * 4. 重写close()方法, 完成归还连接
 * 5. 剩余方法, 还是调用原有的连接对象中的功能即可
 */

public class MyConnection2 implements Connection {
    private Connection con;
    private List<Connection> pool;

    public MyConnection2(Connection con, List<Connection> pool) {
        this.con = con;
        this.pool = pool;
    }

    @Override
    public void close() throws SQLException {
        pool.add(con);
    }
}
public class MyDataSource implements DataSource {
    //3 重写getConnection() , 用于获取一个连接对象
    @Override
    public Connection getConnection() throws SQLException {
        if( pool.size()> 0){
            Connection con = pool.remove(0);
            MyConnection2 myCon = new MyConnection2(con,pool);
            return myCon;
        } else {
            throw new RuntimeException("连接数量已用尽");
        }
    }
}

归还连接 - 适配器设计模式

  • 适配器设计模式归还数据库连接的思想。
    • 我们可以提供一个适配器类,实现 Connection 接口,将所有方法进行实现(除了close方法)
    • 自定义连接类只需要继承这个适配器类,重写需要改进的close0 方法即可
  • 适配器设计模式归还数据库连接的实现步骤。
    • 定义一个适配器类, 实现 Connection 接口。
    • 定义 Connection 连接对象的成员变量。
    • 通过有参构造方法完成对成员变量的赋值。
    • 重写所有方法(除了close), 调用mysq驱动包的连接对象完成即可。
    • 定义一个连接类,继承适配器类。
    • 定义 Connection 连接对象和连接池容器对象的成员变量,并通过有参构造进行赋值。
    • 重写close()方法,完成归还连接。
    • 在自定义连接池中,将获取的连接对象通过自定义连接对象进行包装。
  • 适配器设计模式归还数据库连接存在的问题。
    • 自定义连接类虽然很简洁, 但我在楼下器还是自己编写的, 也比较麻烦
public abstract class MyAdapter implements Connection {
    private Connection con;

    public MyAdapter(Connection con){
        this.con = con;
    }
    //省略其他实现的方法
}
package com.test002;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class MyConnection3 extends MyAdapter{
    private Connection con;
    private List<Connection> pool;

    public MyConnection3(Connection con, List<Connection> pool) {
        super(con);
        this.con = con;
        this.pool = pool;
    }

    @Override
    public void close() throws SQLException {
        pool.add(con);
    }
}

动态代理

  • 动态代理: 在不改变目标对象方法的情况下对方法进行增强
  • 组成
    • 被代理对象: 真实的对象
    • 代理对象: 内存的一个对象
  • 要求
    • 代理对象必须和被代理对象实现相同的接口
  • 实现
    • Proxy.newProxyInstance()
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.eat("饭");
        stu.study();

        /**
         * 要求: 在不改Student类中任何代码前提下, 通过study方法输出一句话
         * 类加载器: 和被代理对象使用相同的类加载器
         * 接口类型Class数组: 和被代理对象使用相同接口
         * 代理规则: 完成代理增加的功能
         */
        StudentInterface proxyStu = (StudentInterface) Proxy.newProxyInstance(stu.getClass().getClassLoader(), new Class[]{StudentInterface.class}, new InvocationHandler() {
            /**
             * 执行student类中所有方法都会经过invoke方法
             * 对method方法进行判断
             *  如果是study, 则对其增强
             *  如果不是, 还调用学生对象原的功能即可
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(method.getName().equals("study")){
                    System.out.println("学快点");
                    return null;
                } else {
                    return method.invoke(stu, args);
                }
            }
        });

        proxyStu.eat("米饭");
        proxyStu.study();
    }
}

归还连接 - 动态代理方式

  • 动态代理方式归还数据库连接的思想。
    • 我们可以通过 Proxy 来完成对 Connection 实现类对象的代理
    • 代理过程中判断如果执行的是 close 方法,就将连接归还池中。如果是其他方法则调用连接对象原来 的功能即可
  • 动态代理方式归还数据库连接的实现步骤。
    • 定义一个类,实现 DataSource接口
    • 定义一个容器,用于保存多个Connection连接对象
    • 定义静态代码块,通过JDBC工具类获取 10 个连接保存到容器中
    • 重马getConnection 方法,从容器中获取一个连接
    • 通过 Proxy 代理,如果是close 方法,就将连接归还池中。如果是其他方法则调用原有功能
    • 定义 getsize 方法,用于获取容器的大小并返回
  • 动态代理方式归还数据库连接存在的问题。
    • 我们自己写的连接池技术不够完善,功能也不够强大
package com.test001;

import com.lizicai.utils.JDBCUtils;
import com.test002.MyConnection2;
import com.test002.MyConnection3;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

public class MyDataSource implements DataSource {
    // 1. 准备窗口, 用于保存多个连接对象, 更改List为线程安全的
    private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());

    // 2. 定义代码块, 通过工具类获取10个连接对象
    static {
        for(int i=0;i<10;i++){
            Connection con = JDBCUtils.getConnection();
            pool.add(con);
        }
    }
    // 动态代理归还Connection
    @Override
    public Connection getConnection() throws SQLException {
        if( pool.size()> 0){
            Connection con = pool.remove(0);
            Connection proxyCon =(Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
                /** 执行Connection 实现类连接对象所有的方法都会经过invoke
                 * 如果是close方法, 归还连接
                 * 如果不是, 直接执行连接对象原有的功能即可
                 *
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if(method.getName().equals("close")){
                        pool.add(con);
                        return null;
                    } else {
                        return method.invoke(con, args);
                    }
                }
            });
            return proxyCon;
        } else {
            throw new RuntimeException("连接数量已用尽");
        }
    }

    // 4. 定义getSize方法, 获取连接池窗口的大小
    public int getSize(){
        return pool.size();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

使用C3P0数据库连接池的使用步骤

  • 导入Jar包, c3p0-0.9.5.5.jar和mchange-commons-java-0.2.19.jar
  • 导入配置文件到src目录下
  • 创建C3P0连接池对象
  • 获取数据库连接进行使用

C3P0下载, 开头/c3p0-bin/c3p0-0.9.5.5/c3p0-0.9.5.5.bin.zip

配置文件示例下载Github地址

<?xml version="1.0" standalone="no" ?>

<!--
<!DOCTYPE c3p0-config [
   <!ENTITY extra SYSTEM "https://www.mchange.com/projects/c3p0/extra.xml">
]>-->

<c3p0-config>
  <default-config>
    <!-- <property name="automaticTestTable">con_test</property> -->
     <property name="checkoutTimeout">30000</property>
    <!-- <property name="idleConnectionTestPeriod">30</property> -->
     <property name="initialPoolSize">10</property>
    <!-- <property name="maxIdleTime">30</property> -->
    <!-- <property name="maxIdleTimeExcessConnections">10</property> -->
    <!-- <property name="maxConnectionAge">60</property> -->
    <!-- <property name="propertyCycle">1</property> -->
     <property name="maxPoolSize">5</property>
     <property name="minPoolSize">2</property>
    <!-- <property name="maxStatements">0</property> -->
    <!-- <property name="maxStatementsPerConnection">5</property> -->
    <!-- <property name="maxAdministrativeTaskTime">4</property> -->
    <!-- <property name="connectionCustomizerClassName">com.mchange.v2.c3p0.test.TestConnectionCustomizer</property> -->
    <!-- <property name="unreturnedConnectionTimeout">15</property> -->
    <!-- <property name="debugUnreturnedConnectionStackTraces">true</property> -->

    <!-- <property name="dataSourceName">poop</property> -->

     <property name="driverClass">org.mariadb.jdbc.Driver</property>
     <property name="jdbcUrl">jdbc:mariadb://192.168.0.100:3306/db14</property>
    <property name="user">root</property>
    <property name="password">rootPassword</property>

    <extensions>
      <!-- <property name="myXmlKey">myXmlVal</property> -->
      <!-- <property name="myKey">myOverriddenVal</property> -->
    </extensions>


    <user-overrides user="swaldman">
      <!--
      <property name="unreturnedConnectionTimeout">5</property>
      <property name="debugUnreturnedConnectionStackTraces">true</property>
      -->
      <!-- <property name="preferredTestQuery">select poop from doop</property> --><!-- intentionally broken -->
    </user-overrides>

<!--    &extra;-->

  </default-config>


<!--
  <named-config name="dumbTestConfig">
    <property name="maxStatements">200</property>
    <property name="jdbcUrl">jdbc:test</property>
    <property name="dataSourceName">scoop</property>
    <user-overrides user="poop">
      <property name="maxStatements">300</property>
    </user-overrides>
   </named-config>
-->

</c3p0-config>

使用默认的配置拿到连接, 可以正常使用了

 DataSource dataSource = new ComboPooledDataSource();
        Connection con = dataSource.getConnection();
package com.test003;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class C3P0Test2 {
    public static void main(String[] args) throws SQLException {
        DataSource dataSource = new ComboPooledDataSource();

        for(int i=1;i<=11;i++){
            Connection con = dataSource.getConnection();
            System.out.println(i+":"+con);
//            if( 5 == i){
//                con.close();
//            }
        }
    }
}

Druid连接池的使用

  • Druid 数据库连接池的使用步骡。
    • 导入jar包, druid-1.2.8.jar
    • 编写配置文件,放在src目录下。
    • 通过 Properties 集合加载配置文件。
    • 通过 Druid 连接池工厂类获取数据库连接池对象。
    • 获取数据库连接进行使用。

Druid不会自动加载配置文件,需要我们手动加载,但是文样的名称可以自定义

driverClassName=org.mariadb.jdbc.Driver
url=jdbc:mariadb://192.168.0.100:3306/db14
username=root
password=rootPassword
initialSize=5
maxActive=10
maxWait=3000
package com.test004;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class DruidTest1 {
    public static void main(String[] args) throws Exception {

//        获取配置文件的流对象
        InputStream is = DruidTest1.class.getClassLoader().getResourceAsStream("druid.properties");

//        1. 通过 Properties 集合, 加载配置文件
        Properties prop = new Properties();
        prop.load(is);

//        2. 通过Druid连接池工厂类获取数据库连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

//        3. 通过连接池对象获取数据库进行使用
        Connection con = dataSource.getConnection();

        String sql = "SELECT * FROM student";

        PreparedStatement pst = con.prepareStatement(sql);

        ResultSet rs = pst.executeQuery();

        while (rs.next()){
            System.out.println(rs.getInt("sid")+"\t"+rs.getString("NAME")
                    +"\t"+rs.getInt("age")+"\t"+rs.getDate("birthday"));
        }

        rs.close();

        pst.close();

        con.close();
    }
}

创建Druid 工具类

package com.lizicai.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * 数据库连接池的工具类
 */

public class DataSourceUtils {
    // 1. 私有构造方法
    private DataSourceUtils(){}

    // 2. 声明数据源变量
    private static DataSource dataSource;

    // 3. 提供静态代码块, 完成配置文件加载和获取数据库连接池对象
    static {
        try {
            InputStream is = DataSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties prop = new Properties();
            prop.load(is);
            dataSource =  DruidDataSourceFactory.createDataSource(prop);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 4. 提供一个获取数据库连接的方法
    public static Connection getConnection()  {
        Connection con = null;
        try {
            con = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }


    // 5. 提供一个获取数据库连接池对象的方法
    public static DataSource getDataSource() throws Exception {
        return dataSource;
    }



    // 6. 释放资源的方法

    public static void close(Connection con, Statement stat, ResultSet rs){
        if(con != null){
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(stat != null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void close(Connection con, Statement stat){
        if(con != null){
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(stat != null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.test004;

import com.lizicai.utils.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DruidTest2 {
    public static void main(String[] args) throws SQLException {
        Connection con = DataSourceUtils.getConnection();

        String sql = "SELECT * FROM student";

        PreparedStatement pst = con.prepareStatement(sql);

        ResultSet rs = pst.executeQuery();

        while (rs.next()){
            System.out.println(rs.getInt("sid")+"\t"+rs.getString("NAME")
                    +"\t"+rs.getInt("age")+"\t"+rs.getDate("birthday"));
        }

       DataSourceUtils.close(con,pst,rs);
    }
}