一.Spring Data JPA简介
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,底层使用了 Hibernate 的 JPA 技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能接口,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
由于微服务系统的广泛应用,服务粒度逐渐细化,多表关联查询的场景一定程度减少。单表查询和单表的数据操作正是JPA的优势。我们本节就为大家介绍如何在Spring Boot中使用JPA。
二.将Spring Data JPA集成到Spring Boot
2.1 引入maven依赖包,包括Spring Data JPA和Mysql的驱动
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
2.2 配置数据库连接和jpa的相关信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: validate
database: mysql
show-sql: true
- spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 。Hibernate 创建数据库表的时候,默认使用的数据库存储引擎是 MyISAM ,这个参数作用是在建表的时候,将存储引擎切换为 InnoDB 。
- spring.jpa.show-sql=true 在日志中打印出执行的 SQL 语句信息。
- spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置属性,其主要作用是:自动根据实体类的定义创建、更新、验证数据库表结构。所以这个参数是一个比较危险的参数,使用的时候一定要注意。该参数的几种配置如下:
- create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
- create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
- update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
- validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
三.Spring Data JPA核心用法
3.1 开发实体Model类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name="article")
public class Article {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(nullable = false,length = 32)
private String author;
@Column(nullable = false, unique = true,length = 32)
private String title;
@Column(length = 512)
private String content;
private Date createTime;
}
- @Entity 必选注解,表示这个类是一个实体类,接受JPA控制管理,对应数据库中的一个表
- @Table 可选注解,指定这个类对应数据库中的表名。如果这个类名和数据库表名符合驼峰及下划线规则,可以省略这个注解。如FlowType类名对应表名flow_type。
- @Id 指定这个字段为表的主键
- @GeneratedValue(strategy=GenerationType.IDENTITY) 指定主键的生成方式,一般主键为自增的话,就采用GenerationType.IDENTITY的生成方式
- @Column 注解针对一个字段,对应表中的一列。nullable = false表示数据库字段不能为空, unique = true表示数据库字段不能有重复值,length = 32表示数据库字段最大程度为32.
3.2 开发数据操作接口
我们只需创建类似XxxRepository接口继承JpaRepository
public interface ArticleRepository extends JpaRepository {
}
3.3 关键字查询接口
除了JpaRepository为我们提供的增删改查的方法。我们还可以自定义方法,使用起来非常简单,甚至可以说是强大。把下面的方法名放到ArticleRepository 里面,它就自动为我们实现了通过author字段查找article表的所有数据。也就是说,我们使用了find(查找)关键字,JPA就自动将方法名为我们解析成数据库SQL操作,太智能了。
public interface ArticleRepository extends JpaRepository {
//注意这个方法的名称,jPA会根据方法名自动生成SQL执行
Article findByAuthor(String author);
}
Article findByAuthor(String author);
等同于SELECT * FROM article WHERE author = ?
,其他具体的关键字,使用方法和生产成 SQL 如下表所示:
关键字 | 接口函数例子 | JPQL 片段 |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。
四.SpringDataJPA实现分页排序
如果一次性加载成千上万的列表数据,在网页上显示将十分的耗时,用户体验不好。所以处理较大数据查询结果展现的时候,分页查询是必不可少的。分页查询必然伴随着一定的排序规则,否则分页数据的状态很难控制,导致用户可能在不同的页看到同一条数据。本文的主要内容就是给大家介绍一下,如何使用Spring Data JPA进行分页与排序。
4.1 分页参数Page
Pageable
是Spring定义的接口,用于分页参数的传递,我们可以使用如下代码进行分页操作:查询第一页(从0开始)的数据,每页10条数据。
Pageable pageable = PageRequest.of(0, 10); //第一页
//Pageable pageable = PageRequest.of(1, 10); //第二页
//Pageable pageable = PageRequest.of(2, 10); // 第三页
//数据库操作获取查询结果
Page articlePage = articleRepository.findAll(pageable);
//将查询结果转换为List
List articleList = articlePage.getContent();
findAll方法以Page类的对象作为响应,如果我们想获取查询结果List,可以使用getContent()方法。但是不建议这样进行转换,因为前端展示一个分页列表,不仅需要数据,而且还需要一些分页信息。如:当前第几页,每页多少条,总共多少页,总共多少条。这些信息在Page(articlePage)对象里面均可以获取到。
4.2 排序参数Sort
Spring Data JPA提供了一个Sort
对象,用以提供一种排序机制。
articleRepository.findAll(Sort.by("createTime"));
articleRepository.findAll(Sort.by("author").ascending().and(Sort.by("createTime").descending()));
- 第一个findAll方法是按照createTime的升序进行排序
- 第一个findAll方法是按照author的升序排序,再按照createTime的降序进行排序
分页和排序在一起:
Pageable pageable = PageRequest.of(0, 10,Sort.by("createTime"));
4.3 分页结果Slice与Page
Slice 和Page 都是Spring Data JPA的数据响应接口,其中 Page 是 Slice的子接口,它们都用于保存和返回数据。
Slice的一些重要方法:
public interface Slice extends Streamable {
List getContent(); //获取切片的内容
Pageable getPageable(); //当前切片的分页信息
boolean hasContent(); //是否有查询结果?
boolean isFirst(); //是否是第一个切片
boolean isLast(); //是否是最后一个切片
Pageable nextPageable(); // 下一个切片的分页信息
Pageable previousPageable(); // 上一个切片的分页信息
}
Page的一些重要方法:
public interface Page extends Slice {
//总页数
int getTotalPages();
//总数据条数
long getTotalElements();
}
通过这两个接口的函数定义可以看出,Slice只关心是不是存在下一个分片(分页),不会去数据库count计算总条数、总页数。所以比较适合大数据量列表的的鼠标或手指滑屏操作,不关心总共有多少页,只关心有没有下一页。Page比较适合传统应用中的table开发,需要知道总页数和总条数。
五.测试JPA关键字查询和分页查询
5.1 完善3.3节ArticleRepository接口,增加分页查询:
public interface ArticleRepository extends JpaRepository {
//注意这个方法的名称,jPA会根据方法名自动生成SQL执行
Article findByAuthor(String author);
//根据author字段查询article表数据,传入Pageable分页参数,不需要自己写SQL
Page findByAuthor(String author, Pageable pageable);
}
由于JpaRepository
接口继承自PagingAndSortingRepository
接口,所以默认已实现分页和排序查询。
5.2编写并运行测试用例
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class JPATest {
@Resource
private ArticleRepository articleRepository;
@Test
public void testFindByAuthor() {
Article article = articleRepository.findByAuthor("Jerry");
log.info(article.toString());
}
@Test
public void testPageable() {
// 查询第一页,每页10条数据,按照createTime字段降序排列
Pageable pageable = PageRequest.of(0, 10,Sort.by("createTime").descending());
Page page = articleRepository.findByAuthor("William", pageable);
for (Article article : page.getContent()) {
log.info(article.toString());
}
}
}