《精通
Spring Boot 42
讲》共分五⼤部分,第三部分主要讲解
Spring Boot
和数据库开发,共
8
课。 Spring Boot ⽀持了主流的
ORM
框架:
MyBatis
、
Hibernate
和
Spring JDBC
,⼏种
ORM
在不同的场 景下各有优势,在 Spring Boot
体系内都有对应的
Starter
包以⽅便集成。⾸先将讲解
Spring JDBC
的 使⽤,然后介绍 MyBatis
和
JPA
的各种使⽤场景以及多数据源的使⽤,接着演示如何集成流⾏的数据 库连接池 Druid
,最后结合本课程第⼆部分内容综合实践
JPA
和
Thymeleaf
的使⽤。
JDBC
(
Java Data Base Connectivity
,
Java
数据库连接)是⼀种⽤于执⾏
SQL
语句的
Java API
,可以为多 种关系数据库提供统⼀访问,它由⼀组⽤ Java
语⾔编写的类和接⼝组成。
JDBC
提供了⼀种基准,据此可以 构建更⾼级的⼯具和接⼝,使数据库开发⼈员能够编写数据库应⽤程序。
说⽩了
JDBC
就是⼀套
Java
访问数据库的
API
规范,利⽤这套规范屏蔽了各种数据库
API
调⽤的差异性。当 Java 程序需要访问数据库时,直接调⽤ JDBC API 相关代码进⾏操作,JDBC 调⽤各类数据库的驱动包进
⾏交互,最后数据库驱动包和对应的数据库通讯,完成 Java
程序操作数据库。
直接在
Java
程序中使⽤
JDBC
⽐较复杂,需要七步才能完成数据库的操作:
- 加载数据库驱动
- 建⽴数据库连接
- 创建数据库操作对象
- 定义操作的 SQL 语句
- 执⾏数据库操作
- 获取并操作结果集
- 关闭对象,回收资源
关键代码如下:
try {
// 1、加载数据库驱动
Class.forName(driver);
// 2、获取数据库连接
conn = DriverManager.getConnection(url, username, password);
// 3、获取数据库操作对象
stmt = conn.createStatement();
// 4、定义操作的 SQL 语句
String sql = "select * from user where id = 6";
// 5、执⾏数据库操作
rs = stmt.executeQuery(sql);
// 6、获取并操作结果集
while (rs.next()) {
// 解析结果集
}
} catch (Exception e) {
// ⽇志信息
} finally {
// 7、关闭资源
}
通过上⾯的示例可以看出直接使⽤
JDBC
来操作数据库⽐较复杂,因此后期在
JDBC
的基础上⼜发展出了很 多著名的 ORM
框架,其中最为流⾏的是
Hibernate
、
MyBatis
和
Spring JDBC
。这三个流⾏的
ORM
框架在 后续的课程中都会讲到,这⾥主要了解⼀下 Spring JDBC
在
Spring Boot
中的使⽤。
Spring Boot
针对
JDBC
的使⽤提供了对应的
Starter
包:
spring-boot-starter-jdbc
,它其实就是在
Spring JDBC 上做了进⼀步的封装,⽅便在
Spring Boot
⽣态中更好的使⽤
JDBC
,下⾯进⾏示例演示。
快速上⼿
Spring Boot
集成
JDBC
很简单,需要引⼊依赖并做基础配置即可,在开发项⽬之前需要先创建表,作为项 ⽬演示使⽤。设计⼀个 User
⽤户表,有
id
、
name
、
password
、
age
等字段,对应的
SQL
脚本如下:
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(32) DEFAULT NULL COMMENT '⽤户名',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`age` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
添加配置
添加依赖包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
演示项⽬中使⽤
MySQL
作为数据库,因此项⽬中需要引⼊
MySQL
驱动包,同时引⼊
spring-boot-starterjdbc。打开
pom.xml
⽂件,按下快捷键:
Ctrl + Alt + SHIFT + U
,或者单击右键,选择
Diagrams | Show Dependencies 选项,查看项⽬依赖类图。
弹出
“
类图
”
对话框后,滚动⿏标放⼤查看,发现
spring-boot-starter-jdbc
直接依赖于
HikariCP
和
springjdbc。
- HikariCP 是 Spring Boot 2.0 默认使⽤的数据库连接池,也是传说中最快的数据库连接池。
- spring-jdbc 是 Spring 封装对 JDBC 操作的⼯具包。
数据源配置:
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnico
de=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
值得注意的是,在
Spring Boot 2.1.0
中,
com.mysql.jdbc.Driver
已经过期,推荐使⽤
com.mysql.cj.jdbc.Driver
。
实体类
创建表对应的实体类:
public class User {
private Long id;
private String name;
private String password;
private int age;
public User(String name, String password, int age) {
this.name = name;
this.password = password;
this.age = age;
}
// 省略 getter setter
}
实体类的数据类型要和数据库字段⼀⼀对应:
- Long 对应 bigint
- String 对应 varchar
- int 对应 int
封装 Repository
创建
UserRepository
定义我们常⽤的增删改查接⼝:
public interface UserRepository {
int save(User user);
int update(User user);
int delete(long id);
List<User> findALL();
User findById(long id);
}
创建
UserRepositoryImpl
类实现
UserRepository
类接⼝:
@Repository
public class UserRepositoryImpl implements UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
}
类上使⽤
@Repository
注解⽤于标注数据访问组件,同时在类中注⼊
JdbcTemplate
,其是
Spring
操作 JDBC 提供的⼯具类。
接下来封装保存⽤户的⽅法:
@Override
public int save(User user) {
return jdbcTemplate.update("INSERT INTO users(name, password, age) values(?, ?
, ?)",
user.getName(), user.getPassword(), user.getAge());
}
通过以上代码可以看出,其实就是拼接数据库插⼊的
SQL
,作为参数传给
update
⽅法。
更新⽤户信息和上面类似:
@Override
public int update(User user) {
return jdbcTemplate.update("UPDATE users SET name = ? , password = ? , age = ?
WHERE id=?",
user.getName(), user.getPassword(), user.getAge(), user.getId());
}
封装删除⽤户的⽅法:
@Override
public int delete(long id) {
return jdbcTemplate.update("DELETE FROM users where id = ? ",id);
}
根据⽤户
id
查询⽤户:
@Override
public User findById(long id) {
return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id=?", new Objec
t[] { id }, new BeanPropertyRowMapper<User>(User.class));
}
这⾥使⽤了
new BeanPropertyRowMapper<User>(User.class)
对返回的数据进⾏封装,它可⾃动 将⼀⾏数据映射到指定类的实例中,⾸先将这个类实例化,然后通过名称匹配的⽅式,映射到属性中去。
最后封装获取⽤户列表:
@Override
public List<User> findALL() {
return jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());
// return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper(
User.class));
}
fifindALL()
使⽤了⼀个新的⽅式来封装结果集的返回,创建⼀个内部类
UserRowMapper
。
class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
user.setAge(rs.getInt("age"));
}
}
UserRowMapper
继承了
RowMapper
,
RowMapper
可以将数据中的每⼀⾏数据封装成⽤户定义的类,实现 RowMapper 接⼝覆盖
mapRow
⽅法,在
mapRow
⽅法封装对数据的返回处理。通过上⾯代码可以看出 UserRowMapper 循环遍历了查询返回的结果集,遍历的同时按照属性进⾏赋值。这样在查询使⽤时只需要 传⼊ new UserRowMapper()
即可⾃动解析返回数据。
测试
接下⾥我们对封装好的
UserRepository
进⾏测试,测试
UserRepository
中的各个⽅法是否正确。创建 UserRepositoryTests 类,将
userRepository
注⼊到类中。
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {
@Autowired
private UserRepository userRepository;
}
测试插⼊数据,直接调⽤
userRepository
对应的
save
⽅法。
@Test
public void testSave() {
User user =new User("neo","123456",30);
userRepository.save(user);
}
执⾏成功后会在数据库中发现⼀条
name
为
neo
的值,证明插⼊成功。
按照同样的⽅法测试更新:
@Test
public void testUpdate() {
User user =new User("neo","123456",18);
user.setId(1L);
userRepository.update(user);
}
测试删除:
@Test
public void testDetele() {
userRepository.delete(1L);
}
测试查询:
@Test
public void testQueryOne() {
User user=userRepository.findById(1L);
System.out.println("user == "+user.toString());
}
测试查询⽤户列表:
@Test
public void testQueryAll() {
List<User> users=userRepository.findALL();
for (User user:users){
System.out.println("user == "+user.toString());
}
}
测试执⾏正常,则表明
userRepository
中⽅法正确。
多数据源的使⽤
在项⽬中使⽤多个数据源是很常⻅的情况,
Spring Boot
中多数据源的使⽤需要⾃⾏封装。我们在上⾯示例项
⽬的基础上进⾏改造。
配置⽂件
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezon
e=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimez
one=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
添加了两个数据源,⼀个是
test1
库,⼀个是
test2
库。
注意,这⾥使⽤的是
spring.datasource.*.jdbc-url
,因为默认连接池
HikariCP
读取的是
jdbc-url
。
初始化 JDBC
在项⽬启动的时候读取配置⽂件中的信息,并对
JDBC
初始化。
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate (
@Qualifier("primaryDataSource") DataSource dataSource ) {
return new JdbcTemplate(dataSource);
}
@Bean(name="secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
这段代码表示在启动的时候根据特定的前缀加载不同的数据源,根据构建好的数据源再创建不同的
JDBC
。
UserRepository 改造
我们对
UserRepository
中的所有⽅法进⾏改造,增加⼀个参数为
JdbcTemplate
,如果⽅法中传输了 JdbcTemplate,⽅法内就会使⽤传递的
JdbcTemplate
进⾏操作,如果传递的
JdbcTemplate
为空,使⽤默认
的
JdbcTemplate
连接操作。
@Repository
public class UserRepositoryImpl implements UserRepository {
@Autowired
private JdbcTemplate primaryJdbcTemplate;
@Override
public int save(User user,JdbcTemplate jdbcTemplate) {
if(jdbcTemplate == null){
jdbcTemplate= primaryJdbcTemplate;
}
return jdbcTemplate.update("INSERT INTO users(name, password, age) values(
?, ?, ?)",
user.getName(), user.getPassword(), user.getAge());
}
//其他⽅法省略,详细内容可以查看源码
}
多数据源测试
测试类中注⼊了两个不同数据源的
JdbcTemplate
,同时注⼊
UserRepository
。测试使⽤不同的 JdbcTemplate 插⼊两条数据,查看两个数据库中是否都保存成功。
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {
@Autowired
private UserRepository userRepository;
@Autowired
private JdbcTemplate primaryJdbcTemplate;
@Autowired
private JdbcTemplate secondaryJdbcTemplate;
@Test
public void testSave() {
User user =new User("smile","123456",30);
userRepository.save(user,primaryJdbcTemplate);
userRepository.save(user,secondaryJdbcTemplate);
}
}
测试前请先创建
test1
和
test2
数据库,以及两个数据库中的⽤户表。
执⾏
testSave()
成功后,登录
test1
和
test 2
数据库查看
user
表,都存在⼀条
name
为
smile
的⽤户信息, 说明多数据源插⼊数据成功,其他⽅法的测试⼤体相同。这样在项⽬中,我们想使⽤哪个数据源操作数据库 时,只需要传⼊数据源对应的 JdbcTemplate
实例即可。
总结
通过本节课程的学习,了解到使⽤原⽣的
JDBC
操作数据库⾮常繁琐,需要开发者⾃⾏封装数据库连接,执 ⾏完成后⼿动关闭对应的资源,这样不利于统⼀规范,也容易出现问题。后期 Spring
针对
JDBC
的使⽤推出 了 Spring JDBC
,
Spring Boot
在此基础上⼜进⾏了⼀步封装,如今在
Spring Boot
项⽬中
JDBC
操作数据库
⾮常简单。