MySQL学习笔记(7)-- 数据库连接池(c3p0、druid)

数据库连接池原理

连接池出现的背景
1.数据库连接是一种重要的资源;
2.频繁的创建和销毁连接对象会给服务器带来巨大的压力。
3.基于以上问题产生了数据库连接池技术。

首先我们通过画图的形式来分析一下我们目前所学的jdbc程序的结构。
通过画图分析得出:当前的jdbc程序每次访问数据库都需要创建一个新的连接,访问完毕之后,还需要释放资源。那么在这样的一个过程中,连接的创建和销毁所消耗的资源是远远大于我们发送sql并执行的时间的。基于这样的情况,我们发现我们的jdbc程序将大量的资源浪费在了连接的创建和销毁上。
就像在上海坐地铁,就一站2分钟的路程,往往在买地铁票的过程需要等待至少10分钟以上的时间。这样是不合理的。所以我们 需要对这样的结构进行优化。

这里写图片描述

思考上面的结构,大部分的时间浪费在了创建和销毁上。那么我们能不能实现将这些连接回收和利用呢?这样我们就不需要不停的创建和销毁了。只需要创建一次,放在指定的地方。而我们使用的时候,直接从里面拿就行了。用完放回原来的地方。不去销毁,当我再次使用的时候,去拿就行了。这样的解决方案就是我们需要的。

这里写图片描述

优化后的结构如上所示。

说明:首先创建一定数量的连接,然后放到指定的地方。当我们需要获取连接的时候,直接从指定的地方获取。用完了,我们再将连接放回去。这样就能实现我们连接的回收利用。并且不用花费时间在创建和销毁连接上。

一次性创建多个连接,将多个连接缓存在内存中 ,形成数据库连接池(内存数据库连接集合),如果应用程序需要操作数据库,只需要从连接池中获取一个连接,使用后,并不需要关闭连接,只需要将连接放回到连接池中。
这样做有什么好处?

好处:节省创建连接与释放连接的性能消耗 —- 连接池中连接起到复用的作用,提高程序性能. 降低了数据库的压力.


DataSource 接口

JDBC 定义的连接池规范 : DataSource 接口 : (连接池接口) ,任何数据库连接池都会实现这个接口,并实现DataSource接口的驱动程序

这里写图片描述

接口有一个和新方法 getConnection(); 作用是获取连接

这里写图片描述


一 、c3p0数据库连接池

在Hibernate和Spring 都提供对C3P0连接池支持.

去下载c3p0 开发包 http://sourceforge.net

这里写图片描述

jar包下载完成后添加到lib文件夹

这里写图片描述

在c3p0数据库连接池中提供了一个核心类ComboPooledDataSource
这个类实现了 DataSource 接口。

// 核心连接池类
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();

// 设置四个JDBC基本连接属性
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql:///day04");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("123456");

在ComboPooledDataSource中还定义了一些常用基本连接池属性

acquireIncrement 如果连接池中连接都被使用了,一次性增长3个新的连接
initialPoolSize 连接池中初始化连接数量 默认:3
maxPoolSize 最大连接池中连接数量 默认:15连接
maxIdleTime 如果连接长时间没有时间,将被回收 默认:0 连接永不过期
minPoolSize 连接池中最小连接数量 默认:3
一般选用默认值即可。

准备数据

-- 使用表
use mydb;

-- 创建数据表student
create table student(
    id int primary key auto_increment,
    sname varchar(20) unique not null,
    gender varchar(20) not null,
    address varchar(20) not null
);

-- 插入一些数据记录
insert into student values(null,'张三','男','北京');
insert into student values(null,'李四','男','上海');
insert into student values(null,'王八','男','广东');
insert into student values(null,'迪丽热巴','女','北京');
insert into student values(null,'柳岩','女男','北京');

这里写图片描述

JDBCUtils工具类

public class JDBCUtils {
    private static final String driverClass;
    private static final String url;
    private static final String username;
    private static final String password;

    static {
        //创建一个propertie对象s
        Properties prop = new Properties();
        //尝试加载文件
        try {
            prop.load(new FileReader("jdbc.properties"));
            //如果加载成功,则可以尝试对类属性进行赋值
            driverClass = prop.getProperty("driverClass");
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            password = prop.getProperty("password");
        } catch (IOException e) {
            // 注意 : 配置文件如果加载失败, 直接抛出一个运行时异常
            throw new RuntimeException("配置文件加载失败!");
        }

    }


    private static void loadDriver() {
        try {
            //加载驱动
            Class.forName(driverClass);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("驱动加载失败!");
        }
    }


    //获取连接
    public static Connection getConnection() throws SQLException {

        return DriverManager.getConnection(url, username, password);

    }

    //释放资源
    //必须要释放的资源为connection,statament资源
    //可能要释放的资源是ResultSet资源

    public static void release(Connection conn, Statement stmt, ResultSet rs) {

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            rs = null; //clear to let GC do its work
        }
        release(conn, stmt);
    }

    public static void release(Connection conn, Statement stmt) {

        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            stmt = null; //clear to let GC do its work
        }

        if(conn !=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            conn = null; //clear to let GC do its work
        }

    }

}

代码实现:

1.硬编码实现 – 手动加载配置文件

//硬编码连接
    @Test
    public void test1() throws PropertyVetoException {
        // 1. 创建了一个核心类对象 :
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        // DataSource dataSource = new ComboPooledDataSource();

        // 2. 设置了四个核心参数
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&characterEncoding=UTF-8");
        dataSource.setUser("root");
        dataSource.setPassword("123456");

        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            //获取连接--注意,不能是新建连接,而是从连接池中获取连接
            conn = dataSource.getConnection();

            //操作数据
            String sql = "select * from student;";

            //预编译
            stmt = conn.prepareStatement(sql);

            //直接运行
            rs = stmt.executeQuery();

            //遍历rs结果集
            while(rs.next()){
                int id = rs.getInt("id");
                String sname = rs.getString("sname");
                String gender = rs.getString("gender");
                String adress = rs.getString("address");
                System.out.println(id+"-"+sname+"-"+gender+"-"+address);
            }


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {

            //释放资源
            JDBCUtils.release(conn, stmt, rs);

        }

    }

运行结果:

这里写图片描述

此代码能够正常运行,但是我们并没有看出来连接池的具体体现,将代码稍作修改,进行十次查询操作,不再输出结果集,直接输出conn对象,然后再进行判断。

//查看连接池对象
    @Test
    public void test2() throws PropertyVetoException {
        // 1. 创建了一个核心类对象 :
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        // DataSource dataSource = new ComboPooledDataSource();

        // 2. 设置了四个核心参数
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&characterEncoding=UTF-8");
        dataSource.setUser("root");
        dataSource.setPassword("123456");

        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        for (int i = 0; i < 10; i++) {
            try {
                //获取连接--注意,不能是新建连接,而是从连接池中获取连接
                conn = dataSource.getConnection();

                //操作数据
                String sql = "select * from student;";

                //预编译
                stmt = conn.prepareStatement(sql);

                //直接运行
                rs = stmt.executeQuery();

                System.out.println(i + "  conn" + conn);
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                //释放资源
                JDBCUtils.release(conn, stmt, rs);

            }
        }
    }

运行结果如下:

这里写图片描述

我们可以发现,同一个连接对象被重复调用了。


2.软编码实现 – 加载xml文件

ComboPooledDataSource 默认加载名为c3p0-config的xml文件。
在src目录下创建c3p0-config.xml 文件,也可采用自定义的其他xml文件。

<?xml version="1.0" encoding="UTF-8" ?>

<c3p0-config>
    <!-- 默认配置,c3p0框架默认加载这段默认配置 -->
    <default-config>
        <!-- 配置JDBC 四个基本属性 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&amp;characterEncoding=utf-8</property>
        <property name="user">root</property>
        <property name="password">123456</property>
    </default-config>
    <!-- 可以自定义配置,为这段配置起一个名字,c3p0指定名称加载配置 -->
    <named-config name="my">
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&amp;characterEncoding=utf-8</property>
        <property name="user">root</property>
        <property name="password">123456</property>
    </named-config>
</c3p0-config>

代码实现 :

// 软编码 : 配置文件
@Test
public void test3() throws PropertyVetoException {

    // 1. 创建了一个核心类对象 :
    // new ComboPooledDataSource(); 自动寻找一个文件. 在 src 目录下寻找 c3p0-config.xml 文件.
    // 如果有, 自动加载该 xml 文件中的数据, 如果没有, 报错.
    // DataSource dataSource = new ComboPooledDataSource("my");  // 名称配置
    DataSource dataSource = new ComboPooledDataSource();  // 默认配置

    Connection conn = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;

    for (int i = 0; i < 10; i++) {
        try {
            // 3. 获取连接 (不要新建, 应该从连接池中直接获取)
            conn = dataSource.getConnection();

            // 4. 操作数据
            String sql = "select * from student;";
            // 预编译
            stmt = conn.prepareStatement(sql);
            // 直接执行
            rs = stmt.executeQuery();

            // 遍历结果集
            while (rs.next()) {
                int id = rs.getInt("id");
                String username = rs.getString("sname");
                String password = rs.getString("gender");
                String email = rs.getString("address");
            }

            System.out.println(i + " conn = " + conn);

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 5. 释放资源
            JDBCUtils.release(conn, stmt, rs);
        }
    }
}

这里写图片描述


3.将 JDBC 工具类中的 getConnection 使用数据库连接池对象方式实现

重新编辑后的JDBCUtils工具类:

public class JDBCUtils {

    // c3p0 数据库连接池对象属性
    private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();

    // 获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    // 释放资源
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            rs = null;
        }
        release(conn, stmt);
    }

    public static void release(Connection conn, Statement stmt) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}

代码实现:

// 软编码 : 配置文件
@Test
public void test3() throws PropertyVetoException {

    Connection conn = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;

    for (int i = 0; i < 10; i++) {
        try {
            // 1. 获取连接 
            conn = JDBCUtils.getConnection();

            // 2. 操作数据
            String sql = "select * from student;";
            // 预编译
            stmt = conn.prepareStatement(sql);
            // 直接执行
            rs = stmt.executeQuery();

            // 遍历结果集
            while (rs.next()) {
                int id = rs.getInt("id");
                String username = rs.getString("sname");
                String password = rs.getString("gender");
                String email = rs.getString("address");
            }

            System.out.println(i + " conn = " + conn);

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 3. 释放资源
            JDBCUtils.release(conn, stmt, rs);
        }
    }
}


二 、Druid数据库连接池

DRUID简介

Druid (德鲁伊) 是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

Druid地址:https://github.com/alibaba/druid

DRUID连接池使用的jar包:druid-1.0.9.jar

参数 说明
url 连接数据库的url:mysql : jdbc:mysql://localhost:3306/druid2
username 数据库的用户名
password 数据库的密码
driverClassName 驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 最大连接池数量
maxIdle 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒

在druid数据库连接池中提供了一个核心类:DruidDataSource
该类实现了 DataSource 接口

//创建一个连接池,连接池的参数使用properties中的数据
public static DataSource createDataSource(Properties properties)

我们可以看到DRUID连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。

DRUID连接池的配置文件名称随便,因为该配置文件需要我们手动实现加载。

准备druid.properties文件:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&characterEncoding=utf-8
username=root
password=123456

//一般忽略  
initialSize=5
maxActive=10
maxWait=3000
maxIdle=6
minIdle=3

数据库沿用之前的c3p0中的数据库
说明:此次不再验证连接池连接对象是否复用

案例实现:

1.硬编码

@Test
    public void test() {

        //创建一个druid数据库连接池对象--核心类 DruidDataSource
        DruidDataSource dataSource = new DruidDataSource();
        //设置连接池核心参数
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");

        DruidPooledConnection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            //获取连接
            conn = dataSource.getConnection();

            //操作数据
            String sql = "select * from student";
            stmt = conn.prepareStatement(sql);
            rs = stmt.executeQuery();

            //遍历结果集
            while(rs.next()){
                int id = rs.getInt("id");
                String sname = rs.getString("sname");
                String gender = rs.getString("gender");
                String address = rs.getString("address");
                System.out.println(id+" - "+sname+" - "+gender+" - "+address);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            JDBCUtils.release(conn, stmt, rs);

        }

    }

这里写图片描述

软编码实现:

@Test
    public void test2() {


        //创建properties对象
        Properties prop = new Properties();
        DataSource dataSource = null;
        try {
            //加载文件
            prop.load(new FileReader("druid.properties"));
            // 创建一个数据库连接池对象 (Druid)--核心类 DruidDataSourceFactory
            dataSource = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            //获取连接
            conn = dataSource.getConnection();

            //操作数据
            String sql = "select * from student";
            stmt = conn.prepareStatement(sql);
            rs = stmt.executeQuery();

            //遍历结果集
            while (rs.next()) {
                int id = rs.getInt("id");
                String sname = rs.getString("sname");
                String gender = rs.getString("gender");
                String address = rs.getString("address");
                System.out.println(id + " - " + sname + " - " + gender + " - " + address);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            JDBCUtils.release(conn, stmt, rs);
        }
    }

注意:druid配置文件的key没有模糊查询,不能乱写,不能有任何错误出现,否则程序运行的时候会出错。

猜你喜欢

转载自blog.csdn.net/zyrdfly/article/details/82709363