国货之光Druid连接池技术使用

国货之光Druid连接池技术使用

连接池性能消耗问题

连接可以复用

连接池分为三个阶段

  • 创建(T1)
  • 使用(T2)
  • 销毁(T3)

如果T1+T3 > T2不合理

连接池:节约了创建和销毁连接的性能消耗,提升了时间的响应,只有t2时间。只需从连接池中获取连接,然后再将用过的连接放回连接池中。

数据库连接池作用

不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃,连接的利用率太低,太浪费。

对于数据库服务器来说,压力太大了,我们数据库服务器和java程序对连接数也无法控制,很容易导致数据库服务器崩溃。

因此,我们希望能管理连接。

1)我们可以建立一个连接池,这个池可以容纳一定数量的连接对象,一开始,我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,不用新建,可以节省时间,然后用户用完后,放回去,别人可以接着用。

2)可以提高连接的使用率,当池中的现有连接都用完了,那么连接池可以向服务器申请新的连接放到池中。

3)直到池中的连接达到“最大连接数”,就不能在申请新的连接了,如果没有拿到连接的用户只能等待

市面常见连接池产品和对比

JDBC的数据库连接池使用javax.sql.DataSource接口进行规范,所有第三连接池都实现此接口,即所有连接池获取连接和回收连接方法都一样,不同的只有性能和扩展功能。

  • DBCP 速度快,但自身存在bug
  • C3P0 速度慢,但稳定性可以
  • Proxool 稳定性差,有监控连接池状态功能
  • Druid(国货之光,集DBCP、C3P0、Proxool优点于一身)
Druid连接池的使用

导入Druid工具类jar包

硬编码方式

直接使用代码设置连接池参数方式:

  1. 创建一个Druid对象
  2. 设置连接池参数(必须|非必须)
  3. 获取连接(通用方法)
  4. 回收连接(通用方法)
 @Test
  public void testHard() throws SQLException {

    DruidDataSource dataSource = new DruidDataSource();
    //必须 连接数据库驱动类的全限定符
    dataSource.setUrl("jdbc:mysql://localhost:3306/book");
    dataSource.setUsername("root");
    dataSource.setPassword("123456");
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    //非必须
    dataSource.setInitialSize(5);//初始化连接数量
    dataSource.setMaxActive(10);//最大的数量

    //获取连接
    DruidPooledConnection connection = dataSource.getConnection();

    //数据库curd

    //连接池提供的连接,close就是回收连接
    connection.close();


  }

软编码方式

编写druid.properties

druid连接池需要的配置参数,key固定命名。

通过读取外部配置文件的方法,实例化druid连接池对象

  1. 读取外部配置文件Properties
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:3306/book
  1. 使用连接池的工具类的工程模式,创建连接池
  @Test
  public void testSoft() throws Exception {
    Properties properties = new Properties();

    //src下的文件,可以使用类加载器提供的方法
    InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("main/resources/druid.properties");
    properties.load(ips);

    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    //数据库curd
    connection.close();
  }
JDBC工具类封装

​ 实现单例模式的关键点是将类的构造函数声明为私有,这样就无法从外部直接实例化该类的对象。然后,通过在类内部定义一个静态方法或属性,来创建或获取该类的唯一实例。这样,无论在何处调用该方法或属性,都可以获得同一个实例对象。

//工具类封装1.0
package com.atguigu.api.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.SQLException;
import java.util.Properties;

/**
 * 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 *
 * 建议:工具类的方法推荐写成静态的,外部调用会更加方便
 * 实现:
 *  属性 : 连接池对象(实例化一次)
 *          单例模式
 
 *          静态代码块
 * 方法:对外提供连接的方法
 *      回收外部传入连接方法
 */

public class JdbcUtils {
  private static DataSource dataSource = null;
  static {
    Properties properties = new Properties();
    InputStream ips = JdbcUtils.class.getClassLoader().getResourceAsStream("main/resources/druid.properties");
    try {
      properties.load(ips);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    try {
      dataSource = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static Connection getConnection() throws SQLException {

    return dataSource.getConnection();
  }
  public static void freeConnection(Connection connection) throws SQLException {
    connection.close();
  }
}

以上工具类每次用JdbcUtils.getConnection( )获取的连接是一个新的连接,不是同一个。如果不能保证是同一个连接对象,就无法保证事务的管理。

ThreadLocal 可以为同一个线程存储共享变量。为解决多线程程序的并发问题提供了一个新的思路,通常用来在多线程中管理共享数据连接、session等。

ThreadLocal用于保存某个线程共享变量,原因是在java中,每一个线程对象中都有一个ThreadLocalMap<ThreadLocal,Object>,Object即为该线程的共享变量,而这个map是通过ThreadLocal的set和get方法操作的。

//工具类封装2.0
package com.atguigu.api.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.SQLException;
import java.util.Properties;

/**
 * 利用线程本地变量,存储连接信息,确保一个线程的多个方法可以获取同一个connection
 * 优势:事务操作的时候,service和dao属于同一个线程,不用在传递参数了
 * 可以调用getConnection自动获取的是相同的连接
 */
public class JdbcUtilsV2 {
  private static DataSource dataSource = null;
  private static ThreadLocal<Connection> tl = new ThreadLocal<>();
  static {
    Properties properties = new Properties();
    InputStream ips = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("main/resources/druid.properties");
    try {
      properties.load(ips);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    try {
      dataSource = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static Connection getConnection() throws SQLException {
    Connection connection = tl.get();
    if (connection == null){
      connection = dataSource.getConnection();
      tl.set(connection);
    }
    return connection;
  }
  public static void freeConnection() throws SQLException {
    Connection connection = tl.get();
    if (connection != null){
      tl.remove();//清空线程本地变量数据
      connection.setAutoCommit(true);//事务状态回归,关闭事务
      connection.close();
    }
  }
}

JDBC的步骤

  1. 注册驱动
  2. 获取连接
  3. 编写SQL语句
  4. 创建statement对象
  5. 占位符赋值
  6. 发送SQL语句
  7. 结果集解析
  8. 回收资源

工具类封装做的事情只满足注册驱动、获取连接和回收资源

高级应用层封装BaseDao

基本上每一个数据表都应该有一个对应的dao接口及其实现类,发现所有表的操作(CURD)代码重复度很高,所有可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称之为BaseDao。

在BaseDao封装方法只用封装两个,而不是四个,因为查询是一类,增删改是一类,它们都是返回int型,传入SQL语句。

package com.atguigu.api.utils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/** TODO:封装dao数据库重复代码
 * 封装两个方法:一个简化非DQL,一个简化DQL
*/
public abstract class BaseDao {
  //可变参数需存在形参列表的最后一位
  /**
   * @param sql 带占位符的SQL语句
   * @param params 占位符的值
   * @return 返回执行影响的行数
   */
  public int executeUpdate(String sql ,Object ... params) throws SQLException {
    Connection connection = JdbcUtilsV2.getConnection();
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //占位符赋值,可变参数可以当做数组使用,占位符赋值是从1开始,但是数组的值是从0开始的
    for (int i = 1; i <= params.length; i++) {
      //占位符的值必须等于sql语句?的位置
      preparedStatement.setObject(i, params[i-1]);
    }
    int row = preparedStatement.executeUpdate();
    preparedStatement.close();
    //connection.close();不合适,如果是事务操作不关闭
    //connection.setAutoCommit(false); 开启事务,业务层处理会回收,不要管连接
    if(connection.getAutoCommit()) {
      //没有开启事务,正常回收连接
      JdbcUtilsV2.freeConnection();
    }    return row;
  }

  /**
   * 非DQL语句封装方法 返回值固定为int
   *
   * DQL语句封装方法 返回类型 List<T>某一实体类的集合
   *               并不是List<Map> map中的key和value虽然可以自定义,
   *               但是map没有数据校验机制(即年龄可以为负数),也不支持反射操作
   *          数据库数据 --> java实体类
   *          表中的一行数据对应java类的一个对象,表对应一个类,多行数据对应List<java实体类> list;
   *          实体类是有校验机制的,用get和set方法做参数校验,实体类也支持反射操作
   *          @param Class<T> clazz 有两点好处:1.确定泛型 例:传入User.class T = User 2.要使用反射机制给属性赋值
   */

  /**
   * 将查询结果封装到一个实体类集合
   * @param clazz 反射实例化对象,同时给泛型赋值,T代表着Class泛型对应的值
   * @param sql 查询语句,要求列名或者别名等于实体类的属性名
   * @param params 占位符的值要和?位置对象对应
   * @return List<T>返回的集合是T对应的类型,返回查询的实体类集合
   * @param <T> <T> 声明一个泛型是T,不确定类型
   */
  public <T> List<T> executeQuery(Class<T> clazz, String sql,Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    Connection connection = JdbcUtilsV2.getConnection();
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    if(params != null && params.length != 0){
      for (int i = 1; i <= params.length ; i++) {
        preparedStatement.setObject(i, params[i-1]);
      }
    }
    //发送SQL语句
    ResultSet resultSet = preparedStatement.executeQuery();
    //结果集解析
    List<T> list = new ArrayList<T>();

    //metaData装的当前列的信息对象
    ResultSetMetaData metaData = resultSet.getMetaData();
    //可以水平遍历
    int columnCount = metaData.getColumnCount();

    while(resultSet.next()){
      //一行数据对应一个T类型对象
      T t = clazz.newInstance();//调用类的无参构造函数实例化对象
      // 自动遍历列
      for (int i = 1; i <= columnCount; i++) {
        //对象属性值
        Object value = resultSet.getObject(i);
        //获取指定列下角标的列的名称
        String propertyName = metaData.getColumnLabel(i);
        //反射,给对象的属性值赋值
        Field field = clazz.getDeclaredField(propertyName);
        field.setAccessible(true);//属性可以设置,打破private的修饰限制
        //参数1要赋值的对象,如果属性是静态,第一个参数可以为null
        //参数2 具体的属性值
        field.set(t,value);
      }
      list.add(t);
    }
    resultSet.close();
    preparedStatement.close();
    if(connection.getAutoCommit()){
      //没有事务,可以回收连接
      JdbcUtilsV2.freeConnection();
    }
    return list;
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值