第十三章:SpringBoot实战SpringDataJPA

649 阅读12分钟

SpringDataJPASpring Data的一个子项目,通过提供基于JPARepository极大的减少了JPA作为数据访问方案的代码量,你仅仅需要编写一个接口集成下SpringDataJPA内部定义的接口即可完成简单的CRUD操作,理论的东西不做多解释,下面我们开始讲解SpringBoot

构建项目

我们使用IntelliJ IDEA工具构建一个SpringBoot项目,预先导入Web、MySQL、JPA依赖,我们简单使用一个RestController来实现JPA的配置,之前也有讲解JPA的简单使用,今天详细的讲解下具体的细节性的内容,项目结构如下图1所示:


图1

使用Druid数据源

我们使用之前章节的配置,加入Druid数据源配置到我们的项目中,复制第四章:使用Druid作为SpringBoot项目数据源(添加监控)项目中的application.yml配置文件到我们项目resources下,并且修改pom.xml添加Druid数据源依赖,如下图2所示:


图2

application.yml配置文件内容如下图3所示(具体代码请到码云下载地址:git.oschina.net/jnyqy/lesso…):


图3

使用JpaRepository

我们在配置使用JpaRepository之前需要对应我们的测试表添加实体映射,为了本章的方便我们直接使用第四章:使用Druid作为SpringBoot项目数据源(添加监控)内的表结构以及实体,sql文件在第四章项目源码的resource目录下,可以下载后自行加载到本地数据库中,表结构如下图4所示:


图4

根据表结构创建对应的实体映射,简单点,我们使用单表操作,SpringDataJPAHibernate的语法一致内部都是使用了JPA的实现。映射实体代码如下图5所示:


图5

上述图5的getter/setter方法我没有贴出来,自行工具构建就可以了。我们的准备做好了,下面我们创建UserJPA接口,上图5我已经创建到了jpa目录内,创建完成后打开添加继承自JpaRepositoryJpaRepository需要泛型接口参数,第一个参数是实体,第二则是主键的类型,UserJPA代码如下图6所示:


图6

数据访问的接口就算是实现了,我们继承的JpaRepository接口内有又继承了PagingAndSortingRepository接口以及QueryByExampleExecutor接口,这两个接口是用来干什么的?而PagingAndSortingRepository接口内部又有一个继承自CrudRepository接口。如果对架构有点了解的朋友应该都知道,这样设计得好处。

CrudRepository

该接口内包含了最简单的CRUD也就是Create、Read、Update、Delete方法,当然还有count、exists方法,如下图7所示:


图7

如果自行定义的JPA继承了该接口就会拥有CrudRepository接口内的所有方法实现。

PagingAndSortingRepository

该接口继承自CrudRepository接口,包含了最基本的CRUD方法的实现,该接口内部添加了两个方法,如下图8所示:


图8

顾名思义,看到接口名称就可以联想了,这个方法就是为了分页而设计的,当然不仅仅是分页还有排序方法。

QueryByExampleExecutor

我们的JpaRepository接口继承了该接口,这个接口提供条件查询,复杂查询方法,可以通过Example方式进行查询数据,源码如下图9所示:


图9

后面我们会讲到QueryDSL,到那时你就会知道SpringDataJPA提供的复杂条件查询并不是最好的选择。

JpaRepository

我们自定义的接口继承了它,也就是说我们的UserJPA拥有了JpaRepository接口及父类接口的所有方法实现,所以我们并不需要添加任何数据操作代码就可以完成数据操作,JpaRepository接口对条件查询以及保存集合数据添加了对应的方法,代码如下图10所示:


图10

具体每个方法是用来做什么的,根据名称就可以看到,这里就不做一一的讲解了。下面我们需要测试我们创建的UserJPA是否可以完成我们上述说的数据操作。

创建测试控制器

我们直接在controller包下创建一个UserController控制器,添加@RestController注解支持,我们因为方便这里就不编写Service层的代码实现了,直接在Controller内注入UserJPA,代码如下图11所示:


图11

我们在UserController内添加了JpaRepository内部实现的findAll方法,用来查询全部用户数据,下面我们启动项目测试。

初尝试运行测试

当你使用SpringBootApplication方式运行项目时控制台会输出项目运行失败的日志提示,这里我们需要注释掉spring-boot-starter-tomcat依赖的scope属性就可以了。

查询数据

尝试访问用户列表地址:127.0.0.1:8080/list,可以看到页面输出了一条数据,这条数据是我事先在数据库中手动添加的,如下图12所示:


图12

添加数据

我们编写简单的添加数据方法在UserController内,代码如下图13所示:


图13

我们在add方法内创建了一个UserEntity对象并对所有的字段都赋值。

注意:SpringDataJPA内有个save方法,这个方法不仅仅是用来添加数据使用,当我们传入主键的值时则是根据主键的值完成更新数据操作。

我们重启下项目,尝试访问127.0.0.1:8080/add地址,界面输出内容如下图14所示:


图14

界面给了我们数据添加成功的提示,我们访问list地址验证是否已经添加成功,如下图15所示:


图15

可以看到,数据已经通过add方法添加到数据库内。由于更新的操作与添加一致这里就不做讲解了,你只需要传入主键的值即可。

删除数据

我们简单实现删除一条数据,在UserController内添加delete方法,方法接受一个主键参数,如下图16所示:


图16

当我们访问/delete时传入userId参数就可以删除对应的数据,下面我们重启下项目,访问127.0.0.1:8080/delete?userId=5,界面输入内容如下图17所示:


图17

数据已经成功的完成了删除操作。

上面的操作一切都是SpringDataJPA为我们自动完成的,到目前为止我们并没有编写一句SQL,那么SpringDataJPA是否支持自定义SQL语句呢?答案必须是肯定的!因为它是这个的强大!

@Query注解自定义SQL

SpringDataJPA内部有两种方式可以实现自定义SQL功能,我们先来讲述使用注解的方式,后期在SpringDataJPA核心技术专题内再详细的讲解使用EntityManager是如何完成自定义SQL、调用存储过程、视图等等操作的。下面我们打开UserJPA接口,添加自定义查询年龄大于20的数据,如下图18所示:


图18

@Query是用来配置自定义SQL的注解,后面参数nativeQuery = true才是表明了使用原生的sql,如果不配置,默认是false,则使用HQL查询方式。我们在UserController内添加方法/age,测试我们的自定义SQL是否有效,代码如下图19所示:


图19

重启下项目,访问127.0.0.1:8080/age,效果如下图20所示:


图20

@Query配合@Modifying

从名字上可以看到我们的@Query注解好像只是用来查询的,但是如果配合@Modifying注解一共使用,则可以完成数据的删除、添加、更新操作。下面我们来测试下自定义SQL完成删除数据的操作,我根据名字、密码字段共同删除一个数据,接口代码如下图21所示:


图21

我们再来编写UserController添加对应的方法调用deleteQuery接口方法,如下图22所示:


图22

重启下项目,访问地址:127.0.0.1:8080/deleteWhere,界面输出内容如下图23所示:


图23

界面竟然出现了异常,这是怎么回事呢?可以看到抛出的异常TranscationRequiredException,意思就是你当前的操作给你抛出了需要事务异常,SpringDataJPA自定义SQL时需要在对应的接口或者调用接口的地方添加事务注解@Transactional,来开启事务自动化管理。下面我们在UserJPA内添加@Transactional注解,重启项目再来访问刚才的地址,效果如下图24所示:


图24

界面已经给我提示了删除成功,我们查看下控制台看打印的SQL是否是我们自定义的,如下图25所示:


图25

我们自定义的SQL被成功的打印了,自定义SQL完成添加,更新操作时跟删除一致,都需要添加@Query以及@Modifying注解配合使用。

自定义BaseRepository

项目在正常情况下不仅仅只继承一个JpaRepository接口,下一章我们整合SpringDataJPA跟QueryDSL时就需要添加多个接口继承了,那么我们业务数据接口每一个都去继承几个相同的接口?答案肯定是 NO,当然多个继承也是可以的,不过对于系统设计还有代码复用性来说并不是最好的选择!

我们创建一个包名叫做base,在包内添加一个BaseRepository接口,并且接口继承我们的JpaRepository,代码如下图26所示:


图26

又出现了一个新的注解,@NoRepositoryBean,这个注解是用来干什么的呢?

Spring开源程序猿在命名规则上应该是比较严格的,从名字上我们几乎就可以判断出用途,这个注解如果配置在继承了JpaRepository接口以及其他SpringDataJpa内部的接口的子接口时,子接口不被作为一个Repository创建代理实现类。

我们创建的业务数据接口直接继承BaseRepository就行了,继承的子接口会拥有JpaRepository所有方法实现。

分页查询

分页对于大型系统来说肯定是必不可少的,那么我们在SpringDataJpa内是如何使用分页来完成查询的呢?

一般情况我们会创建一个BaseEntity,在BaseEntity内添加几个字段:排序列,排序方式,当前页码,每页条数等,下面我们也来创建这么一个父类,代码如下图27所示:


图27

我们修改UserEntity继承BaseEntity,然后在数据库内添加上几条测试数据,我们每次页面的数量就不用20了,我们在创建查询条件时修改成2条。

我们打开UserController添加cutPage方法,用于作为分页查询入口,(注意:文章的讲解都没有添加Service层所以所有的业务逻辑都在Controller内处理的,正式项目请不要这样编写。)我们在cutPage方法内添加对应的分页逻辑,如下图28所示:


图28

接下来我们重启下项目,访问地址:127.0.0.1:8080/cutpage?page=1,查看界面输出效果如下图29所示:


图29

我们再来看下控制台的输出,如下图30所示:


图30

可以看到控制台给我们打印了两条SQL,第一条是分页查询的SQL,第二条是查询表内总数量的SQL。SpringDataJPA内部对数量做出了封装,你可以通过Page对象也就是PagingAndSortingRepository接口内的findAll(PageRequest request)方法的返回值类型中获取到总条数、总页数。

数据排序

我们上面在BaseEntity内添加了排序的字段以及排序方式,我们重新编辑下cutPage方法,修改pageRequest创建方式,添加Sort对象到PageRequest对象内,就可以实现排序数据。如下图31所示:


图31

上图31可以看到我们修改了排序字段我们使用了默认的id,(注意:这里的排序字段不是数据库内的字段名而是实体内的属性名)以及排序方式改成了倒序,SpringDataJPA对排序方式添加了一个枚举类型,创建Sort对象时也需要枚举对象,因为我们BaseEntity配置的是字符串所以上面多了一步判断排序方式返回枚举对象。

重启下项目,再来访问分页路径,界面输出效果如下图32所示:


图32

可以看到数据已经是倒序方式展示了,控制台的日志输出也对应的添加了order by语句,如下图33所示:


图33

总结

综上所述本章的内容已经讲解完了,本章的内容比较多昨天完成没有编写完,还请见谅。本章主要讲解了SpringBoot项目中使用SpringDataJPA的基本操作,包括了:CURD、分页、排序、自定义SQL、定义BaseRepository、事务处理等。本章内容并不是SpringDataJPA全面内容,后续我会在SpringDataJPA核心技术专题详细讲解。

本章内容已经更新到码云:

SpringBoot配套源码地址:gitee.com/hengboy/spr…

SpringCloud配套源码地址:gitee.com/hengboy/spr…

SpringBoot相关系列文章请访问:目录:SpringBoot学习目录

QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录

SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录

SpringBoot相关文章请访问:目录:SpringBoot学习目录,感谢阅读!

欢迎加入QQ技术交流群,共同进步。


QQ技术交流群