Java笔记二:数据库连接池

2 篇文章 0 订阅

 

数据库连接池

由于数据库的连接对象创建工作,比较消耗性能。

所以一开始先在内存中开辟一块空间(集合),先往池子里面放置多个连接对象。后面需要连接数据库的时候,直接从池子中获取,不需要自行创建连接对象。使用完毕后,归还连接对象,确保连接对象能循环利用。

所建的MyDateSource类要继承DataSource接口(Sun公司针对数据库连接的定义的一套规范),并实现其方法。目前只实现其中连接池对外公布的获取连接的方法getConnction()

自定义数据库连接池对象

具体代码示例如下:

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.List;
import java.util.logging.Logger;

/*
*这是一个数据库连接池
* 一开始先往池子里面放10个连接(通过构造方法或者静态代码块方法实现)
*
* 1.开始创建10个连接
*
* 2.来的程序通过getConnection获取连接
*
* 3.用完之后,使用addBack归还连接
*
* 4.扩容
* */

public class MyDateSource implements DataSource {

    List<Connection> list = new ArrayList<Connection>();//通过集合体现连接池。
    public MyDateSource() throws SQLException {
        for (int i =0;i<9;i++){
            Connection conn = JDBCutil.getConn();
            list.add(conn);
        }
    }
    //该连接池对外公布的获取的连接方法
    @Override
    public Connection getConnection() throws SQLException {
        //获取连接的时候,先检测是否还有空闲的连接
        if (list.size()==0){//如果没有,扩容,重新创建5个新连接添加,分配
            for (int i =0;i<5;i++){
                Connection conn = JDBCutil.getConn();
                list.add(conn);
            }
        }
        //remove(0) ----> 移除第一个,移除集合中的第一个
        Connection conn = list.remove(0);//借用一个连接就移除一个连接,返回值是所移除的对象
        return conn;
    }

    //归还连接方法。
    public void addBack(Connection conn){
        list.add(conn);
    }
}

不过这样基础的实现,会带来一些问题:比如

  1. 需要额外记住 addBack()方法
  2. 单例问题,如果new 多个 MydataSource对象,会导致出现对个连接池对象。
  3. 无法面向接口编程,因为Datasource接口中没有addBack()方法,编译时候会报错。(DataSource d = new MydataSource; //编译报错)

解决方法:(以addBack为切入点)

修改接口中的close方法,原来的Connection对象的close方法是真的关闭连接。而我们可以修改这个close方法,使其调用的时候,不再是关闭连接,而是归还连接对象。

使用装饰者模式的方法(面向接口的思想),也就是把原有的Connection类包装起来。即是,我们自己写一个类,继承Connection接口,然后将实现的Connection类作为构造函数参数传入类中,而后需要用到哪些Connection接口的方法,就调用传进来的Connection参数去调用其本身的方法,对于想改造的类,就先调用的自己的方法,再调用其参数本身的方法。通过这样的方式进行对源码方法的扩展。

如以下所示:需要改造的方法是close,用到的方法是prepareStatement

public class ConnectionWrap implements Connection {//包装类,包装connection实例
    List<Connection> list;
    Connection connection = null;
    //构造方法,传入一个connection实例作为调用。
    public ConnectionWrap(Connection connection, List<Connection> list){
        super();
        this.connection = connection;
        this.list = list;
    }

    @Override
    public void close() throws SQLException {
        System.out.println("有人来归还对象了,归还之前是:" + list.size());
        list.add(connection);
        System.out.println("有人来归还对象了,归还之后是:" + list.size());
        //connection.close();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return connection.prepareStatement(sql);
    }

    ....

}
public class MyDataSource implements DataSource {//继承了DataSource接口的连接池类

    List<Connection> list = new ArrayList<Connection>();//通过集合体现连接池。
    public MyDataSource() throws SQLException {
        for (int i =0;i<9;i++){
            Connection conn = JDBCutil.getConn();
            list.add(conn);
        }
    }
    //该连接池对外公布的获取的连接方法
    @Override
    public Connection getConnection() throws SQLException {
        //获取连接的时候,先检测是否还有空闲的连接
        if (list.size()==0){//如果没有,扩容,重新创建5个新连接添加,分配
            for (int i =0;i<5;i++){
                Connection conn = JDBCutil.getConn();
                list.add(conn);
            }
        }
        //remove(0) ----> 移除第一个,移除集合中的第一个元素
        Connection conn = list.remove(0);//借用一个连接就移除一个连接,返回值是所移除的对象

        //在把这个对象抛出去的时候,对这个对象进行包装
        Connection connection = new ConnectionWrap(conn,list);//经过包装类包装的connection类

        return connection;
    }
}
@Test
    public void PoolTest() throws SQLException {//测试运行类
        Connection conn = null;
        PreparedStatement ps = null;
        MyDataSource dataSource = new MyDataSource();
        try {
             conn = dataSource.getConnection();

            String sql = "insert into bank values (null ,'kk','1000')";
            ps = conn.prepareStatement(sql);
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            /*ps.close();
            dataSource.addBack(conn);//归还连接*/
            //传入的是经过装饰过的connection对象,使用的是我们自己写的close方法。
            JDBCutil.release(ps,conn);
        }
}

开源数据库连接池对象一:DBCP

需要引入三个jar包,分别是:

commons-dbcp2-2.5.0,commons-pool2-2.6.0,commons-logging-1.2

而其中如果不导入 commons-logging-1.2 包可能会引起 java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory 错误,找不到相关类

直接代码写的方式:

public void testDBCP() throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.构建数据源对象
            BasicDataSource dataSource = new BasicDataSource();

            //连接的是什么类型的数据库,访问的是什么类型的数据库,用户名,密码
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");

            conn = dataSource.getConnection();
            String sql = "insert into bank values (null,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"kk6");
            ps.setDouble(2,1100);
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCutil.release(ps,conn);
        }
    }

配置文件方式:dbcpconfig.properties 文件

#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username=root
password=root

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

使用方式:

import JDBCutil.JDBCutil;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.junit.Test;

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


public class DBCPDemo02 {
    @Test
    public void DBCPtest02() throws Exception {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            BasicDataSourceFactory factory = new BasicDataSourceFactory();
            Properties properties = new Properties();
            //通过类加载器的方式加载properties配置文件。
            InputStream is = DBCPDemo02.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            //InputStream is = new FileInputStream("dbcpconfig.properties");用于配置文件在项目根目录下,直接读取
            properties.load(is);
            DataSource dataSource = factory.createDataSource(properties);

            conn = dataSource.getConnection();
            String sql = "insert into bank values (null,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"kk7");
            ps.setDouble(2,1500);
            ps.executeUpdate();

        } catch (Exception e) {
        e.printStackTrace();
        } finally {
        JDBCutil.release(ps,conn);
        }
    }
}

开源数据库连接池对象二:C3P0

需要引入两个jar包

c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar

不导入 mchange-commons-java-0.2.12.jar 包可能会引起 java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector 错误,找不到相关类

直接代码写的方式:

import JDBCutil.JDBCutil;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

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

public class C3P0Demo {
    @Test
    public void C3P0test(){
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            //1.创建datasource
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            //2.设置连接数据的信息
            dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
            dataSource.setUser("root");
            dataSource.setPassword("root");

            conn = dataSource.getConnection();

            String sql = "insert into bank values (null,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"kk8");
            ps.setDouble(2,1530);
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutil.release(ps,conn);
        }
    }
}

使用配置文件方式:

需要注意,配置文件的名字一定要是 c3p0-config.xml ,因为在框架源码中,会自动使用类加载器的方式加载为c3p0-config.xml的文件,所以在使用的时候,只需要new ComboPoolDataSource对象,然后使用即可,框架会自动帮我们加载配置文件。

同时在配置文件中<default-config>标签指的是:默认的数据库配置,new对象时没有指定名称则会默认使用此配置。

还能可以使用<name-config name=“oracle”> 标签来配置另一个数据库的连接,在new ComboPoolDataSource 对象时带入name参数可以使用指定配置,如:ComboPooledDataSource dataSource1 = new ComboPooledDataSource("oracle");

并且c3p0-config.xml 要跟随java文件,放置在src目录下,不然程序将读取不到配置文件。

配置文件:c3p0-config.xml

<c3p0-config>
    <!--default-config指的是,默认的数据库配置,new对象时没有指定名称则会默认使用此配置-->
    <!--可以使用 <name-config name="oracle"> 标签来配置另一个数据库的连接,在new ComboPoolDataSource对象时带入name作为参数指定使用配置-->
    <default-config>

        <property name="automaticTestTable">con_test</property>
        <!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出 SQLException,如设为0则无限期等待。单位毫秒。Default: 0 -->
        <property name="checkoutTimeout">30000</property>
        <!-- 测试空闲连接的间隔时间 default: 0 -->
        <property name="idleConnectionTestPeriod">30</property>
        <!-- 初始化时连接数,default: 3 -->
        <property name="initialPoolSize">10</property>
        <!-- 连接的最大空闲时间,default: 0 -->
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">25</property>
        <!-- 连接池中最小连接数,default: 3 -->
        <property name="minPoolSize">5</property>
        <!-- 连接池为数据源缓存的PreparedStatement的总数 -->
        <property name="maxStatements">200</property>

        <!--IDEA将“&”当成了特殊符号 不允许直接使用&,需要替换成 “&amp;”-->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&amp;serverTimezone=UTC&amp;useSSL=false</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">root</property>

    </default-config>

</c3p0-config>

代码:

package C3P0util;

import JDBCutil.JDBCutil;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class C3P0Demo02 {
    @Test
    public void C3P0test02(){
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            //配置文件方式,直接new一个对象使用即可,默认会找xml中的 default-config 分支
            //因为在源码中,自动使用了类加载器的方式加载名为:c3p0-config.xml 的文件,所以xml的名字需要固定为c3p0-config.xml
            ComboPooledDataSource dataSource = new ComboPooledDataSource();

            //带入oracle参数,使用xml配置文件中名为的 oracle 分支
            //ComboPooledDataSource dataSource1 = new ComboPooledDataSource("oracle");

            conn = dataSource.getConnection();

            String sql = "insert into bank values (null,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"kk8");
            ps.setDouble(2,1530);
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutil.release(ps,conn);
        }
    }
}

数据库操作对象:DButils

需要导入DButils的jar包,commons-dbutils-1.7.jar

在DButils框架中,只是帮我们简化了CRUD的代码,也就是操作数据库语句代码,但是对于连接的创建和获取工作,并不负责,所以,需要将DButils 和 上面的连接池对象 C3P0,DBCP 相结合使用。

DBUtils的3个核心对象。QueryRunner类(里面有query查询、update增删改和batch批处理)、ResultSetHandler接口(用于select后如何封装数据的)、DBUtils类(定义了关闭资源和事务处理的方法)。

而DButils框架将数据库操作分为了两种方法,QueryRunner.update(增,删,改)QueryRunner.query(查)

update 方法针对 增,删,改语句;而 query 方法针对查询语句。

普通代码示例如下:

实体类Bank

package BankModel;

public class Bank {
    private String name;
    private double money;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Bank{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public double getMoney() {
        return money;
    }
}

DButilTest的update(增,删,改)部分代码如下:

package DButil;

import BankModel.Bank;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;

import java.sql.ResultSet;
import java.sql.SQLException;

public class DButilsTest {
    @Test
    public void InsertTest() throws SQLException {

        //创建连接对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        //dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作,不属于他的工作范围
        QueryRunner queryRunner = new QueryRunner(dataSource);//传入一个连接对象作为参数

        //update方法针对增加,删除,修改
        //update(String sql,Object...params)

        //增加

        int i = queryRunner.update("insert into bank values(null,?,?)","kk9",1255);

        //删除
        int j = queryRunner.update("delete from bank where id=?",8);

        //修改
        int k = queryRunner.update("update bank set name=?,money=? where id=?","kk08",2988,9);
    }
}

DButilTest的query(查询)部分代码(new 匿名实现类的实例化ResultSetHandler接口方法)如下:

package DButil;

import BankModel.Bank;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;

import java.sql.ResultSet;
import java.sql.SQLException;

public class DButilsTest {
    @Test
    public void InsertTest() throws SQLException {

        //创建连接对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        //dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作,不属于他的工作范围
        QueryRunner queryRunner = new QueryRunner(dataSource);//传入一个连接对象作为参数
        
        //query方法针对查询,需要准备一个封装返回数据的类对象,如Bank类
        //query(String sql,ResultSetHandler<T>,Object...Params)
        //去执行查询,查询到的数据还是存放在Result里,然后调用接口中的handle方法,由用户手动去封装返回的数据

        Bank bank =  queryRunner.query("select * from bank where id=?", new ResultSetHandler<Bank>() {
            //通过匿名实现类的方式,实例化ResultSetHandler<T>接口,使得返回值为Bank实体类对象。
           @Override
            public Bank handle(ResultSet resultSet) throws SQLException {
              Bank bank = new Bank();
              while (resultSet.next()){
                  //自行接收数据,并且将数据封装在准备好的bank类对象中,返回这个对象。
                  String name = resultSet.getString("name");
                  double money = resultSet.getDouble("money");

                  bank.setMoney(money);
                  bank.setName(name);
              }
               return bank;
            }
        }, 9);
    }
}

上面的query查询代码由于需要自己新建一个匿名实现类,过程复杂,所以需要进行简化,简化方法就是找到ResultSetHandler 结果集的处理接口接口的具体实现类作为参数。

查询单个对象的实现类:new BeanHandler<T>(Class<T> type),例:new BeanHandler<Bank>(Bank.class)

查询多个对象的实现类(返回值是集合):new BeanListHandler<T>(Class<T> type),例:

new BeanListHandler<Bank>(Bank.class)

而上面的参数 Bank.class 的意思是:通过该类的字节码得到该类的实例(反射),也即是框架自动帮我们创建了类的实例作为参数

优化后的query(具体实现类方法)代码如下:

package DButil;

import BankModel.Bank;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;

import java.sql.ResultSet;
import java.sql.SQLException;

public class DButilsTest {
    @Test
    public void InsertTest() throws SQLException {

        //创建连接对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        //dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作,不属于他的工作范围
        QueryRunner queryRunner = new QueryRunner(dataSource);//传入一个连接对象作为参数
        
        //查询单个对象,返回值是一个对象的使用BeanHandler 类
        //参数Bank.Class 通过类的字节码得到该类的实例(反射)
        //Bank bank = Bank.class.newInstance();//创建一个类的实例
        //new BeanHandler<T>(Class<T> type)

        Bank bank = queryRunner.query("select * from bank where id=?",
                new BeanHandler<Bank>(Bank.class),7);

        System.out.println(bank.toString());

        //查询多个对象,返回值是一个集合
        //new BeanListHandler<T>(Class<T> type)
        List<Bank> list = queryRunner.query("select * from bank",
               new BeanListHandler<Bank>(Bank.class));

        for (Bank bank1:list
             ) {
            System.out.println(bank1.toString());
        }
    }
}

而ResultSetHandler(结果集的处理接口)的常用实现类有:

BeanHandler:查询到的单个数据封装成一个对象。

BeanListHandler:查询到的多个数据封装成一个List对象。

 

ArrayHandler:查询到的单个数据封装成一个数据。

ArrayListHandler:查询到的多个数据封装成一个集合,集合里面的元素是数组。

 

MapHandler:查询到的单个数据封装成一个map。

MapListHandler:查询到的多个数据封装成一个集合,集合里面的元素是map。

 

总结

数据连接池

负责与数据库的连接对象的创建和获取功能

DBCP(不使用配置文件的方法,使用配置文件的方法)

C3P0(不使用配置文件的方法,使用配置文件的方法)

自定义连接池(装饰者模式 Wrap)


DBUtils

简化了我们的CRUD,里面定义了通用的CRUD方法

queryRunner.update();

quertRunner.query();


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值