Day50.JDBC — 数据库连接池、DBUtils工具类、封装DOA层代码、ThreadLocal

Day50.JDBC — 数据库连接池、DBUtils工具类、封装DOA层代码、ThreadLocal,第1张

目录

一、数据库连接池

Druid 连接池的使用

二、Apache的DBUtils

实现增删改 — QueryRunner、update()

实现查询 — query、ResultSetHandler

ResultSetHandler结果集处理类

三、封装 DAO层代码 ***

通用方法再封装 — 反射封装BaseDAOImpl类

解决JDBC工具类事物问题 — ThreadLocal *

使用ThreadLocal类

总结


一、数据库连接池

为什么使用数据库连接池?

  • 1.资源重用,避免每次创建和销毁连接都带来的较大系统开销

  • 2.链接数有限,可以防止大量用户并发访问数据库服务器。

  • 3.更快的响应速度,连接池提前进行初始化,减少了数据库连接初始化和释放过程的时间开销

参考共享单车,数量有限。

连接池的原理

  1. 连接池维护着两个容器空闲池和活动池

  2. 空闲池用于存放未使用的连接,活动池用于存放正在使用的连接,活动池中的连接使用完之后要归还回空闲池

  3. 当Java程序需要连接时,先判断空闲池中是否有连接,如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用

  4. Java程序需要连接时,如果空闲池中没有连接了,则先判断活动池的连接数是否已经达到了最大连接数,如果未达到最大连接数,则会新创建一个连接放置到活动池,供Java程序使用

  5. 如果空闲池中没有连接了,活动池中的连接也已经达到了最大连接数,则不能新创建连接了,那么此时会判断是否等待超时,如果没有等待超时则需要等待活动池中的连接归还回空闲池

  6. 如果等待超时了,则可以采取多种处理方式,例如:直接抛出超时异常,或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用

连接池的实现 —— DataSource接口

JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),所有的Java数据库连接池都需要实现该接口。 该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现

常见的数据库连接池

  • DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  • HikariCP 俗称光连接池,是目前速度最快的连接池
  • Druid(德鲁伊) 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池
Druid 连接池的使用

导入druid-1.1.10.jar

创建druid连接池的配置文件druid.properties文件,需要放置到项目src下这是为了getResourceAsStream()方法能够方便读取 (暂未深究)。

druid.properties所需参数:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名
username=root 用户名
password=123456 密码
initialSize=5 初始的连接数量
maxActive=10 最大连接数
maxWait=1000 等待时间

public class JDBCUtils {
    //连接池        单例模式??
    static DataSource dataSource;
    //静态代码块
    static {
        //属性集对象
        Properties pro = new Properties();
        try {
            //将配置文件信息 读取到属性集内  (将配置文件信息转为 字节流)
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
            //通过DruidDataSourceFactory创建连接池
            dataSource = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //通过连接池获取链接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    //测试连接池
    public static void main(String[] args) throws SQLException {
        for (int i = 0; i < 11; i++) {
            //获取连接池链接
            Connection connection = getConnection();

            System.out.println("connection=" + connection);
            //将链接还到池子
            connection.close();
        }
    }
}

Class.getClassLoader.getResourceAsStream(String path)

:默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。

附:Druid连接池的配置参数列表

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:使用ConfigFilter · alibaba/druid Wiki · GitHub
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
二、Apache的DBUtils

其中QueryRunner类封装了SQL的执行,是线程安全的

(1)可以实现增、删、改、查、

(2)考虑了事务处理需要共用Connection。

(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库 *** 作,能够大大减少编码量。

DBUtils相关API:

  1. QueryRunner() ,创建QueryRunner对象,用于执行SQL语句

  2. update(Connection conn, String sql, Object... params),执行增删改

  3. query(String sql, ResultSetHandler rsh, Object... params) ,执行查询

  4. BeanListHandler<>(结果集合的泛型)      对象集合处理器

  5. ResultSetHandler  结果集处理器,query的返回值

实现增删改 — QueryRunner、update()
    @Test//DBUtils 完成添加数据
    public void test01() throws Exception {
        //1.通过连接池工具类(刚才写的)获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建 QueryRunner 对象
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "INSERT INTO temp(name,age,gender) VALUES(?,?,?)";

        //4.传入链接、sql、参数,执行链接
        int i = queryRunner.update(connection, sql, "古力娜扎", 22, "女");
        System.out.println(i+"行受到了影响。");

        //省略了命令发送器,填装等步骤 setObject、executeUpdate...
        //5.关闭资源
        connection.close();
    }

    @Test//DBUtils 完成删除数据
    public void test02() throws Exception {
        //获取数据库链接 -- (类似socket)
        Connection connection = JDBCUtils.getConnection();
        //事物: 修改自动提交,不想删除
        connection.setAutoCommit(false);

        //创建queryRunner对象
        QueryRunner queryRunner = new QueryRunner();

        //准备Sql
        String sql = "DELETE FROM temp WHERE id = ?";

        //传入数据,执行链接
        int i = queryRunner.update(connection, sql, 1);
        System.out.println(i+"行受到了影响。");

        //回滚,数据不想删
        connection.rollback();
        //关闭资源
        connection.close();
    }
    @Test//DBUtils 完成修改数据
    public void test03() throws Exception {
        //获取链接
        Connection connection = JDBCUtils.getConnection();

        QueryRunner queryRunner = new QueryRunner();

        String sql = "UPDATE temp SET salary = ? where name = ?";
        //传入数据,执行链接
        int i = queryRunner.update(connection, sql, 5000, "迪丽热巴");
        System.out.println(i+"行受到了影响。");
        
        //关流
        connection.close();
    }
实现查询 — query、ResultSetHandler

queryRunner        查询程序
query()                  查询
BeanListHandler<>(结果集合的泛型)      对象集合处理器
ResultSetHandler()                                  结果集处理器

需要提前将查询的表封装为JavaBean对象

    @Test//DBUtils 完成数据查找
    public void test01() throws Exception {
        //1.通过连接池工具类(刚才写的)获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建 QueryRunner 对象
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "SELECT * FROM temp";

        //4.查询数据,并将数据封装到结果集内,BeanListHandler<>(结果集合的泛型) 需要将查询的应表封装为JavaBean对象
        List list = queryRunner.query(connection, sql, new BeanListHandler<>(temp.class));

        //5.遍历循环
        list.forEach(System.out::println);
      /*temp{id=1, name='张三', age=18, gender='男', salary=180.0, balance=10000.0}
        temp{id=2, name='迪丽热巴', age=22, gender='女', salary=5000.0, balance=10000.0}
        temp{id=3, name='古力娜扎', age=22, gender='女', salary=3000.0, balance=10000.0}*/
        //5.关闭资源
        connection.close();
    }
ResultSetHandler结果集处理类

DBuils 查询时,结果返回什么数据,重点是结果集处理器

BeanListHandler  返回一个集合:所有结果

BeanHandler       返回一个对象:第一个结果

ScalarHandler     返回一个数据:结果数量

Handler类型说明
BeanHandler将结果集中第一条记录封装到一个指定的javaBean中。
BeanListHandler将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
ScalarHandler它是用于单个数据。例如select count(*) from 表。
ArrayHandler将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
ArrayListHandler将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。
ColumnListHandler将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler将结果集中每一条记录封装到Map,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值。
MapHandler将结果集中第一条记录封装到了Map集合中,key就是字段名称,value就是字段值
MapListHandler将结果集中每一条记录封装到了Map集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中。
三、封装 DAO层代码 ***

(DAO) Data Access Object 数据访问对象,是一个面向对象的数据库接口,主要实现插入 更新 查询 删除等 *** 作。

JavaEE项目的三层架构:

//领导| 规定了对Temp表的 *** 作
public interface TempDao {
    //查询所有
    List selectAll() throws Exception;
    //查询指定id
    Temp getById(int id) throws Exception;
    //更新
    void updateTemp(Temp temp) throws Exception;
    //添加
    void insertTemp(Temp temp) throws Exception;
    //删除
    void deleteById(int id) throws Exception;
}

实现类

public class TempDaoImpl implements TempDao {
    @Override//查询所有
    public List selectAll() throws Exception {
        //1.获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "SELECT * FROM temp";

        //4.执行 *** 作
        List list = queryRunner.query(connection, sql, new BeanListHandler<>(Temp.class));

        //关闭资源
        connection.close();

        return list;
    }
    @Override//查询指定id
    public Temp getById(int id) throws Exception {

        //1.获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "SELECT * FROM temp WHERE id = ?";

        //4.执行 *** 作
        Temp temp = queryRunner.query(connection, sql, new BeanHandler<>(Temp.class),id);

        //关闭资源
        connection.close();

        return temp;
    }
    @Override//更新
    public void updateTemp(Temp temp) throws Exception {
        //1.获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "UPDATE temp SET name = ?,age = ?,gender = ?,salary = ? WHERE id = ?";

        //4.执行 *** 作
        queryRunner.update(connection,sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary(),temp.getId());

        //关闭资源
        connection.close();
    }
    @Override//添加
    public void insertTemp(Temp temp) throws Exception {
        //1.获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "INSERT INTO temp(name,age,gender,salary) VALUES (?,?,?,?)";

        //4.执行 *** 作
        queryRunner.update(connection,sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary());
       //关闭资源
       connection.close();
    }
    @Override//删除
    public void deleteById(int id) throws SQLException {
        //1.获取链接
        Connection connection = JDBCUtils.getConnection();

        //2.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();

        //3.准备sql
        String sql = "DELETE FROM temp WHERE id = ?";

        //4.执行 *** 作
        queryRunner.update(connection,sql,id);

        //关闭资源
        connection.close();
    }
}

测试类

    TempDao tempDao = new TempDaoImpl();
    @Test   //测试
    public void testInster() throws Exception {
        //Integer id, String name, Integer age, String gender, Double salary, Double balance
        Temp temp = new Temp(6,"赵丽颖", 19, "女", 13000.0);

        tempDao.insertTemp(temp);

        tempDao.updateTemp(temp);

        tempDao.deleteById(3);

        List temps = tempDao.selectAll();

        Temp byId = tempDao.getById(3);

    }
通用方法再封装 — 反射封装BaseDAOImpl类

基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的 *** 作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDAOImpl

测试temp表:

通用方法封装

public class BaseDaolmpl {
    //通用的BaseDao 通用的增 删 改 查方法
    public void update(String sql,Object...params) {
        try {
            //1.通过线程池获取链接
            Connection connection = JDBCUtils.getConnection();
            //2.创建queryRunner
            QueryRunner queryRunner = new QueryRunner();

            //3.执行 *** 作
            queryRunner.update(connection,sql,params);

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

    /**
     * 通用的查询多条数据
     * @param clazz  集合的泛型类型
     * @param sql
     * @param params 参数
     * @param 
     * @return
     */
    //不确定查询哪张表,加入泛型!!!!
    public  List findAll(Class clazz, String sql, Object... params) {
        try {
            //1.获取链接
            Connection connection = JDBCUtils.getConnection();
            //2.创建queryRunner
            QueryRunner queryRunner = new QueryRunner();

            List query = queryRunner.query(connection, sql, new BeanListHandler<>(clazz), params);

            return query;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
    /**
     * 通用的返回单个对象
     * @param clazz   bean的链接类型
     * @param sql
     * @param params  参数
     * @param 
     * @return
     */
    public  T findOneBean(Class clazz, String sql, Object... params) {
        try {
            //1.获取链接
            Connection connection = JDBCUtils.getConnection();
            //2.创建queryRunner
            QueryRunner queryRunner = new QueryRunner();

            T bean = queryRunner.query(connection, sql, new BeanHandler<>(clazz), params);

            return bean;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
    /**
     * 通用的查询单个值,暂未用到
     * @param sql
     * @param params
     * @return
     */
    public Object getSingleValue(String sql,Object...params){
        try {
            //1.获取链接
            Connection connection = JDBCUtils.getConnection();
            //2.创建queryRunner
            QueryRunner queryRunner = new QueryRunner();

            Object query = queryRunner.query(connection, sql, new ScalarHandler<>(), params);

            return query;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

方法接口

public interface TemoDao_base {
    //获取所有
    List getAll();
    //获取单个
    Temp getById(int id);
    //更新
    void updateTemp(Temp temp);
    //添加
    void insertTemp(Temp temp);
}

实现类

public class TempDao_baseImpl extends BaseDaolmpl implements TemoDao_base {
    //继承BaseDao,方法已经封装好,只需要传入Sql语句和?参数即可
    @Override//获取所有
    public List getAll() {
        String sql = "SELECT * FROM temp";

        List all = findAll(Temp.class, sql);

        return all;
    }

    @Override//获取单个
    public Temp getById(int id) {
        String sql = "SELECT * FROM temp WHERE id = ?";

        Temp oneBean = findOneBean(Temp.class, sql, id);

        return oneBean;
    }

    @Override//修改数据
    public void updateTemp(Temp temp) {

        String sql ="UPDATE temp SET name = ?,age = ?,gender = ?,salary = ? WHERE id = ?";

        update(sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary(),temp.getId());

    }
    @Override//添加数据
    public void insertTemp(Temp temp) {

        String sql ="INSERT INTO temp(name,age,gender,salary) VALUES(?,?,?,?)";

        update(sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary());

    }
}

测试

    @Test
    public void test01(){
        TempDao_baseImpl tb = new TempDao_baseImpl();
        //1.测试查询所有
        List all = tb.getAll();
        all.forEach(System.out::println);

        //2.测试修改
        //获取指定id对象
        Temp byId = tb.getById(1);
        //传入,修改
        byId.setSalary(1500.0);
        tb.updateTemp(byId);

        //3.测试添加
        Temp temp1 = new Temp("炎爆龙", 100, "未知", 0.0);
        tb.insertTemp(temp1);
    }
解决JDBC工具类事物问题 — ThreadLocal *

问题:线程池可以让同一个线程内获取到不同的链接,在不同的链接内对表进行修改,破坏事务处理原则(保证事物都作为一个工作单元来执行),无法做到同时成功或同时失败。

JDBC事物 *** 作前提:所有DML *** 作 在同一个事务内(连接 connection)内。

需求:
    (1)修改did=1的部门简介为“部门1的简介”
    (2)修改did=2的部门名称  = 部门1的名称(故意制造错误,因为t_department表的dname有唯一键元素,这条修改会失败)
    要求(1)(2)同时成功或同时失败,即在一个事务中完成。

调用线程池,(1)(2)使用了不同的线程。

使用ThreadLocal类

解决事物问题,多线程程序的并发问题,保证同一个事物,同一个链接。

ThreadLocal可以保证单个线程中的数据库 *** 作使用的是同一个数据库连接,从而解决解决多线程程序的并发问题。(因为事物只在当前链接有效,如果两个链接不同无法生效)

需求:
    (1)修改did=1的部门简介为“部门1的简介”
    (2)修改did=2的部门名称  = 部门1的名称(故意制造错误,因为t_department表的dname有唯一键元素,这条修改会失败)
    要求(1)(2)同时成功或同时失败,即在一个事务中完成。

关于 

 ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap其key就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法 *** 作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

    /* Thread源码
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

修改线程池 — 代码实现

public class JDBCUtils_ThreadLocal {
    static DataSource dataSource;
    //ThreadLocal,每个线程独有,用于存储共享变量
    static ThreadLocal local = new ThreadLocal<>();

    //静态代码块
    static {
        //属性集对象
        Properties pro = new Properties();
        try {
            //将配置文件信息 读取到属性集内  (将配置文件信息转为 字节流)
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
            //通过配置文件创建了链接对象
            dataSource = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //通过连接池获取链接
    public static Connection getConnection() throws SQLException {

        Connection connection = local.get();
        //如果ThreadLocalMap内不存在,用连接池获取,存在则直接返回
        if(connection==null){
            //连接池获取
            connection = dataSource.getConnection();
            //放入ThreadLocal内
            local.set(connection);
        }
        return connection;
    }
    //关闭链接方法
    public static void closeConnectiopn() throws Exception{
        Connection connection = JDBCUtils_ThreadLocal.getConnection();
        //事务关闭,防止后面复用出现问题(因为连接使用完成后会归还,等待下次调用)
        connection.setAutoCommit(true);
        //删除线程内存储的链接
        local.remove();

        connection.close();
    }
}

 local.set()源码:获取当前线程,获取当前线程Map,不存在则创建

//Params:value – the value to be stored in the current thread's copy of this thread-local.
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
总结

1、获取数据库的链接

Connection conn = JDBCUtils,getConnection();  //数据库连接池 | 手动获取链接
conn.setAutoCommit(false);                //体现事物

2、如下的多个DML *** 作,作为一个事务出现:

*** 作1:需要使用通用的增删改查 *** 作                //通用的增删改查 *** 作如何实现?
*** 作2:需要使用通用的增删改查 *** 作                //方式1:手动使用PreparedStatement实现
*** 作3:需要使用通用的增删改查 *** 作                //方式2:使用dbutils.jar中QueryRunner类

conn.commit();

3、如果出现异常,则:

coon.rollback();

4、关闭资源

JDBCUtils.closeResource(..,...,...);

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/738495.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存