数据库连接池
★★★数据库连接池
传统获取
Connection连接的问题:传统的
JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名和密码(0.05s ~ 1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁地进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃,且多个数据库连接的频繁开启和关闭会非常耗时。
每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,
MySQL崩溃。解决传统开发中的数据库连接问题,可以采用数据库连接池技术 (
connection pool)。
内存泄漏(
Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
★★★数据库连接池原理
预先在连接池中放入一定数量的连接,当需要建立数据库连接时,只需从“连接池”中取出一个,使用完毕之后再放回去(而不是完全释放连接)。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池种类:
JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现。C3P0数据库连接池,速度相对较慢,稳定性不错 (hibernate, spring)。DBCP数据库连接池,速度相对C3P0较快,但不稳定。Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一点。BoneCP数据库连接池,速度快。Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3PO、Proxool优点于一身的数据库连接池。
注意:在使用数据库连接池时,应该先导入相应的
jar包才行。
C3P0数据库连接池
前置工作:在项目下创建一个文件夹,例如
lib,将C3P0的jar包拷贝到该目录下,通过添加库导入该jar包。-> 超详细的JDBC基础,内含 C3P0 和 Druid 等 JAR 包下载-CSDN博客public final class ComboPooledDataSource extends AbstractComboPooledDataSource implements Serializable, Referenceable
有两种使用
C3P0数据库连接池的方式:- 手动给数据源设置相关的参数。
- 使用配置文件模板来完成相关参数的设置。-> 推荐使用第二种方式。
C3P0的配置文件模板为c3p0-config.xml,需要将该配置文件放在项目的src目录下,配置文件内容如下: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
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
<property name="user">root</property>
<property name="password">java</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
<!-- 数据源名称,代表连接池 -->
<named-config name="mySource">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/java_db</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">123456</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 每次增长的连接数 -->
<property name="acquireIncrement">5</property>
<property name="maxIdleTime">30</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">100</property>
<!-- 最小连接数 -->
<property name="minPoolSize">10</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>连接
C3P0的两种方式代码如下: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
56package com.f.chapter21.jdbc.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/7 21:26
* 演示C3P0数据库连接池的使用
*/
public class C3P0_ {
//方式1:相关参数在程序中指定,如 user、password、url等
public void testC3P0_01() throws Exception {
//1.创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2.通过配置文件获取相关的连接信息
Properties properties = new Properties();
properties.load(new FileInputStream("file\\jdbc.properties"));
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
//3.手动给数据源 comboPooledDataSource 设置相关的参数
// 连接是由 comboPooledDataSource 来管理的
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
comboPooledDataSource.setInitialPoolSize(10); //初始化连接数为 10 个
comboPooledDataSource.setMaxPoolSize(50); //设置连接池的最大连接数为 50 个
//4.得到连接池中的数据库连接
Connection connection = comboPooledDataSource.getConnection();
System.out.println("第一种方式连接成功,其中一个连接为 " + connection);
//5.将连接归还给数据库连接池
connection.close();
}
//方式2:使用配置文件模板来完成
public void testC3P0_02() throws Exception {
//1.将 C3P0 提供的 c3p0-config.xml 配置文件放在项目的 src 目录下
// 在文件中指定连接数据库和连接池的相关参数
//2.创建一个数据源对象,将配置文件中对应的数据源的名称作为参数传入
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mySource");
//3.得到连接池中的数据库连接
Connection connection = comboPooledDataSource.getConnection();
System.out.println("第二种方式连接成功,其中一个连接为 " + connection);
//4.将连接归还给数据库连接池
connection.close();
}
}- 注意:在数据库连接池技术中,
close不是真的断掉连接,而是把使用的Connection对象放回连接池。
★Druid数据库连接池
前置工作:在项目下创建一个文件夹,例如
lib,将Druid的jar包拷贝到该目录下,通过添加库导入该jar包。-> 超详细的JDBC基础,内含 C3P0 和 Druid 等 JAR 包下载-CSDN博客和
C3P0类似,Druid也有配置文件模板druid.properties,配置文件内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/java_db?useSSL=false&rewriteBatchedStatements=true
username=root
password=123456
#初始化连接数
initialSize=10
#最小连接数
minIdle=5
#最大连接数
maxActive=50
#最大超时(单位为ms)
maxWait=5000然后就可以利用该配置文件进行
Druid数据库连接池的连接了: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
28package com.f.chapter21.jdbc.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/12 21:03
*/
public class Druid_ {
public void testDruid() throws Exception {
//1.创建Properties对象,读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("file\\druid.properties"));
//2.根据 Properties 对象创建 Druid 数据库连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//3.得到连接池中的数据库连接
Connection connection = dataSource.getConnection();
//4.将连接归还给数据库连接池
connection.close();
}
}
Druid工具类
在引入了数据库连接池技术后,就可以将
JDBC工具类进一步完善升级,例如升级为使用Druid数据库连接池技术的Druid工具类: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
52package com.f.chapter21.jdbc.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/12 21:25
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成ds的初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("file\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//得到Druid数据库连接池的Connection对象
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接,即将Connection对象归还给Druid数据库连接池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}Druid工具类的使用: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
65package com.f.chapter21.jdbc.utils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author fzy
* @date 2023/8/12 21:37
*/
public class JDBCUtilsByDruid_Use {
public void testDML() throws SQLException {
//1.得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//System.out.println(connection.getClass()); //class com.alibaba.druid.pool.DruidPooledConnection
//2.组织一个dml sql语句
String insertSQL = "INSERT INTO test_table VALUES (?, ?)";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
try {
preparedStatement = connection.prepareStatement(insertSQL);
//给占位符 ? 赋值
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, "北京新闻");
//执行dml sql语句
preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//4.关闭资源
JDBCUtilsByDruid.close(null, preparedStatement, connection);
}
}
public void testSelect() throws SQLException {
//1.得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.组织一个select sql语句
String selectSQL = "SELECT * FROM test_table WHERE id = ?";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
//4.创建ResultSet对象
ResultSet resultSet = null;
try {
preparedStatement = connection.prepareStatement(selectSQL);
//给占位符 ? 赋值
preparedStatement.setInt(1, 1);
//执行select sql语句,得到 ResultSet 结果集
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getInt("id") + " - " + resultSet.getString("name"));
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//5.关闭资源
JDBCUtilsByDruid.close(resultSet, preparedStatement, connection);
}
}
}可以看到,虽然
mysql和Druid都实现了Connection接口,但是- 在
mysql的jar包中,其Connection的运行类型是com.mysql.jdbc.JDBC4Connection - 在
Druid的jar包中,其Connection的运行类型是com.alibaba.druid.pool.DruidPooledConnection
所以对于
mysql和Druid的Connection对象,其实现的Connection接口的close方法是不一样的,前者是真的关闭了连接,而后者是把使用的Connection对象放回了数据库连接池。- 在
★★★Apache-DBUtils
在使用
ResultSet得到查询的数据时,会有诸多问题:ResultSet和Connection是关联的,如果关闭连接,就不能使用结果集。而如果一直保持连接,就会持续占用数据库连接池的资源,降低资源利用率。
结果集不利于数据管理 [只能用一次],即结果集用完就会关连接,所以结果集不能复用。
使用结果集中的数据也不方便,
getString、getInt等方法包含的信息量太少,相比之下,更希望是getName、getAge等明确表达得到的数据的含义的方法名。
为了解决上面使用
ResultSet的问题,可以使用下图的方式:
- 创建一个
Java类,该类的每一个对象都对应数据表中的一条数据,则该类的属性即为数据表中的字段。**这样的类称为JavaBean**。 - 然后根据
ResultSet中的数据,得到该Java类的一组对象,将这些对象添加到ArrayList<>中,就相当于将ResultSet中的数据保存了下来。 - 这时即使关闭了连接,也可以使用得到的数据,实现了数据的复用。
Apache DBUtils就实现了上述的方式。前置工作:在项目下创建一个文件夹,例如
lib,将DBUtils的jar包commons-dbutils.jar拷贝到该目录下,通过添加库导入该jar包。-> Apache Commons DBUtils - 下载安装和环境设置
★ApDBUtils查询(源码分析)
commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化JDBC编码的工作量。DBUtils类:QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理。使用
QueryRunner类实现查询。ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式:类 说明 ArrayHandler 把结果集中的第一行数据转成对象数组。 ArrayListHandler 把结果集中的每一行数据都转成一个数组,再存放到 List中。BeanHandler 将结果集中的第一行数据封装到一个对应的 JavaBean实例中。BeanListHandler 将结果集中的每一行数据都封装到一个对应的 JavaBean实例中,存放到List里。ColumnListHandler 将结果集中某一列的数据存放到 List中。KeyedHandler(name) 将结果集中的每行数据都封装到 Map里,再把这些map再存到一个map里,其key为指定的key。MapHandler 将结果集中的第一行数据封装到一个 Map里,key是列名,value就是对应的值。MapListHandler 将结果集中的每一行数据都封装到一个 Map里,然后再存放到List。上表中的
BeanListHandler所用的就类似于前面图中所说的方式。
例子:使用
DBUtils + Druid的方式,完成对account表的查询: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
65
66
67
68
69
70
71
72
73
74
75
76
77
78package com.f.chapter21.jdbc.utils;
import com.f.chapter21.jdbc.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author fzy
* @date 2023/8/13 14:10
* 使用Apache DBUtils工具类 + Druid数据库连接池的方式,完成对 `account` 表的查询
*/
public class ApDBUtils_Use_Select {
public void testDBUtils_Query() throws SQLException {
//1.使用 Druid 得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//使用 DBUtils 的类和接口
//2.使用 QueryRunner 执行查询的操作,将得到的结果存放在 List 中
QueryRunner queryRunner = new QueryRunner();
String selectSQL = "SELECT * FROM account WHERE id >= ?";
//创建了一个Account类,该Account类对应于account表
// (1)query方法就是执行一个sql语句,得到一个Resultset,并将 ResultSet 封装到 ArrayList 集合中
// (2)返回该 ArrayList 集合
// (3)connection:连接
// (4)selectSQL:执行的查询sql语句
// (5)new BeanListHandler<>(Account.class):ResultSet -> Account对象(用反射得到Account类的信息) -> ArrayList
// 底层使用反射机制获取 Account 类的属性,然后对 ResultSet 进行封装,封装为 Account对象
// (6)1:传给sql语句中占位符 ? 的参数,可以有多个值,因为是可变参数 Object... params
// (7)在 query 方法中会关闭 ResultSet 和 PreparedStatement 对象,所以后面无需关闭
/**
* query方法源码:
* public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
* if (conn == null) {
* throw new SQLException("Null connection");
* } else if (sql == null) {
* throw new SQLException("Null SQL statement");
* } else if (rsh == null) {
* throw new SQLException("Null ResultSetHandler");
* } else {
* Statement stmt = null; //定义Statement
* ResultSet resultSet = null; //接收查询得到的ResultSet
* T result = null; //最终返回的ArrayList
*
* try {
* if (params != null && params.length > 0) {
* PreparedStatement ps = this.prepareStatement(conn, sql); //得到PreparedStatement
* stmt = ps;
* this.fillStatement(ps, params); //将sql语句的 ? 填入值
* resultSet = this.wrap(ps.executeQuery()); //得到ResultSet并包装
* } else {
* stmt = conn.createStatement();
* resultSet = this.wrap(((Statement)stmt).executeQuery(sql));
* }
*
* result = rsh.handle(resultSet); //进一步包装,包装为ArrayList,在这里用到了反射,对传入的Account.class进行使用
* } catch (SQLException var12) {
* this.rethrow(var12, sql, params);
* } finally {
* this.closeQuietly(resultSet); //关闭ResultSet
* this.closeQuietly((Statement)stmt); //关闭Statement
* }
*
* return result; //返回ArrayList
* }
* }
*/
List<Account> queryResult
= queryRunner.query(connection, selectSQL, new BeanListHandler<>(Account.class), 1); //1是传给?的值
System.out.println(queryResult);
//3.关闭连接,释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
}Account类: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
54package com.f.chapter21.jdbc;
/**
* @author fzy
* @date 2023/8/13 14:42
* Account类,对应mysql中的account表
*/
public class Account { //JavaBean、POJO、Domain
private Integer id;
private String name;
private Double balance;
public Account() {
}
public Account(Integer id, String name, Double balance) {
this.id = id;
this.name = name;
this.balance = balance;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", balance=" + balance +
'}';
}
}多行多列:用
BeanListHandler,返回ArrayList。单行多列:用
BeanHandler,返回相应的类对象。单行单列:用
ScalarHandler,返回Object。
表和JavaBean的类型映射关系
| 表中的数据类型 | Java类型 |
|---|---|
| int | Integer |
| varchar | String |
| char | String |
| double | Double |
| date | java.util.Date |
ApDBUtils-DML
例子:使用
DBUtils + Druid的方式,完成对account表的增加、删除、修改: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
43package com.f.chapter21.jdbc.utils;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author fzy
* @date 2023/8/13 15:29
* 使用Apache DBUtils工具类 + Druid数据库连接池的方式,完成对 `account` 表的DML(insert、delete、update)
*/
public class ApDBUtils_Use_DML {
public void testDBUtils_DML() throws SQLException {
//1.使用 Druid 得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//使用 DBUtils 的类和接口
//2.使用 QueryRunner 执行dml的操作
// (1)执行dml操作的方法都是 queryRunner.update
// (2)返回的值是受影响的行数
QueryRunner queryRunner = new QueryRunner();
//insert
String insertSQL = "INSERT INTO account VALUES (null, ?, ?)";
int insertAffectedRows = queryRunner.update(connection, insertSQL, "王五", 1000);
System.out.println(insertAffectedRows > 0 ? "插入成功" : "插入失败");
//delete
String deleteSQL = "DELETE FROM account WHERE name = ?";
int deleteAffectedRows = queryRunner.update(connection, deleteSQL, "李四");
System.out.println(deleteAffectedRows > 0 ? "删除成功" : "删除失败/所要删除的数据不存在");
//update
String updateSQL = "UPDATE account SET balance = ? WHERE name = ?";
int updateAffectedRows = queryRunner.update(connection, updateSQL, 10000, "张三");
System.out.println(updateAffectedRows > 0 ? "更新成功" : "更新失败/所要更新的数据不存在");
//3.关闭连接,释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
}
★★★JavaBean属性名和表列名
JavaBean类的属性名和其相应的MySQL表的列名之间会有一些问题:首先,
JavaBean中的字段名与MySQL列名的命名风格有所不同。**JavaBean遵循驼峰式命名法**,即首字母小写,后面每一个单词的首字母都大写;而MySQL则采用下划线命名法,即采用下划线分隔单词,所有字母均小写。因此,在将
JavaBean对象的属性保存到MySQL数据库中时,需要将JavaBean的字段名转化为MySQL列名。最直接的方式是直接在SQL语句中使用“AS”关键字将JavaBean字段名变成MySQL列名。反之同理,例如下面的代码:1
SELECT user_id AS 'userId', user_name AS 'userName', age FROM user;
否则是无法处理
JavaBean的相应属性和MySQL表的相应列的。例如,在某个
JavaBean类中有属性userId,而在MySQL表中有列user_id。在查询得到
MySQL表中的记录后,Apache-DBUtils底层会通过反射调用setUser_id方法对该JavaBean的实例对象的user_id属性赋值,但是JavaBean中只有setUserId方法,所以就无法对JavaBean的实例对象的userId属性赋值,则该对象的userId值就会为null。相当于查询无效。另外可以看到,在
JavaBean类中,需要有对其属性的Setter方法,否则是无法将查询结果赋值给JavaBean的实例对象的。
但是,对于大型系统而言,这种方法可能会导致
SQL语句变得臃肿且难以维护。因此,更好的做法是在
MySQL表中使用JavaBean字段名作为列名。这样可以避免在SQL语句中使用“AS”关键字进行字段名转化,从而使得SQL语句更加简洁。
总之,为了能将查询结果赋值给
JavaBean实例对象,需要满足两个条件:JavaBean类的属性名和其相应的MySQL表的列名需要一致,可以一开始就设置为一样,也可以在MySQL查询中使用AS。JavaBean类中需要有对其属性的Setter方法。
★★★BasicDAO
虽然
Apache-DBUtils + Druid简化了JDBC开发,但还有不足:SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查。- 对于
select操作,如果有返回值,返回类型不能固定,需要使用泛型。 - 将来的表很多,业务需求复杂,不可能只靠一个
Java类完成。
所以,我们需要使用到
DAO(Data Access Objects),数据访问对象。如下图所示,在
Utils工具类的基础上,我们加入DAO,来操作数据库中对应的表:
Utils:工具类,如前面的数据库连接池Druid以及Apache-DBUtils。DAO:数据访问对象类,不同的XxxDAO类对应不同的表,通过XxxDAO类的对象操作数据库中对应的表,同时还允许有独属于表的特有操作。不同的
XxxDAO类操作数据库中与其对应的表,但不同的XxxDAO类应该有类似的共同操作,例如连接数据库、释放连接等,可以将各个XxxDAO类的共同操作提取出来,抽象为BasicDAO类,以简化代码,提高维护性和可读性。而其他的
XxxDAO类皆继承BasicDAO类,为其子类。这样的通用类,称为
BasicDAO类,是专门和数据库交互的,即完成对数据库(表)的crud操作。**在
BaiscDAO的基础上,实现一张表对应一个DAO**,以更好地完成功能,比如Customer表 —Customer.java(javabean)—CustomerDao.java
**
Domain**:对应于数据库中的表的类,如前面例子中的Account类对应于account表。
BasicDAO分析
- 如前所述,在实现
BasicDAO时,应该有以下几部分:utils包,存放工具类。dao包,存放XxxDAO和BasicDAO类。domain包,存放JavaBean类。test包,存放测试类。
★★★BasicDAO实现
utils包
这里较为简单,只有一个
Druid数据库连接池的工具类: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
52package com.f.chapter21.dao_.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/12 21:25
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成ds的初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("file\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//得到Druid数据库连接池的Connection对象
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接,即将Connection对象归还给Druid数据库连接池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
dao包
BasicDAO类: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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91package com.f.chapter21.dao_.dao;
import com.f.chapter21.jdbc.utils.JDBCUtilsByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author fzy
* @date 2023/8/13 20:12
* BasicDAO,是其他DAO的父类
*/
public class BasicDAO<T> { //泛型指定具体的类型
private QueryRunner queryRunner = new QueryRunner();
//通用的dml操作,针对任意的表
public int dml(String sql, Object... params) {
//1.获得连接
Connection connection = null;
try {
//2.执行sql语句
connection = JDBCUtilsByDruid.getConnection();
return queryRunner.update(connection, sql, params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//3.关闭连接
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 通用的select操作,针对任意的表,返回多行多列数据
*
* @param sql sql语句,可以有?
* @param clazz 类的Class对象,例如Account.class
* @param params 传入?的具体的值,可以有多个
* @return 根据类的类型,返回对应的ArrayList集合
*/
public List<T> queryMulti(String sql, Class<T> clazz, Object... params) {
//1.获得连接
Connection connection = null;
try {
//2.执行sql语句
connection = JDBCUtilsByDruid.getConnection();
return queryRunner.query(connection, sql, new BeanListHandler<T>(clazz), params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//3.关闭连接
JDBCUtilsByDruid.close(null, null, connection);
}
}
//通用的select操作,针对任意的表,返回单行多列数据
public T querySingle(String sql, Class<T> clazz, Object... params) {
//1.获得连接
Connection connection = null;
try {
//2.执行sql语句
connection = JDBCUtilsByDruid.getConnection();
return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//3.关闭连接
JDBCUtilsByDruid.close(null, null, connection);
}
}
//通用的select操作,针对任意的表,返回单行单列数据,即返回单值
public Object queryScalar(String sql, Object... params) {
//1.获得连接
Connection connection = null;
try {
//2.执行sql语句
connection = JDBCUtilsByDruid.getConnection();
return queryRunner.query(connection, sql, new ScalarHandler<>(), params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//3.关闭连接
JDBCUtilsByDruid.close(null, null, connection);
}
}
}AccountDAO类:1
2
3
4
5
6
7
8
9
10
11
12package com.f.chapter21.dao_.dao;
import com.f.chapter21.dao_.domain.Account;
/**
* @author fzy
* @date 2023/8/13 20:43
*/
public class AccountDAO extends BasicDAO<Account> {
//1.拥有BasicDAO的所有方法
//2.可以根据业务需求,编写独属于该表的特有方法
}
domain包
对应于
account表的Account类: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
54package com.f.chapter21.dao_.domain;
/**
* @author fzy
* @date 2023/8/13 14:42
* Account类,对应mysql中的account表
*/
public class Account { //JavaBean、POJO、Domain
private Integer id;
private String name;
private Double balance;
public Account() {
}
public Account(Integer id, String name, Double balance) {
this.id = id;
this.name = name;
this.balance = balance;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", balance=" + balance +
'}';
}
}
test包
用于测试
XxxDAO类的TestDAO类: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
37package com.f.chapter21.dao_.test;
import com.f.chapter21.dao_.dao.AccountDAO;
import com.f.chapter21.dao_.domain.Account;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* @author fzy
* @date 2023/8/13 20:45
*/
public class TestDAO {
//测试 AccountDAO 对 account 表的 crud 操作
public void testAccountDAO() {
AccountDAO accountDAO = new AccountDAO();
//1.测试查询操作
// (1)查询多行多列数据
List<Account> accounts = accountDAO.queryMulti("SELECT * FROM account WHERE id >= ?", Account.class, 1);
System.out.println(accounts);
// (2)查询单行多列数据
Account account = accountDAO.querySingle("SELECT * FROM account WHERE id = ?", Account.class, 1);
System.out.println(account);
// (3)查询单行单列数据,即查询单值
Object name = accountDAO.queryScalar("SELECT name FROM account WHERE id = ?", 1);
System.out.println(name);
//2.测试dml操作
int insertAffectedRows = accountDAO.dml("INSERT INTO account VALUES (null, ?, ?)", "王五", 1000);
System.out.println(insertAffectedRows > 0 ? "插入成功" : "插入失败");
int deleteAffectedRows = accountDAO.dml("DELETE FROM account WHERE name = ?", "李四");
System.out.println(deleteAffectedRows > 0 ? "删除成功" : "删除失败/所要删除的数据不存在");
int updateAffectedRows = accountDAO.dml("UPDATE account SET balance = ? WHERE name = ?", 20000, "张三");
System.out.println(updateAffectedRows > 0 ? "更新成功" : "更新失败/所要更新的数据不存在");
}
}