zl程序教程

您现在的位置是:首页 >  后端

当前栏目

c3p0和druid数据库连接池(连接池分析、德鲁伊多表实例)

实例数据库 分析 连接池 多表 Druid C3P0
2023-09-11 14:22:11 时间

在这里插入图片描述

一、前言引入

  • 什么是数据库连接池?

官方:数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。


吾人为:创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。所以,在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,还更加安全可靠,对象池、线程池、缓存池,各种池子的类比看的 话原理大体是相通的。

  • 传统的连接机制与数据库连接池的运行机制区别

传统统连接: 一般来说,Java应用程序访问数据库的过程是:

  1. 装载数据库驱动程序;
  2. 通过JDBC建立数据库连接;
  3. 访问数据库,执行SQL语句;
  4. 断开数据库连接。

在这里插入图片描述
使用了数据库连接池的机制:

  1. 程序初始化时创建连接池
  2. 使用时向连接池申请可用连接
  3. 使用完毕,将连接返还给连接池
  4. 程序退出时,断开所有连接,并释放资源
  • 为何要使用数据库连接池
    • 假设网站一天有很大的访问量,数据库服务器就需要为每次连接创建一次数据库连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。
    • 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.

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

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

  • 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费

  • 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作

  • 如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放

一、前言引入基本概念介绍
二、使用数据库连接池的关键点
三、数据库连接池的优势和其工作原理
四、德鲁伊连接池工具类实例实现

二、使用数据库连接池的关键点

1、并发问题

为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。使用方法可以参考,相关文献。

2、事务处理

DB连接池必须要确保某一时间内一个 conn 只能分配给一个线程。不同 conn 的事务是相互独立的。

我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
  我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。

3、连接池的分配与释放

连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
  对于连接的管理可使用一LinkedList(双端队列)。每当用户请求一个连接时,系统检查这个LinkedList中有没有可以分配的连接。如果有就把那个最合适的连接分配给他

4、连接池的配置与维护

连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。
  如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。

三、数据库连接池的优势和其工作原理

1、连接池的优势

连接池用于创建和管理数据库连接的缓冲池技术,缓冲池中的连接可以被任何需要他们的线程使用。当一个线程需要用JDBC对一个数据库操作时,将从池中请求一个连接。当这个连接使用完毕后,将返回到连接池中,等待为其他的线程服务。

连接池的主要优点有以下三个方面。

第一、减少连接创建时间。连接池中的连接是已准备好的、可重复使用的,获取后可以直接访问数据库,因此减少了连接创建的次数和时间。

第二、简化的编程模式。当使用连接池时,每一个单独的线程能够像创建一个自己的JDBC连接一样操作,允许用户直接使用JDBC编程技术。

第三、控制资源的使用。如果不使用连接池,每次访问数据库都需要创建一个连接,这样系统的稳定性受系统连接需求影响很大,很容易产生资源浪费和高负载异常。连接池能够使性能最大化,将资源利用控制在一定的水平之下。连接池能控制池中的连接数量,增强了系统在大量用户应用时的稳定性。

2、连接池的工作原理

连接池技术的核心思想是连接复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。

连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。

  • 第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。
  • 第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:

当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待(线程中有拒绝策略),如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。

该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

  • 第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

在这里插入图片描述

3、常用连接池的介绍

DataSource本身只是Oracle公司提供的一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现。我们只需要学习这个工具如何使用即可。常用的连接池实现组件有这些:

  • 阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目
  • DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。
  • C3P0是一个开源的JDBC连接池,目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能。
  • Proxool数据库连接池技术,它是sourceforge下的一个开源项目,这个项目提供一个健壮、易用的连接池,最为关键的是这个连接池提供监控的功能。

本文只讲解C3p0和Druid

四、案例讲解

数据库连接常用的类和参数

方法或包释义
javax.sql.DataSource数据源,也叫连接池
Connection getConnection()从连接池中得到一个连接对象
initialPoolSize连接池一开始已经创建好的连接数
maxPoolSize连接池中最大的连接个数
checkoutTimeout如果连接池中所有的连接对象都在使用,设置等待多久的时间,单位是毫秒。如果超时会抛出异常。

使用JdbcTemplate完成基本的增删改查(单表)

package com.jdbctemplate.jdbcTemplate;

import com.alibaba.druid.util.JdbcUtils;
import com.itheima.jdbctemplate.domain.Emp;
import com.itheima.jdbctemplate.utils.DruidUtil;
import org.junit.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @Author: kangna
 * @Date: 2019/8/16 14:40
 * @Version:
 * @Description: SpringJdbc
 */
public class SpringJdbcTest {
   /*
   update() 执行DML语句
   queryForMap():查询结果集封装为Map集合

    */

   // 获取JDBCTmplate 对象 ,,,,,,,此处 使用了定义的工具类
    private JdbcTemplate template = new JdbcTemplate(DruidUtil.getDataSource());


    @Test
    public void test_01() {
        String sql = "update student set age = 55 where id = ?";
        int count = template.update(sql, 1);
        System.out.println(count);
    }

    /*
    更新操作
     */
    @Test
    public void test_02() {
        String sql = "update student set age = ? where id = ?";
        Object[] objects = {57,1}; // 将要设置的参数定义为数组
        int count = template.update(sql,objects);
        System.out.println(count);
    }

    /**
     * 查询操作,将其封装为Map 集合
     * 这个方法的查询结果集长度只能为 1
     */
    @Test
    public void test_03() {
        String sql = "select * from emp where id = ? "; // select * 只作示例,开发中不建议
        Map<String, Object> map = template.queryForMap(sql,1001); // 传SQL,设置id = 1001
        System.out.println(map);
    }



    /**
     * 查询所有记录,将其封装为List
     */
    @Test
    public void test_04() {
        String sql = "select * from emp";
        List<Map<String, Object>> list = template.queryForList(sql);

        for (Map<String, Object> stringObjectMap : list) {
            System.out.println(stringObjectMap);
        }
    }


    @Test
    public void test_05() {
        String sql = "select * from emp"; // 这里使用了 BeanPropertyRowMapper将 查询数据封装为Emp 对象
        List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
        for (Emp emp : list) {
            System.out.println(emp);
        }

    }

    /**
     * 查询总的记录数
     */
    @Test
    public void test_06() {
        String sql = "select count(id) from emp";
        Long total = template.queryForObject(sql, Long.class);
        System.out.println(total);
    }


    /**
     * 查询 emp 表中所有员工的平均工资,
     *
     */
    @Test
    public void test_07() {
        String sql = "SELECT AVG(salary) FROM emp";
        Double avgSalary = template.queryForObject(sql, Double.class);
        System.out.println(avgSalary);
    }

    /**
     * 需求:模拟用户登录  用户名和  工资  查询某个用户,
     * 需要将  一条查询记录封装为  emp 对象
     *
     *
     * queruForObject 这个方法返回 1 条 记录,否则 出错
     */
    @Test
    public void test_08() {
        String username = "孙悟空";
        double salary = 8000;
        String sql = "SELECT * FROM emp WHERE ename = ? AND salary = ?";
        Object[] arr = {username, salary};
        Emp emp = null;
        try {
            emp = template.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), arr);
        } catch (DataAccessException e) {
            e.printStackTrace();
        } finally {
            if (emp != null) {
                System.out.println("查有此人");
            } else {
                System.out.println("查无此人");
            }
        }

    }

}
4.1 C3p0案例入门

API 介绍
在这里插入图片描述
xml配置文件

<?xml version="1.0" encoding="utf-8"?>
<c3p0-config>
    <!--默认配置-->
    <default-config>
        <!--连接数据库的信息-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/heima</property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <!--连接池的配置信息-->

    </default-config>

    <!--命名配置-->
    <named-config name="javaee85">

        <!--连接数据库的信息-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/heima</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!--连接池的配置信息-->

        <!--初始连接数-->
        <property name="initialPoolSize">3</property>

        <!--最大连接数-->
        <property name="maxPoolSize">8</property>

        <!--超时抛出异常-->
        <property name="checkoutTimeout">2000</property>
    </named-config>

</c3p0-config>

简单测试代码实现

package com.jdbctemplate.jdbcTemplate;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author: kangna
 * @Date: 2019/8/15 22:09
 * @Version:
 * @Description: c3p0 数据库连接池
 */
public class c3p0DemoTest {
    @Test
    public void testc3p0DemoTest() throws SQLException {

        // 创建数据库连接对象
        ComboPooledDataSource ds = new ComboPooledDataSource();
        ds.getInitialPoolSize();
        System.out.println(ds.getInitialPoolSize());

        ComboPooledDataSource ds1 = new ComboPooledDataSource("javaee85"); // 自定义
        ds.getInitialPoolSize();
        System.out.println(ds.getInitialPoolSize());
    }
}

运行结果
在这里插入图片描述

4.2 德鲁伊(Druid)案例综合分析实现

API介绍

4.2.1 DruidUtil工具类代码实现

package com.jdbctemplate.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/**
 * @Author: kangna
 * @Date: 2019/8/16 11:25
 * @Version: 1.0
 * @Description: DruidUtil 工具类
 */
public class DruidUtil {


    // 定义成员变量

    private static DataSource ds;

    static {
        try {
            // 加载配置文件
            Properties properties = new Properties();
            properties.load(DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties"));
            // 获取DataSource
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @return 数据库连接池对象
     */
    public static DataSource getDataSource() {
        return ds;
    }

    /**
     * 获取连接
     * @return 返回连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    /**
     * 关闭连接
     * @param rs
     * @param pstmt
     * @param conn
     */
    public static void close(ResultSet rs, PreparedStatement pstmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                rs = null;
            }
        }
        if (pstmt != null) {
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                pstmt = null;
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                conn = null;
            }
        }
    }
}

4.2.2 多表的查询实例

1.员工表

CREATE TABLE `emp` (
  `id` int(11) NOT NULL,
  `ename` varchar(50) DEFAULT NULL,
  `job_id` int(11) DEFAULT NULL,
  `mgr` int(11) DEFAULT NULL,
  `joindate` date DEFAULT NULL,
  `salary` decimal(7,2) DEFAULT NULL,
  `bonus` decimal(7,2) DEFAULT NULL,
  `dept_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `emp_jobid_ref_job_id_fk` (`job_id`),
  KEY `emp_deptid_ref_dept_id_fk` (`dept_id`),
  CONSTRAINT `emp_deptid_ref_dept_id_fk` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`id`),
  CONSTRAINT `emp_jobid_ref_job_id_fk` FOREIGN KEY (`job_id`) REFERENCES `job` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2.职位表

CREATE TABLE `job` (
  `id` int(11) NOT NULL,
  `jname` varchar(20) DEFAULT NULL,
  `description` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

3.部门表

CREATE TABLE `dept` (
  `id` int(11) NOT NULL,
  `dname` varchar(50) DEFAULT NULL,
  `loc` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

4.职位等级表

CREATE TABLE `salarygrade` (
  `grade` int(11) NOT NULL,
  `losalary` int(11) DEFAULT NULL,
  `hisalary` int(11) DEFAULT NULL,
  PRIMARY KEY (`grade`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

5.插入的数据

-- 添加4个部门
INSERT INTO dept(id,dname,loc) VALUES 
(10,'教研部','北京'),
(20,'学工部','上海'),
(30,'销售部','广州'),
(40,'财务部','深圳');

-- 添加4个职务
INSERT INTO job (id, jname, description) VALUES
(1, '董事长', '管理整个公司,接单'),
(2, '经理', '管理部门员工'),
(3, '销售员', '向客人推销产品'),
(4, '文员', '使用办公软件');
-- 添加员工
INSERT INTO emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id) VALUES 
(1001,'孙悟空',4,1004,'2000-12-17','8000.00',NULL,20),
(1002,'卢俊义',3,1006,'2001-02-20','16000.00','3000.00',30),
(1003,'林冲',3,1006,'2001-02-22','12500.00','5000.00',30),
(1004,'唐僧',2,1009,'2001-04-02','29750.00',NULL,20),
(1005,'李逵',4,1006,'2001-09-28','12500.00','14000.00',30),
(1006,'宋江',2,1009,'2001-05-01','28500.00',NULL,30),
(1007,'刘备',2,1009,'2001-09-01','24500.00',NULL,10),
(1008,'猪八戒',4,1004,'2007-04-19','30000.00',NULL,20),
(1009,'罗贯中',1,NULL,'2001-11-17','50000.00',NULL,10),
(1010,'吴用',3,1006,'2001-09-08','15000.00','0.00',30),
(1011,'沙僧',4,1004,'2007-05-23','11000.00',NULL,20),
(1012,'李逵',4,1006,'2001-12-03','9500.00',NULL,30),
(1013,'小白龙',4,1004,'2001-12-03','30000.00',NULL,20),
(1014,'关羽',4,1007,'2002-01-23','13000.00',NULL,10);

-- 添加5个工资等级
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES 
(1,7000,12000),
(2,12010,14000),
(3,14010,20000),
(4,20010,30000),
(5,30010,99990);

6.查询需求和SQL示例

import com.itheima.jdbctemplate.domain.Emp;
import com.itheima.jdbctemplate.utils.DruidUtil;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;
import java.util.Map;

/**
 * @Author: kangna
 * @Date: 2019/8/16 19:52
 * @Version:
 * @Description: 数据库中创建  部门表 和  员工表
 */
public class DeptAndEmpSqlOpe {


    // 创建模板对象
    private static JdbcTemplate jdbcTemplate = new JdbcTemplate(DruidUtil.getDataSource());


    @Test
    public void test_03() {

        updateEmp(1001,10000);
    }

    @Test
    public void test_04() {

        queryAllEmp();
    }

    @Test
    public void test_05() {
        queryEmp();
    }

    @Test
    public void test_06() {
        queryEmpSpecific();
    }

    @Test
    public void test_07() {
        querySalary(10000);
    }
    @Test
    public void test_08() {
        querySpeciDept();
    }


    /**
     * 3.
     * 编写方法接收一个员工编号和工资两个参数,
     * 方法内将指定编号的员工工资修改为新的工资
     * @param id
     * @param salary
     */
    public static void updateEmp(int id, double salary) {
        String sql = "UPDATE emp SET salary = ? WHERE id = ?;";
        int count = jdbcTemplate.update(sql, salary, id);
        System.out.println(count);
    }

    /**
     * 4. 查询指定职位所有员工的信息,我在这里指定文员,跟多表查询是一样的,只不过这里将结果封装成了对象
     */

    public static void queryAllEmp() {
        String sql = "SELECT emp.* FROM emp , job WHERE emp.`job_id` = job.`id` AND job.`jname` = '文员'";

        List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
        System.out.println(list.size());
        for (Emp emp1 : list) {
            System.out.println(emp1);
        }

    }

    /**
     * 5. 编写方法查询指定姓名的员工信息,返回Employee对象
     */
    public static void queryEmp() {
        String sql = "select * from emp where ename = '林冲'";
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        for (Map<String, Object> stringObjectMap : list) {
            System.out.println(stringObjectMap);
        }
    }

    /**
     *6.查询所有姓李员工的工资并输出在控制台
     */
    public static void queryEmpSpecific() {
        String sql = "select salary from emp where ename like '李%'";
        List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }

    /**
     * 7. 接收一个工资参数,方法内查询工资大于等于传入的工资的员工,
     * 返回符合条件所有员工信息List<Employee>集合。
     * @param salary
     */
    public static void querySalary(double salary) {
        String sql = "select * from emp where emp.salary > ?";
        List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class), salary);
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }
    /**
     * 8. 查询指定部门的所有员工信息,返回List<Employee>集合
     */
    public static void querySpeciDept() {
        String sql = "select * from emp,dept where emp.dept_id = dept.id and dept.dname = ?";
        List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class), "学工部");
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }
}

在这里插入图片描述
导入的jar包
在这里插入图片描述
在这里插入图片描述
---------------------------- 感谢点赞!-----------------------------------------