1. JDBC工具类
项目地址:https://gitee.com/ghzhouwei/springboot-data-example
- 前面讲过数据库连接池帮传统JDBC解决了连接的创建和管理方面的问题,有兴趣的可以查看连接池技术这篇文章。然而JDBC编程还存在可以继续加工封装的部分,来分析一段JDBC查询数据代码:
// 定义sql语句
String sql = "select pk_id, username, password, sex, birthday from user";
// 获取数据库操作对象
Statement statement = connection.createStatement();
// 执行sql语句并获得结果
ResultSet resultSet = statement.executeQuery(sql);
// 遍历结果集
while (resultSet.next())
{
// 提取数据
Integer id = resultSet.getInt("pk_id");
String userName = resultSet.getString("username");
String passWord = resultSet.getString("password");
String sex = resultSet.getString("sex");
Date birthday = resultSet.getDate("birthday");
// 组装实体
UserDO user = new UserDO();
user.setId(id);
user.setUserName(userName);
user.setPassWord(passWord);
user.setSex(sex);
user.setBirthday(birthday);
// 打印实体
System.out.println("user = " + user);
}
针对上述代码可以从一个宏观的角度来分析,操作数据库就是为了拿到数据库的字段数据把它转成我们程序中所要用到的实体对象。然而针对不同的SQL每次获取的字段数据不同,需要转成实体对象也不同,这就面临着如何对查询到的字段数据进行解析,如何将解析的数据组装成对应的实体对象。获取什么样的字段数据,组装成什么样的实体对象这个只有程序员知道,怎么组装这就需要对类进行解剖,需要用到反射的技术。
- 为了简化这种查询数据组装实体的操作,在JDBC的基础上出现了很多工具类,比如Apache的DBUtils、Spring的JdbcTemplate等。
2. 创建数据库环境
基于MySQL数据库建立用户表方便对JDBC工具类的操作使用。
# 创建用户表
CREATE TABLE IF NOT EXISTS `user`
(
`pk_id` INT AUTO_INCREMENT,
`username` VARCHAR(20) NOT NULL,
`password` VARCHAR(20) NOT NULL,
`sex` CHAR(1),
`birthday` DATETIME,
CONSTRAINT `pk_user` PRIMARY KEY(`pk_id`)
);
# 添加用户数据
INSERT INTO
`user`(`username`, `password`, `sex`, `birthday`)
VALUES
('刘备', '01234', '男', '2020-11-15 10:34:12'),
('关羽', '12345', '男', '2020-11-24 10:34:12'),
('张飞', '23456', '男', '2020-12-08 10:34:12'),
('赵云', '34567', '男', '2020-12-18 10:34:12'),
('大乔', '45678', '女', '2020-11-01 10:34:12'),
('小乔', '56789', '女', '2020-12-01 10:34:12');
3. DBUtils工具类
DBUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响性能。
在DBUtils工具类中有两个比较核心的东西,一个是QueryRunner,一个是ResultSetHandler。前者是我们发送SQL的关键,后者就是将字段数据封装成实体对象的关键,而且ResultSetHandler有很多实现,不同的实现对应着不同的封装策略。
3.1 添加依赖
- DBUtils是Apache组织提供的,要想使用必须导入相应的jar包,这里采用maven构建方式。
(1)连接数据库必须的mysql驱动依赖。
(2)连接池技术用HikariCP技术。
(3)JDBC工具类采用Apache的DBUtils。
(4)Junit用来进行单元测试。
<!-- 依赖管理 -->
<dependencies>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
<!-- commons-dbutils -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 核心对象
- QueryRunner是DBUtils的核心,如何创建一个QueryRunner对象呢?它需要使用到数据源对象,也就是所谓的连接池对象。
QueryRunner queryRunner = new QueryRunner(datasource);
@Test
public void dbutilsTest()
{
// 创建数据源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://192.168.1.5:3306/zw_test");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner(dataSource);
// 打印对象
System.out.println("queryRunner = " + queryRunner);
// 关闭资源
dataSource.close();
}
- 拿到了QueryRunner核心对象就可以发SQL了,它有两个核心的方法
(1)queryRunner.update(String sql):执行增删改的SQL语句,支持占位符。
(2)queryRunner.query(String sql):执行查询的SQL语句,支持占位符。
@Test
public void test01() throws SQLException
{
// 定义SQL语句
String sql = "insert into user(pk_id, username, password, sex, birthday) values(null, ?, ?, ?, ?)";
// 增删改:添加数据
int count = queryRunner.update(sql, "马超", "654321", "男", new Date());
// 打印结果
System.out.println("count = " + count);
}
- 执行结果
- 添加数据前
- 添加数据后
3.3 封装策略
- QueryRunner对象的update方法是用来执行增删改等写操作,而QueryRunner对象的query方法用来执行查询等读操作,此时就可以指定查询到的字段数据要封装成什么样的数据结构,这个就是ResultSetHandler来完成的。
- ResultSetHandler是一个接口,它有很多的实现,不同的实现就是不同的封装策略,一般使用到如下策略:
3.3.1 ArrayHandler策略
- 它是将结果集中的第一条记录封装到一个数组中,显然是针对一条记录的。
- 假如查询到一条记录为:
- 经过ArrayHandler封装后是一个数组:
/**
* ArrayHandler
* @throws SQLException
*/
@Test
public void test02() throws SQLException
{
String sql = "select * from user where pk_id = ?";
Object[] userObject = queryRunner.query(sql, new ArrayHandler(), 1);
for (Object object : userObject)
{
System.out.println("object = " + object);
}
}
3.3.2 ArrayListHandler策略
- 它是将结果集中每一条记录封装到数组中后组成一个List集合,显然是针对多条记录,ArrayHandler的升级版。
- 假如查询到多条记录如下:
- 进过ArrayListHandler封装后是一个List,List中每一个元素是一个数组:
/**
* ArrayListHandler
* @throws SQLException
*/
@Test
public void test03() throws SQLException
{
String sql = "select * from user";
List<Object[]> userList = queryRunner.query(sql, new ArrayListHandler());
for (Object[] userArray : userList)
{
for (Object object : userArray)
{
System.out.println("object = " + object);
}
System.out.println("-----------------------------------");
}
}
3.3.3 BeanHandler策略
- 它是将一条记录封装到我们自己定义的JavaBean中,针对单个记录,应该是用的比较多的一种。
需要注意的是这个JavaBean中的字段必须提供Getter和Setter方法,而且查询到的字段要和属性名对应。
如果字段与属性不对应可以采用取别名的方式。
- 用户实体类
package com.intest.template.domain;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @ClassName: UserDO
* @Description: 用户实体类
* @Author: ZhouWei
* @Date: 2020-11-22 - 17:11
*/
public class UserDO implements Serializable
{
/**
* 用户编号
*/
private Integer id;
/**
* 用户账号
*/
private String userName;
/**
* 用户密码
*/
private String passWord;
/**
* 用户性别
*/
private String sex;
/**
* 用户生日
*/
private Date birthday;
/**
* Getter和Setter方法
*/
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public String getUserName()
{
return userName;
}
public void setUserName(String userName)
{
this.userName = userName;
}
public String getPassWord()
{
return passWord;
}
public void setPassWord(String passWord)
{
this.passWord = passWord;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
public Date getBirthday()
{
return birthday;
}
public void setBirthday(Date birthday)
{
this.birthday = birthday;
}
/**
* toString方法
*/
@Override
public String toString()
{
return "UserDO{" +
"id=" + id +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", sex='" + sex + '\'' +
", birthday=" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(birthday) +
'}';
}
}
- 查询示例
/**
* BeanHandler
* @throws SQLException
*/
@Test
public void test04() throws SQLException
{
String sql = "select pk_id as id, username as userName, password as passWord, sex as sex, birthday as birthday from user where pk_id = ?";
UserDO user = queryRunner.query(sql, new BeanHandler<UserDO>(UserDO.class), 1);
System.out.println("user = " + user);
}
- 查询结果
3.3.4 BeanListHandler策略
-
它是将多条记录封装到自己定义的JavaBean中然后组成一个List,针对多个记录,也是用的比较多的一种,类似BeanHandler。
-
查询示例
/**
* BeanListHandler
* @throws SQLException
*/
@Test
public void test05() throws SQLException
{
String sql = "select pk_id as id, username as userName, password as passWord, sex as sex, birthday as birthday from user";
List<UserDO> users = queryRunner.query(sql, new BeanListHandler<UserDO>(UserDO.class));
for (UserDO user : users)
{
System.out.println("user = " + user);
}
}
- 查询结果
3.3.5 ColumnListHandler策略
- 它是将多条记录的某个字段封装到一个List中,针对多条记录的一个字段。
- 查询示例
/**
* ColumnListHandler
* @throws SQLException
*/
@Test
public void test06() throws SQLException
{
String sql = "select * from user";
List<String> userNames = queryRunner.query(sql, new ColumnListHandler<String>("username"));
for (String userName : userNames)
{
System.out.println("userName = " + userName);
}
}
- 查询结果
3.3.6 KeyedHandler策略
- 这个策略很少用到,它有两层Map结构,类似于Map<Integer, Map<String, String>>这种。外层map的key一般指定为主键,外层map的value也是一个map,这个map就一条记录。内层map的key是字段名称,内层map的value就是字段对应的值。
- 假如查询到的结果如下:
- 封装后是一个Map嵌套:
- 查询示例
/**
* KeyedHandler
* @throws SQLException
*/
@Test
public void test07() throws SQLException
{
String sql = "select * from user";
Map<Integer, Map<String, Object>> userMaps = queryRunner.query(sql, new KeyedHandler<Integer>("pk_id"));
for (Map.Entry<Integer, Map<String, Object>> userMapEntry : userMaps.entrySet())
{
Integer key = userMapEntry.getKey();
System.out.println("key = " + key);
Map<String, Object> value = userMapEntry.getValue();
for (Map.Entry<String, Object> userMap : value.entrySet())
{
String key1 = userMap.getKey();
Object value1 = userMap.getValue();
System.out.println("key1 = " + key1);
System.out.println("value1 = " + value1);
}
System.out.println("----------------------------");
}
}
- 查询结果
3.3.7 MapHandler策略
-
它是将第一条记录封装成一个map结构,map的key就是字段名称,map的value就是字段对应的值,针对单条记录。
-
查询示例
/**
* MapHandler
* @throws SQLException
*/
@Test
public void test08() throws SQLException
{
String sql = "select * from user where pk_id = ?";
Map<String, Object> userMap = queryRunner.query(sql, new MapHandler(), 1);
for (Map.Entry<String, Object> filedEntry : userMap.entrySet())
{
String key = filedEntry.getKey();
System.out.println("key = " + key);
Object value = filedEntry.getValue();
System.out.println("value = " + value);
}
}
- 查询结果
3.3.8 MapListHandler策略
-
它是将多条记录封装到Map中后组成一个List结构,是MapHandler的升级版,针对多条记录。
-
查询示例
/**
* MapListHandler
* @throws SQLException
*/
@Test
public void test09() throws SQLException
{
String sql = "select * from user";
List<Map<String, Object>> userMapList = queryRunner.query(sql, new MapListHandler());
for (Map<String, Object> userMap : userMapList)
{
for (Map.Entry<String, Object> filedEntry : userMap.entrySet())
{
String key = filedEntry.getKey();
System.out.println("key = " + key);
Object value = filedEntry.getValue();
System.out.println("value = " + value);
}
System.out.println("--------------------------------");
}
}
- 查询结果
3.3.9 ScalarHandler策略
- 它是将查询到的单一值封装,也就是标量封装,只有一条记录并且这个记录只有一个字段,比如select count(*) from user可以使用它。
- 查询示例
/**
* ScalarHandler
* @throws SQLException
*/
@Test
public void test10() throws SQLException
{
String sql = "select count(*) from user";
Long count = queryRunner.query(sql, new ScalarHandler<Long>());
System.out.println("count = " + count);
}
- 查询结果
4. JdbcTemplate模板
jdbcTemplate是spring提供的JDBC模板,旨在简化JDBC操作。
4.1 添加依赖
(1)连接数据库必须的mysql驱动依赖。
(2)连接池技术用HikariCP技术。
(3)JDBC工具类采用Spring的JdbcTemplate。
(4)Junit用来进行单元测试。
<!-- 依赖管理 -->
<dependencies>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
4.2 核心对象
JdbcTemplate的核心对象就是JdbcTemplate,它的创建也是依赖于数据源。
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
@Test
public void jdbcTemplateTest()
{
// 创建数据源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://192.168.1.5:3306/zw_test");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 创建JDBC模板对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// 打印对象
System.out.println("jdbcTemplate = " + jdbcTemplate);
// 关闭资源
dataSource.close();
}
4.3 写操作
- JdbcTemplate并没有像DBUtils那样提供很多的封装策略,但是它的API也是满足我们需求的。
4.3.1 单独写操作
- JdbcTemplate提供update方法来执行增删改操作,并且支持占位符。
int update(String sql)
int update(String sql, Object... object)
4.3.2 批量写操作
- JdbcTemplate提供batchUpdate方法来执行增删改批量操作,并且支持占位符。
int[] batch(String[] sql)
int[] batchUpdate(String[] sql, List<Object[]> list)
4.4 读操作
- JdbcTemplate针对查询的API就比较丰富了,主要分为两类,查询单列、查询多列。
4.4.1 查询单列
- 查询单列单行
T queryForObject(String sql, Class<T> classObject)
T queryForObject(String sql, Class<T> classObject, Object... object)
- 查询单列多行
List<T> queryForList(String sql, Class<T> classObject)
List<T> queryForList(String sql, Class<T> classObject, Object... object)
4.4.2 查询多列
- 查询多列单行封装为Map
Map<String, Object> queryForMap(String sql)
Map<String, Object> queryForMap(String sql, Object... object)
- 查询多列多行封装为Map
List<Map<String, Object>> queryForList(String sql)
List<Map<String, Object>> queryForList(String sql, Object... object)
- 查询多列单行封装为Bean
T queryForObject(String sql, new BeanPropertyRowMapper<T>(Class<T>))
T queryForObject(String sql, new BeanPropertyRowMapper<T>(Class<T>), Object... object)
- 查询多列多行封装为Bean
List<T> query(String sql, new BeanPropertyRowMapper<T>(Class<T>))
List<T> query(String sql, new BeanPropertyRowMapper<T>(Class<T>), Object... object)