Chapter 2. 数据库连接池

2.1 引言

数据库连接池,负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

2.1.1 数据库连接池的原理

连接池基本思想是,在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并发将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。

2.1.2 数据库连接池影响因素

数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数制约。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。

连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

2.2 数据库连接技术

2.2.1 C3P0

步骤

  1. 不仅要导入数据库驱动 jar 包,还要导入 两个 jar 包(见下图,可去该网站查找下载)。
  2. 定义配置文件(文件名称为 c3p0.properties 或者 c3p0-config.xml ),路径一般放在项目的 src 目录下。
  3. 创建核心对象——数据库连接池DataSource对象——new一个 ComboPooledDataSource
  4. 调用该对象的方法以获取连接:getConnection

使用举例

2.2.2 Druid (使用较多)

步骤

  1. 不仅要导入数据库驱动 jar 包,导入数据库连接池的 jar 包(见下图)
  2. 定义配置文件(任意名称、任意目录,前提是以properties 为文件名后缀 )
  3. 通过类加载器获取以配置文件为路径的文件流,然后调用 properties 的方法来加载配置文件。
  4. 获取数据库连接池 DataSource 对象—— 调用工厂类 DruidDataSourceFactory 的方法 createDataSource
  5. 调用该对象的方法以获取连接:getConnection

使用举例

2.2.3 定义工具类 JDBCUtils

定义一个工具类 JDBCUtils,首先要通过静态代码块加载配置文件,初始化连接池对象。提供的方法应有:

  • 通过数据库连接池对象获取连接对象
  • 释放 PreparedStatement 对象、ResultSet 对象,归还 连接对象。

使用举例

工具类 JDBCUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package top.JoyDee.JDBCUtils;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtils {

private static DataSource ds;
static {
try{
//1. 加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2. 获取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
} //获取连接

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

public static void close(Statement stmt, Connection conn){
close(null, stmt, conn);
}

public static void close(ResultSet res, Statement stmt, Connection conn){
if(res != null){
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close(); //关闭资源
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close(); //归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

演示测试类:DruidByUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package top.JoyDee.LearningDatasource.Druid;

import top.JoyDee.JDBCUtils.JDBCUtils;

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

public class DruidByUtils {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = JDBCUtils.getConnection();
String sql = "INSERT INTO userlist values(null, ?, ?);";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "Nami");
pstmt.setString(2, "abcdefg");
int res = pstmt.executeUpdate();
System.out.println(res);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(pstmt, conn);
}
}
}

运行结果:

2.3 Spring JDBC

Spring 框架对 JDBC 进行了简单封装,提供了一个JDBCTemplate 对象简化了JDBC的开发。

❗ 自定义的类的成员变量,必须与 SQL 中的列名称相同,如此,才能够使用 queryForObject 方法进行封装。

2.3.1 使用步骤

  1. 导入几个 jar 包(见下图)

  2. 创建 JdbcTemplate 对象,依赖于 数据源对象 DataSource

  3. 调用 JdbcTemplate的相关方法来完成数据库的相关操作

    下面方法中参数 Object ... args 为占位符 ? 依次(即无需说明第几个)要填的参数

    • update(String sql, Object ... args):执行 DML 语句完成数据库表中数据的增、删、改

    • queryForMap(String sql, Object ... args):查询结果并将 结果集 封装为 Map 集合(列名作为 key,值作为 value

      注意,该方法的查询结果集长度只能为 1

    • query(String sql, RowMapper<T> rowMapper):查询结果,将结果封装为 JavaBean 对象(一般我们使用 BeanPropertyRowMapper 实现类,来帮助我们将数据自动封装到我们自定义的 JavaBean 类对象)

      1
      query(sql, new BeanPropertyRowMapper<自定义类>(自定义类.class))

      注意,若返回结果为单个元素,则直接返回对象;若返回结果为多个元素,则会返回以该对象为类型的 List ,举例如下:

      1
      2
      List<User> users = template.query(sql, new BeanPropertyRowMapper<User>(User.class)); //其中sql="SELECT * FROM USER";
      User user = template,queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username, userpassword); //其中 sql = "SELECT * FROM userlist WHERE username = ? AND password = ?";
    • queryForObject(String sql, Object[] args, Class<T> elementType):查询结果,并将结果封装为对象,常用于 聚合函数 的查询

      1
      2
      3
      4
      public int findTotalCount(){
      String sql = "SELECT COUNT(*) FROM userlist";
      return template.queryForObject(sql, Integer.class);
      } //Integer 自动拆箱为 int

简单使用举例(使用工具类 JdbcUtils )

2.3.3 执行DML、DQL

使用 JdbcUtils + Junit + Spring JDBC

提示:当使用 @Test 出现红线时,应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package top.JoyDee.LearningDatasource.JdbcTemplate;

import jdk.jfr.StackTrace;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import top.JoyDee.Domain.User;
import top.JoyDee.JDBCUtils.JDBCUtils;
import java.util.List;
import java.util.Map;

public class JdbcTemplateDemo2 {
//获取JDBCTemplate对象
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/* DML语句使用举例 */
@Test
public void test1(){
String sql = "UPDATE userlist SET username = 'Sanji' where id = 2;";
int res = template.update(sql);
Assert.assertEquals(1, res);
}

@Test
public void test2(){
String sql = "INSERT INTO userlist(id, username, password) VALUES(?, ?, ?);";
int res = template.update(sql, null, "Zoro", "abcdefg");
Assert.assertEquals(1, res);
}
/* DQL语句使用举例 */
@Test
public void test3(){ //查询id为特定值的记录,并将其封装为Map集合(注意,查询结果只能为1行!!!)
String sql = "SELECT * FROM userlist WHERE id = ?;";
Map<String, Object> mymap = template.queryForMap(sql, 1, 3);
//将列名作为key,值作为value
System.out.println(mymap);
}

@Test
public void test4(){
String sql = "SELECT * FROM userlist;";
List<Map<String, Object>> mapList = template.queryForList(sql);
for(Map<String, Object> stringObjectMap : mapList){
System.out.println(stringObjectMap);
}
}
//查询所有记录并将其封装为 自定义对象 的List集合
@Test
public void test5(){
String sql = "SELECT * FROM userlist;";
List<User> userList = template.query(sql, new BeanPropertyRowMapper<User>(User.class));
for(User e : userList){
System.out.println(e);
}
}
//查询总查询数
@Test
public void test6(){
String sql = "SELECT COUNT(id) FROM userlist";
Long total = template.queryForObject(sql, Long.class); //传入需要返回类型的字节码文件
System.out.println(total);
}

}

建议使用两根手指将页面拉大~

test1() 运行结果test2() 运行结果
test3() 运行结果test4() 运行结果
test6() 运行结果

2.3.4 组合查询的技巧

由于用户查询时,有可能不需要条件查询,有可能需要条件 1 查询,也有可能需要条件 2 查询 …

为了更好地维护,可以这样写:

  1. 定义并初始化 sql 字符串及相应的 StringBuilder(因为可能存在大量的拼接操作,详见 JavaSE 笔记)。

    1
    2
    String sql = "SELECT COUNT(*) FROM user WHERE 1 = 1 "; //这里的 "WHERE 1 = 1" 用得十分妙
    StringBuilder strB = new StringBuilder(sql);
  2. 通过 HTTP 请求中携带的信息,获取条件查询的参数:

    1
    2
    3
    Map<String, String[]> conditions = request.getParameterMap();
    Set<String> keySet = conditions.keySet();
    List<Object> params = new ArrayList<Object>();
  3. 遍历 conditions,查看每个元素的 key 是否存在 value,若有,则拼接到原来的 sql 字符串。格式统一,方便维护——sql.append("AND ? LIKE ?");

    1
    2
    3
    4
    5
    6
    7
    8
    for(String key : keySet){
    //根据key获取value
    String[] value = condition.get(key)[0];
    if(value != null && !"".equals(value)){
    sb.append(" AND " + key + "LIKE ? ");
    params.add("%" + value + "%");
    }
    }