Druid-ThreadLocal-QueryRunner

数据库连接池

一般情况下我们链接数据库是需要则创建,利用完则销毁,也要遵循JDBC的四步骤:注册驱动,建立通道,配置小车,销毁。

当然,这种一条一条地创建链接是十分耗费资源的,而且效率也不高。数据库连接池的概念应运而生。

我们预先设置好一部分链接,放在一个池中,并且将这些连接标记为空闲状态。如果要使用连接就从池中获取一个连接使用,用完之后再次放回到池中。

  • 连接池自己应该有自动初始化的功能,自动增长,自动缩减
  • 自动增长:当池中的空闲连接使用完后,自动创建新的空闲连接到连接池中
  • 自动缩减:当池中的空闲连接剩余过多时,自动关闭部分链接

Druid

数据库连接池有很多种:C3P0,DBCP,Druid。其中Druid是目前最为流行的连接池。

Druid连接池是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0,DBCP,PROXOOL等DB池的优点,同时加入了日志监控,能很好地监控DB池连接状况和SQL执行情况。下载Duird的jar包:druid-1.1.5.jar

配置文件+IO流创建连接池

1
2
3
4
5
6
7
8
9
10
11
//database.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/companydb

username=root
password=*********

initialSize = 10
maxActive = 20
minIdle = 5
maxWait = 3000
  • 使用IO流来加载该配置文件,其中注意Dbutils.class是当前类名。这个流是写在类Dbutils中的。
1
2
3
Properties properties = new Properties();
InputStream inputStream = Dbutils.class.getClassLoader().getResourceAsStream("/database.properties");
properties.load(inputStream);
  • 将已经加载了IO流的properties导入连接池
1
2
private static DruidDataSource ds;
ds = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
  • 至此,一个Druid连接池创建好了。其中需要特别注意的是,使用流创建连接池时配置文件中的key必须要和官方配置参数完全相同。
官方配置参数 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候
可以通过名字来区分开来。如果没有配置,将会生成一个名字,
格式是:”DataSource-“ + System.identityHashCode(this)
url 连接数据库的url,不同数据库不一样。例如MySQL数据库:
url=jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8
&useSSL=false&serverTimezone=Asia/Shanghai
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中可以使用ConfigFilter
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。
  • 处理完异常后,创建连接池的完整代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class DbUtils {
    private static DruidDataSource ds;
    static{
    Properties properties = new Properties();
    InputStream inputStream = DbUtils.class.getResourceAsStream("/database.properties");
    try {
    properties.load(inputStream);
    ds = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
    } catch (IOException e) {
    e.printStackTrace();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    ThreadLocal<>存储链接

ThreadLocal用法

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

ThreadLocal存储Connection

1
private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();

创建ThreadLocal<>时将其声明为private static final,可以有效防止ThreadLocal的弱引用问题。至此,每一个线程将会有一个属于自己的链接Connection,如果没有的话就去连接池中取一个然后绑定到当前线程中。线程使用完链接后就remove()解除绑定,同时连接池中close()关闭该链接。

以getConnection()为例

1
2
3
4
5
6
7
8
9
10
11
12
public static Connection getConection(){
Connection connection = THREAD_LOCAL.get(); //试图获取与当前线程绑定的Connection
try {
if(connection == null){ //说明当前线程还没有绑定Connection,去连接池获取
connection = ds.getConnection(); //连接池创建了一个Connection
THREAD_LOCAL.set(connection); //将该Connection与当前线程绑定
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection; //当前线程绑定好了一个Connection,开始准备制作小车PreparedStatement。
}
1
Connection connection = Dbutils.getConnection();

相比于传统方法获取Connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static Connection connection = null;
public static PreparedStatement preparedStatement =null;
//注册驱动
ResourceBundle resourceBundle = ResourceBundle.getBundle("database");
String driver = resourceBundle.getString("driver");
String url = resourceBundle.getString("url");
String username = resourceBundle.getString("username");
String password = resourceBundle.getString("password");
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//建立通道
try {
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
}

可以很明显的看出,连接池技术很好的代替了以上一整块代码,免去了注册驱动以及一条条建立通道的麻烦,最重要的是,可复用性和隐蔽性碾压了传统方法,所有的原始数据如username,password等都不会在代码中展现出来,全部都隐蔽在配置文件中。

ThreadLocal<>的优越性体现在与连接池的高度契合上,它使得一次作业绑定唯一一个Connection,否则一次作业中可以通过ds.getConnection()一直获取Connection,这显得十分无意义且浪费连接池资源。

QueryRunner+ResultSetHandler

使用前需添加jar包:commons-dbutils.jar+mysql-connector-java.jar

QreryRunner类(org.apache.commons.dbutils.QueryRunner) 是Dbutils的核心类之一,它显著的简化了SQL查询,并与ResultSetHandler协同工作将使编码量大为减少。它包含以下几个方法:

  1.   **query(Connection conn, String sql, ResultSetHandler rsh,Object[] params)**:执行选择查询,在查询中,对象阵列的值被用来作为查询的置换参数。
    
  2.   **update(Connection conn, String sql, Object[] params)**:被用来执行插入、更新或删除(DML)操作。
    

其中ResultSetHandler接口(org.apache.commons.dbutils.ResultSethandler)执行处理一个结果集对象,将数据转变并处理为任何一种形式,供其他应用使用。实现类如下:

  • ArrayHandler:把结果集中的第一行数据转成对象数组。
  • ArrayListHandler:把结果集中的每一行数据都转成一个对象数组,再存放到List中。
  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。//重点
  • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。//重点
  • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
1
2
private QueryRunner queryRunner = new QueryRunner();
Admin admin = queryRunner.query(DbUtils.getConection(),"select * FROM admin WHERE username=?;",new BeanHandler<Admin>(Admin.class),"theo");

Connection conn:线程绑定的通道,用来传输SQL语句,返回结果

String sql:如果是查询语句就使用.query(),如果是DML语句就使用.update()

ReslutSetHandler rsh:因为JDBC中查询语句需要返回ResultSet,而ResultSet本质上是表格。虽然我们可以人为遍历表格然后一个个装入Admin生成一个对象实例,ReslutSetHandler rsh代替我们做了这件事。

Object[] params:可变数组 ,预加载的SQL语句中有多少?这里就对应了多少参数。相当于preparedStatement.setString(1,username);

总之,QueryRunner技术代替了准备小车和执行语句[ps.executeUpdate()、ps.executeQuery()]的作用,进一步的,搭配ResultSetHandler还可以将返回结果进行处理,用过都说好!

  • QueryRunner.query()

这条语句牵涉到了数据库,Admin类。如果要用到BeanHandler就必须要考虑数据库key值与Admin类属性的匹配。比如说数据库中表头有username VARCHAR(20),Admin类的属性就得是String username;而不是String UserName;。如果返回的ResultSet是多行多列,也就是有多个实体信息,就需要用到BeanListHandler了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!