数据库连接池
★★★数据库连接池
传统获取
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 ? "更新成功" : "更新失败/所要更新的数据不存在");
}
}