我是小先,一个专注大数据、分布式技术的非斜杠青年,爱Coding,爱阅读、爱摄影,更爱生活!
大数据小先博客主页:https://me.csdn.net/u010974701
源代码仓库:https://github.com/zhshuixian/learn-spring-boot-2
上一小节主要介绍了 Spring Boot 2.x 实战--整合 Log4j2 与 Slf4j 实现日志打印和输出到文件。
在应用开发中,难免要和数据库打交道,在 Java 生态中,常用的开源持久层框架有 MyBatis 、Hibernate 等,这里要说明一点的是,《Spring Boot 2.X》实战的示例项目将主要使用 MyBatis 或者 MyBatis-Plus。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
这一小节将以用户信息表的为例子实战 Spring Data JPA 连接 SQL 数据库并读写数据,主要分为如下几个部分:
JPA 的依赖引入
JPA 链接 MySQL
JPA 实体 @Entity、@Id
JPA 写入、更新、删除、查询数据
JPA 多条记录写入、查询、分页查询
JPA 自定义 SQL 查询
这里使用 MySQL,如果你想使用如:PostgreSQL 等其他的数据库,只需要更改相对应的依赖和指定 Driver 驱动包即可。
这里需要你提前安装好 MySQL 或其他 SQL 数据库。
参考文章在 Linux 下安装 MySQL 8 : https://blog.csdn.net/u010974701/article/details/85625228
安装完成后运行如下命令:
1create database spring;
1、什么是 Spring Data JPA
JPA(Java Persistence API),中文名称为 Java 持久化 API,从 JDK 5 开始引入,是 ORM 的标准 Java 规范。JPA 主要是为了简化 Java 持久层应用的开发,整合像 Hibernate、TopLink、JDO 等 ORM 框架,并不提供具体实现。
JPA 的一些优点特性:
标准化 :标准的 JPA 规范提供的接口 类,几乎不用修改代码就可以迁移到其他 JPA 框架。
简单易用:使用注解的方式定义 Java 类和关系数据库之间的映射,无需 XML 配置。
迁移方便:更改数据库、更换 JPA 框架几乎不用修改代码。
高级特性:媲美 JDBC 的查询能力;可以使用面向对象的思维来操作数据库;支持大数据集、事务、并发等容器级事务;
JPA 主要的技术:
ORM:使用注解或者 XML 描述对象和数据表的映射关系
API:规范的 JPA 接口、类。
JPQL:面向对象的查询语言,避免程序和具体 SQL 紧密耦合。
Spring Data JPA 是 Spring Data 的子集,默认使用 Hibernate 作为底层 ORM。官网文档 https://spring.io/projects/spring-data-jpa 是这么介绍 :
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.
Spring 对 JPA 的支持非常强大,使得 JPA 的配置更加灵活;将 EntityManager 的创建与销毁、事务管理等代码抽取出来统一管理;实现了部分 EJB 的功能,如容器注入支持。Spring Data JPA 则更进一步,简化业务代码,我们只需要声明持久层的接口即可,剩下的则交给框架帮你完成,其使用规范的方法名,根据规范的方法名确定你需要实现什么样的数据操作逻辑。
扩展阅读 :根据方法名自动生成 SQL 规则可以参考微笑哥的博客:http://ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html
Hibernate 开源 ORM(对象/关系映射)框架。
Spring Data JPA 默认使用 Hibernate 作为底层 ORM ,是 Spring Data 的子集。
2、Spring Data JPA 的配置
2.1、依赖引入
在 IDEA 新建一个项目 02-sql-spring-data-jpa,勾选如下的依赖,相关依赖下载慢的话可以更换国内的镜像源:

Gradle 依赖配置
1dependencies {
2 implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
3 implementation 'org.springframework.boot:spring-boot-starter-web'
4 compileOnly 'org.projectlombok:lombok'
5 runtimeOnly 'mysql:mysql-connector-java'
6 annotationProcessor 'org.projectlombok:lombok'
7 testImplementation('org.springframework.boot:spring-boot-starter-test') {
8 exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
9 }
10}
1<dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-data-jpa</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>org.springframework.boot</groupId>
8 <artifactId>spring-boot-starter-web</artifactId>
9 </dependency>
10 <dependency>
11 <groupId>mysql</groupId>
12 <artifactId>mysql-connector-java</artifactId>
13 <scope>runtime</scope>
14 </dependency>
15 <dependency>
16 <groupId>org.projectlombok</groupId>
17 <artifactId>lombok</artifactId>
18 <optional>true</optional>
19 </dependency>
20 <dependency>
21 <groupId>org.springframework.boot</groupId>
22 <artifactId>spring-boot-starter-test</artifactId>
23 <scope>test</scope>
24 <exclusions>
25 <exclusion>
26 <groupId>org.junit.vintage</groupId>
27 <artifactId>junit-vintage-engine</artifactId>
28 </exclusion>
29 </exclusions>
30 </dependency>
31 </dependencies>
2.2、连接 SQL 数据库
编辑 src/main/resources/application.properties 文件,写入如下内容:
1# 数据库 URL、用户名、密码、JDBC Driver更换数据库只需更改这些信息即可
2# MySQL 8 需要指定 serverTimezone 才能连接成功
3spring.datasource.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
4spring.datasource.password=xiaoxian
5spring.datasource.username=root
6spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
7# Hibernate 的一些配置
8spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
9# 是否在 Log 显示 SQL 执行语句
10spring.jpa.show-sql=true
11# hibernate.ddl-auto 配置对数据库表的操作
12# create和create-drop:每次都删掉 Entity 对应的数据表并重新创建
13# update : 根据 Entity 更新数据表结构,不会删除数据表
14# none: 默认值,不做任何操作,实际中推荐使用这个
15spring.jpa.hibernate.ddl-auto=none
特别说明的是,spring.jpa.hibernate.ddl-auto 使用 create 模式可以方便的自动根据 @Entity 注解的类自动生成对应的数据表,但实际开发中不建议使用,不然重新运行项目就是一次删(pao)库(lu)。
1# create 模式的一些日志
2Hibernate: drop table if exists sys_user
3Hibernate: create table sys_user (user_id bigint not null, user_address varchar(128), user_age integer, username varchar(16), primary key (user_id)) engine=InnoDB
3、开始使用 Spring Data JPA
项目按功能分为如下三层:

API 接口层:提供 RESTful API 接口,是系统对外的交互的接口。
接口服务层:应用的主要逻辑部分,不推荐在 API 接口层写应用逻辑。
数据持久层:编写相应的 Repository 接口,实现与 MySQL 数据库的交互。
3.1 数据表结构和 @Entity 实体类
这里使用一个用户信息表作为示例,结构如下所示:
| 字段名 | 字段类型 | 备注 |
|---|---|---|
| user_id | bigint | 主键,自增 |
| username | varchar(18) | 用户名,非空唯一 |
| nickname | varchar(36) | 用户昵称,非空 |
| user_age | tinyint | 用户年龄 |
| user_sex | varchar(2) | 用户性别 |
1-- MySQL 数据库,其他数据库可能需要自行修改
2create table sys_user
3(
4 user_id bigint auto_increment,
5 username varchar(18) not null,
6 nickname varchar(36) not null,
7 user_age tinyint null,
8 user_sex varchar(2) null,
9 constraint sys_user_pk
10 primary key (user_id)
11);
1@Entity
2@Getter
3@Setter
4@Table(name = "sys_user",schema = "spring")
5public class SysUser {
6 @Id
7 @GeneratedValue(strategy=GenerationType.IDENTITY)
8 private Long userId;
9
10 @Column(length = 18,unique = true,nullable = false,name = "username",updatable = true)
11 @NotEmpty(message = "用户名不能为空")
12 @Pattern(regexp = "^[a-zA-Z0-9]{3,16}$", message = "用户名需3到16位的英文,数字")
13 private String username;
14
15 @Column(length = 18,nullable = false)
16 @NotEmpty(message = "用户昵称不能为空")
17 private String nickname;
18
19 @Range(min=0, max=100,message = "年龄需要在 0 到 100 之间")
20 private Integer userAge;
21
22 // userSex 会自动映射到 user_sex 的字段名
23 @Column(length = 2)
24 private String userSex;
25}
代码解析:
@Entity:表明这是一个实体类,在 JPA 中用于注解 ORM 映射类。
@Table(name = "sys_user", schema = "spring"):注明 ORM 映射类对应的表名,默认是类名。如:SysUser 映射为 sys_user,Java 驼峰法命名映射到 SQL 时,用下划线 _ 隔开,字段名也是同样的规则。schema 指定数据库,默认就是数据库连接配置中指定的。
@Id:主键注解。
IDENTITY,自增主键,由数据库自动生成
SEQUENCE,序列,根据数据库的序列来生产主键
TABLE ,指定一个数据表来保存主键
AUTO,由程序自动控制,默认值
@Column(length = 18,unique = true,nullable = false,name = " ", updatable = true):指定字段的长度、是否唯一、是否可以 null、字段名,是否可以更新这个字段。其中默认非唯一约束、可以为 null 值,字段名默认根据名称规则映射,updateable 默认 true。
@NotEmpty(message = " "):不能为空,message 表示 null 或者字符长度为 0 时候的提示信息。
@Pattern:正则表达式,例如你可以用来验证用户名、密码是否符合规范。
@Range:指定最大值和最小值,例如指定分数最大是 100。
3.2、编写 JpaRepository 接口
1import org.springframework.data.jpa.repository.JpaRepository;
2import org.springframework.stereotype.Repository;
3import org.xian.boot.entity.SysUser;
4
5@Repository
6public interface SysUserRepository extends JpaRepository<SysUser, Long> {
7// JpaRepository<SysUser, Long> ,第一个参数指定 Entity 实体类,第二个指定主键类型
8}
3.3、增加、查询、更新、删除
MyResponse:通用消息返回类,增加、删除、修改操作是否成功和信息返回的类:
1@Getter
2@Setter
3@NoArgsConstructor
4@AllArgsConstructor
5@ToString
6public class MyResponse implements Serializable {
7 private static final long serialVersionUID = -2L;
8 private String status;
9 private String message;
10}
3.3.1、增加一条数据
1package org.xian.boot.service;
2import org.springframework.stereotype.Service;
3import org.xian.boot.MyResponse;
4import org.xian.boot.entity.SysUser;
5import org.xian.boot.repository.SysUserRepository;
6import javax.annotation.Resource;
7
8@Service
9public class SysUserService {
10 @Resource
11 private SysUserRepository sysUserRepository;
12
13 /**
14 * 保存一条记录
15 * @param sysUser 用户信息
16 * @return 保存结果
17 */
18 public MyResponse save(SysUser sysUser) {
19 try {
20 sysUserRepository.save(sysUser);
21 return new MyResponse("success", "新增成功");
22 } catch (Exception e) {
23 return new MyResponse("error", e.getMessage());
24 }
25 }
26}
代码解析:
@Service:定义一个 Bean ,此注解的类会自动注册到 Spring 容器。
@Resource:相对于 @Autowired 注解,Bean 的自动装配。
1package org.springframework.data.repository;
2@NoRepositoryBean
3public interface CrudRepository<T, ID> extends Repository<T, ID> {
4 /**
5 * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
6 * entity instance completely.
7 *
8 * @param entity must not be {@literal null}.
9 * @return the saved entity; will never be {@literal null}.
10 * @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
11 */
12 <S extends T> S save(S entity);
13}
1@RestController
2@RequestMapping(value = "/api/user")
3public class SysUserController {
4 @Resource
5 private SysUserService sysUserService;
6
7 @PostMapping(value = "/save")
8 public MyResponse save(@RequestBody SysUser sysUser) {
9 return sysUserService.save(sysUser);
10 }
11}
1{
2 "username":"xiaoxian",
3 "nickname":"小先哥哥",
4 "userAge":17,
5 "userSex":"男"
6}
3.3.2、查询一条数据
功能,根据用户名 username 查询用户信息,SysUserRepository 类中新增:
1 /**
2 * 根据用户名查询用户信息
3 *
4 * @param username 用户名
5 * @return 用户信息
6 */
7 SysUser findByUsername(String username);
1 public SysUser find(String username) {
2 return sysUserRepository.findByUsername(username);
3 }
1 @PostMapping(value = "/find")
2 public SysUser find(@RequestBody String username) {
3 return sysUserService.find(username);
4 }

3.3.3、更新用户数据
目标,实现一个根据用户名更改用户信息的接口,SysUserService 新增:
1 public MyResponse update(SysUser sysUser) {
2 // 实际开发中,需要根据具体业务来写相应的业务逻辑,这里只是做一个示例
3 try {
4 // 需要先根据用户名 username 查询出主键,然后再使用 save 方法更新
5 SysUser oldSysUser = sysUserRepository.findByUsername(sysUser.getUsername());
6 sysUser.setUserId(oldSysUser.getUserId());
7 sysUserRepository.save(sysUser);
8 return new MyResponse("success", "更新成功");
9 } catch (Exception e) {
10 return new MyResponse("error", e.getMessage());
11 }
12 }
1 @PostMapping(value = "/update")
2 public MyResponse update(@RequestBody SysUser sysUser){
3 return sysUserService.update(sysUser);
4 }

3.3.4、删除一条数据
目标,实现一个 API 接口,实现对用户信息的删除,SysUserService 新增:
1 public MyResponse delete (String username){
2 try {
3 SysUser oldSysUser = sysUserRepository.findByUsername(username);
4 sysUserRepository.delete(oldSysUser);
5 return new MyResponse("success", "删除成功");
6 } catch (Exception e) {
7 return new MyResponse("error", e.getMessage());
8 }
9 }
1 @PostMapping(value = "/delete")
2 public MyResponse delete(@RequestBody String username){
3 return sysUserService.delete(username);
4 }

3.4 、多条记录写入
多条记录的写入跟单条记录的写入差不多。SysUserService 新增:
1 public MyResponse saveAll(List<SysUser> sysUserList) {
2 try {
3 sysUserRepository.saveAll(sysUserList);
4 return new MyResponse("success", "新增成功");
5 } catch (Exception e) {
6 return new MyResponse("error", e.getMessage());
7 }
8 }
1 @PostMapping(value = "/saveAll")
2 public MyResponse saveAll(@RequestBody List<SysUser> sysUserList) {
3 return sysUserService.saveAll(sysUserList);
4 }

3.5、浏览全部记录
1 public List<SysUser> list(){
2 return sysUserRepository.findAll();
3 }
SysUserController 新增:
1 @GetMapping(value = "list")
2 public List<SysUser> list(){
3 return sysUserService.list();
4 }
重新运行,使用 Postname GET 方式访问 http://localhost:8080/api/user/list ,可以看到返回数据库表中所有的数据。
3.6、分页浏览
在 3.5 小节中,使用此方式查询的是全部数据,对于数据量大的表来说非常不方便,这里将实现一个分页浏览的 API 接口:
SysUserService 新增:
1 public Page<SysUser> page(Integer page, Integer size) {
2 // 根据 userId 排序,Sort.Direction.ASC/DESC 升序/降序
3 Pageable pageable = PageRequest.of(page, size, Sort.Direction.ASC, "userId");
4 return sysUserRepository.findAll(pageable);
5 }
SysUserController 新增:
1 @PostMapping(value = "page")
2 public Page<SysUser> page(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "3") Integer size) {
3 // page 从 0 开始编号
4 // 默认浏览第一页,每页大小为 3
5 return sysUserService.page(page, size);
6 }
重新运行,使用 Postname 方式访问 http://localhost:8080/api/user/page?page=1&size=2 ,可以看到返回如下所示的数据:
1{ // content 结果集
2 "content": [
3 {
4 "userId": 12,
5 "username": "zhang",
6 "nickname": "张小先",
7 "userAge": 23,
8 "userSex": "男"
9 },
10 {
11 "userId": 16,
12 "username": "daxian",
13 "nickname": "大先哥哥",
14 "userAge": 19,
15 "userSex": "男"
16 }
17 ],
18 "pageable": {
19 // 排序信息
20 "sort": {
21 "sorted": true,
22 "unsorted": false,
23 "empty": false
24 },
25 "offset": 2,
26 "pageSize": 2, // 每页数据集大小
27 "pageNumber": 1, // 页数
28 "paged": true,
29 "unpaged": false
30 },
31 "totalElements": 5, // 总数据量
32 "last": false, // 是否最后一页
33 "totalPages": 3, // 总页数
34 "size": 2, // 每页数据集大小
35 "number": 1, // 当前页数
36 "sort": {
37 "sorted": true,
38 "unsorted": false,
39 "empty": false
40 },
41 "numberOfElements": 2, // content 内容的数量
42 "first": false, // 是否第一页
43 "empty": false // content 内容是否为空
44}
3.6、自定义查询 SQL
1 /**
2 * 根据用户昵称查询用户信息 等价于 findByNicknameLike
3 *
4 * @param nickname 用户昵称
5 * @param pageable 分页
6 * @return 用户信息
7 */
8 @Query("SELECT sysUser from SysUser sysUser where sysUser.nickname like %:nickname%")
9 Page<SysUser> searchByNickname(@Param("nickname") String nickname, Pageable pageable);
10
11 /**
12 * 根据用户昵称查询用户信息 和 searchByNickname 等价
13 *
14 * @param nickname 用户昵称
15 * @param pageable 分页
16 * @return 用户信息
17 */
18 Page<SysUser> findByNicknameLike(@Param("nickname") String nickname, Pageable pageable);
SysUserService 新增:
1 public Page<SysUser> searchByNickname(String nickname, Integer page, Integer size) {
2 // 根据 userId 排序
3 Pageable pageable = PageRequest.of(page, size, Sort.Direction.ASC, "userId");
4 return sysUserRepository.searchByNickname(nickname,pageable);
5 }
SysUserController 新增:
1 @PostMapping(value = "search")
2 public Page<SysUser> search(@RequestParam String nickname, @RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "3") Integer size) {
3 return sysUserService.searchByNickname(nickname, page, size);
4 }
重新运行,使用 Postname 方式访问 http://localhost:8080/api/user/search?nickname=瑞&page=0&size=5 ,可以看到返回如下所示的数据:

4、本章小结
本章主要介绍 Spring Data JPA 和如何使用,实战演示了 JPA 如何进行数据库的增加、删除、修改、更新操作,对于像 JPA 的多表查询,多数据源支持由于篇幅的原因不再一一展开,感兴趣的读者可以通过参考文档、扩展阅读和搜索引擎进一步深入了解。
在 JPA 中,多表查询有两种方式:
一种是 JPA 的级联查询,@OneToMany,@ManyToOne等注解在 @Entity 实体类 中指定多表关联规则;
另一种就是新建一个类,用于接收返回的结果集,然后通过 @Query 自定义查询 SQL;
扩展阅读:http://ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html
参考链接:https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/index.html
https://docs.spring.io/spring-data/jpa/docs/2.2.5.RELEASE/reference/html/
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
个人比较倾向于 MyBatis-Plus ,不过实际开发中 MyBatis 使用比较广泛。如果你有什么好的建议,欢迎点击下方留言告诉小先。
原创不易,感谢支持!
- MORE | 更多精彩文章 -
解锁新姿势:探讨复杂的 if-else 语句“优雅处理”的思路
如果你喜欢本文,
请长按二维码,关注 编程技术进阶

转发至朋友圈,是对我最大的支持。
好文章,我在看❤️




