一、前情提要
在第一篇文章 Java操作数据库(一)——JDBC的快速入门 里面,我们介绍了JDBC的快速上手。
因为只是快速入门,所以有些东西没有介绍,比如:JDBC如果使用连接池、JDBC如何开启事务等等。
因此,这篇文章会先进行上述信息的补充。
由于原生JDBC开发起来非常麻烦,select获取的结果集还要逐列获值。
因此,DbUtils就出现啦,它可以自动帮我们把结果封装成对象,使用起来非常方便。
二、JDBC的补充知识
2.1 连接池(DataSource)
连接池,就是用于管理JDBC创建的Connection(连接)的一个容器,大家可以类比成我们Java中的线程池。
- 作用:可以减少连接的重复创建和销毁,尽可能地提高程序执行的效率。
- 原理(简):连接池会提前创建一定数量的
连接,当调用连接的close方法,这个连接会回到连接池而不是销毁。
连接池有许多种类:比如DBCP、C3P0、Druid、Hikari等等。
我们这里就选择使用阿里巴巴的Druid(德鲁伊)。
首先,我们需要引入Druid(推荐使用Maven)[Maven和jar包二选一即可]
jar包下载地址(提取码:8b5l)
Maven依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
接下来,我们使用连接池来修改上篇文章中的JDBC工具类。
前文的配置我们是直接写死在Java代码中,但是在一般的开发中,这些配置信息不应该放在Java代码中,而是放在一个专门存储配置信息的文件中(比如.properties、.yml)等等。
配置文件放置位置(可自行调整):
- 非Maven项目:
src->config包,并创建一个druid.properties文件。 - Maven项目:直接在
resources中创建druid.properties文件。
druid.properties:
# =============================数据库相关配置(自行调整)=====================================
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/travel?useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
username=root
password=123456
#==============================Druid连接池相关配置(自行调整,还有其他配置可以设置)=================================
# 初始连接数量
initialSize=10
# 最大连接数量
maxActive=20
# 获取连接最大等待时间
maxWait=3000
JDBCUtil.java:
哈哈,这个工具类是不是有点简陋了……(哈哈因为close等等琐事DbUtils帮我们做了OwO)
最后还会封装有个BaseDao喔,那个才是重头戏(●'◡'●)~
public class JDBCUtil {
/**
* 连接池
*/
static DataSource dataSource = null;
/**
* 静态代码块,用来初始化连接池
*/
static {
try {
//1.通过类加载器将properties文件变成输入流
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
// 加载输入流
properties.load(is);
//2.创建指定参数的数据库连接池
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取当前连接池
* @return
*/
public static DataSource getDataSource() {
return dataSource;
}
/**
* 获取Connection
* @return Connection
*/
public static Connection getConnection(){
Connection connection = null;
try {
// 建立Connection
connection = dataSource.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
/**
* 获取Connection,并开启事务
* @return Connection
*/
public static Connection getConnectionWithTransaction(){
Connection connection = null;
try {
// 建立Connection
connection = dataSource.getConnection();
// 关闭自动提交,即开启事务。(!!!需要自己写完业务相关的代码,手动提交)
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
}
2.2 JDBC开启事务
(上面的工具类有相应的方法)
获取
connection关闭自动提交,即开启事务
java connection.setAutoCommit(false);执行流程
java try{ // 业务操作¨K46K}catch{ // 回滚 connection.rollback(); }finally{ // 关闭连接 connection.close(); }
三、DbUtils的快速入门
终于轮到我们的主角DbUtils登场啦(ง •_•)ง!
DbUtils是属于Apache旗下的开源项目,官网:DbUtils
3.1 简介
我们先来简单看下,DbUtils是干什么的。
这图大家简单看下就好,我下面会总结一下(噗,就是为了证明我是从官网找的233)
简而言之:DbUtils可以高效地使用JDBC进行开发,让你更加专注于查询和更新数据(可以自动完成实体类等的映射)。
特点:
Small——DbUtils内容不多,上手非常快,主要有两个核心:QueryRunner和ResultSetHandler。Transparent—— 你只需要负责操作(写sql)即可,DbUtils会执行sql返回结果并关闭资源Fast—— 引入DbUtils即可使用,不需要额外操作。
3.2 `QueryRunner`和`ResultSetHandler`
QueryRunner和ResultSetHandler是DbUtils的两个核心。
3.2.1 QueryRunner介绍
QueryRunner的作用主要是:处理sql语句,进行增删改查等。
常用构造器:
// Constructor for QueryRunner that takes a DataSource to use.
// 传入一个连接池给QR使用
QueryRunner(DataSource ds)
常用CRUD的API我会在章节4进行演示。
3.2.2 ResultSetHandler介绍
`ResultSetHandler的作用主要是:完成结果映射(使用反射),比如我们查出的结果可以直接是一个User实体类。
| 常用映射 | 描述 |
|---|---|
| BeanHandler | 结果集 -> 实体类 (select * from user where id = 1 等等) |
| BeanListHandler | 结果集 -> 实体集合 (select * from user 等等) |
| ScalarHandler | 结果集 -> 单个数据(比如select count(*) from user 等等) |
常用构造器:
// Creates a new instance of BeanHandler.
// 这是BeanHandler的构造器,传入一个想要转换的实体类的Class类型,因为底层还是使用反射
// 其他类似
BeanHandler(Class<? extends T> type)
3.3 上手小案例
3.3.1 建立 user 表
大家简单建立个user表即可。
create database if not exists jdbc_demo
use jdbc_demo
create table user
(
id bigint auto_increment,
username varchar(64) not null comment '帐号',
password varchar(64) not null comment '密码',
constraint user_pk
primary key (id)
)
comment '用户表';
INSERT INTO jdbc_demo.user (username, password) VALUES ('user1', '123');
INSERT INTO jdbc_demo.user (username, password) VALUES ('user2', '456');
INSERT INTO jdbc_demo.user (username, password) VALUES ('user3', '123456');
3.3.2 导入相关jar包
mysql-connector-java-8.0.19.jar—— mysql驱动commons-dbutils-1.7.jar—— DbUtilsjar包下载地址(提取码:r7q9)druid-1.1.22.jar—— druid
3.3.3 操作流程
这里分了 6 点,主要是为了大家能够方便理解(●'◡'●),在章节4我会压缩下流程。
// 1.获取连接池
DataSource dataSource = JDBCUtil.getDataSource();
// 2.建立QueryRunner,将连接池当成参数传进构造器中
QueryRunner queryRunner = new QueryRunner(dataSource);
// 3.创建相关的ResultSetHandle,形成结果映射
BeanHandler<User> userBeanHandler = new BeanHandler<>(User.class);
// 4.编写sql语句
String sql = "select * from jdbc_demo.user where id = ?";
// 5.执行qr的query方法
// 参数1:sql语句
// 参数2:ResultSetHandler
// 参数3:类似preparedStatement,完成对sql语句占位符的替换
// 这个query底层就是使用preparedStatement
User user = queryRunner.query(sql, userBeanHandler, 1);
// 6.查看结果
System.out.println(user);
// Perfect (●'◡'●)
// User{id=1, username='user1', password='123'}
哈哈,通过这个小案例,我们成功获取了User实体类的相关信息。
是不是感觉很方便→ v →,别着急,更精彩的还在后面(●'◡'●)!
4. 常用CRUD的API介绍
4.1 查找单个数据
使用ScalarHandler
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 查询,
// Tip:先写handler,再写sql语句,这样有提示
Long query = queryRunner.query("select count(*) from jdbc_demo.user", new ScalarHandler<>());
// 3.输入
System.out.println(query);
// 得到 3
4.2 查找单条数据
使用BeanHandler
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 查询,
// Tip:先写handler,再写sql语句,这样有提示
User user = queryRunner.query(
"select * from jdbc_demo.user where username = ?",
new BeanHandler<>(User.class),
"user2");
// 3.输入
System.out.println(user);
// 得到 User{id=2, username='user2', password='456'}
4.3 查找多条数据
使用BeanListHandler
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 查询,
// Tip:先写handler,再写sql语句,这样有提示
// 也可以使用where,这边只是举个例子
List<User> users = queryRunner.query(
"select * from jdbc_demo.user",
new BeanListHandler<>(User.class));
// 3.输出
System.out.println(users);
// 得到
// [User{id=1, username='user1', password='123'},
// User{id=2, username='user2', password='456'},
// User{id=3, username='user3', password='123456'}]
4.4 增加
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 操作,
// Tip:先写handler,再写sql语句,这样有提示
// 也可以使用where,这边只是举个例子
// 增删改 都是 update
int res = queryRunner.update("insert into jdbc_demo.user values (null,'user4','user4')");
// 3.输出
System.out.println(res);
// 得到 1 说明成功(●'◡'●)
我们可以看到数据库确实有新的数据。
4.5 更新
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 操作,
// Tip:先写handler,再写sql语句,这样有提示
// 增删改 都是 update
int res = queryRunner.update(
"update jdbc_demo.user set password = '123456' where username = ?",
"user4");
// 3.输出
System.out.println(res);
// 得到 1 说明成功(●'◡'●)
可以看到,数据库的数据已经被修改了。
4.6 删除
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 操作,
// Tip:先写handler,再写sql语句,这样有提示
// 增删改 都是 update
int res = queryRunner.update(
"delete from jdbc_demo.user where username = ? ",
"user4");
// 3.输出
System.out.println(res);
// 得到 1 说明成功(●'◡'●)
可以看到,数据库中的user4已经被删除啦OvO
4.7 批量操作
这里我们介绍下批量插入, 删除和更新也是类似的。(记住数据库的url 要写上 rewriteBatchedStatements=true)
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
// 2. 操作
// 使用batch方法,
// 参数1:批量操作的sql
// 参数2:二位数组,行数 -> 执行次数 ,列 -> 替换占位符
Object[][] params = new Object[10][];
for (int i = 0; i < params.length; i++) {
params[i] = new Object[]{"lemonfish" + i, i};
}
// batch代表每行语句执行的结果
int[] batch = queryRunner.batch(
"insert into jdbc_demo.user values (null,?,?)",
params
);
// 3.输出
System.out.println(Arrays.toString(batch));
// 得到 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 说明全部成功(●'◡'●)
可以看到,10条数据被成功插入啦
4.8 事务操作
事务操作和之前的有一些区别,QueryRunner操作事务,需要用到connection(其实还是原生的JDBC),这时候用我们自己封装的工具类获取connection就行。
// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 2. 获取开启事务的connection
Connection connection = JDBCUtil.getConnectionWithTransaction();
// 3. 业务
// 3.1 删除、添加
try {
int delete = queryRunner.update(connection,
"delete from jdbc_demo.user where username = ? ",
"user3");
int insert = queryRunner.update(connection,
"insert into jdbc_demo.user values (null,'user4','user4')");
// 3.2 手动制造错误,查看回滚效果
int error = 1 / 0;
// 3.3 手动提交
connection.commit();
} catch (SQLException throwables) {
// 3.4 进行回滚
connection.rollback();
throwables.printStackTrace();
} finally{
// 记住关闭
connection.close();
}
rollback会在代码执行出错的情况下,回滚到connection执行sql语句前的状态。
5. 封装BaseDAO
哈哈,相信大家看到这里,是不是已经感觉相比原生JDBC,DbUtils用起来已经很爽了呢。
别急,还有更爽的呢→ v →
我们接下来封装一个BaseDAO ,封装一些常用的增删改查操作,然后使用子类继承的方式即可使用。
增删改查,一句sql搞定
这个就是BaseDAO的代码,基本就是整合了下前面的API。
/**
* 封装基本的增删改查,以复用代码
*/
public class BaseDAO<T> {
QueryRunner qr = new QueryRunner(JDBCUtil.getDataSource());
/**
* 增删改
*
* @param sql sql语句
* @param params 占位符
* @return
*/
public boolean update(String sql, Object... params) {
int result = 0;
try {
result = qr.update(sql, params);
} catch (SQLException e) {
e.printStackTrace();
}
return result > 0;
}
/**
* 查询 单个值
*
* @param sql
* @param params
* @return
*/
public Object selectScalar(String sql, Object... params) {
try {
return qr.query(sql, new ScalarHandler<>(), params);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 查询 单行
*
* @param sql
* @param clazz
* @param params
* @return
*/
public T selectOne(String sql, Class<T> clazz, Object... params) {
try {
return qr.query(sql, new BeanHandler<>(clazz), params);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 查询 多行
*
* @param sql
* @param clazz
* @param params
* @return
*/
public List<T> selectList(String sql, Class<T> clazz, Object... params) {
try {
return qr.query(sql, new BeanListHandler<>(clazz), params);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 批量操作 增删改
* @param sql
* @param params
* @return
*/
public int[] batch(String sql,Object[][] params){
int[] batch = new int[0];
try {
batch = qr.batch(sql,params);
} catch (SQLException e) {
e.printStackTrace();
}
return batch;
}
}
使用方式
编写一个
UserDAO继承BaseDAOpublic class UserDAO extends BaseDAO<User>{ // 可以自行拓展,BaseDAO只是封装了最基本的增删改查 }使用实例
// 1.获取UserDAO UserDAO userDAO = new UserDAO(); // 2.使用 User user = userDAO.selectOne("select * from user where username= ?", User.class, "user1"); // 3. 操作结果 System.out.println(user); // 输出结果:User{id=1, username='user1', password='123'}
哈哈!是不是简简单单一句sql就搞定查询啦OwO,增删改等等也是一样喔~~
DbUtils的底层实现主要是JDBC和反射(比如生成bean,然后调用setter等等),源码稍微看个大概不是很难,有兴趣的小伙伴可以自行阅读下。
6. DEMO 源码
为了大家方便拿到文章中的DEMO代码,我把DEMO源码放到 Github 上啦,有需要的小伙伴可以自行下载喔(包括所有本文出现过的代码)。
7. 写在最后
看到这里,相信你对Java操作数据库又有了进一步的认识啦(●'◡'●)
基本在接触持久层框架前,这算是比较好的处理办法啦。
如果有哪里写得不是很好,欢迎在评论区指出
希望大家都能一起进步,不要忘记顺手点个👍b( ̄▽ ̄)d噢~~