Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!Spring Data JPA 让我们解脱了 DAO 层的操作,基本上所有 CRUD 都可以依赖于它来实现,在实际的工作工程中,推荐使用 Spring Data JPA + ORM(如:hibernate) 完成操作,这样在切换不同的 ORM 框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦
JPA 是一套规范,内部是有接口和抽象类组成的。hibernate 是一套成熟的 ORM 框架,而且 Hibernate 实现了 JPA 规范,所以也可以称 hibernate 为 JPA 的一种实现方式,我们使用 JPA 的 API 编程,意味着站在更高的角度上看待问题(面向接口编程)。SpringDataJPA是 Spring 提供的一套对 JPA 操作更加高级的封装,是在 JPA 规范下的专门用来进行数据持久化的解决方案。
☞ SpringBoot 整合 SpringDataJPA
☞ Spring 整合 SpringDataJPA
SpringDataJPA 致力于减少数据访问层的开发量,开发者唯一要做的就是声明持久层的接口,其他都交给SpringDataJPA来帮你完成。一般我们会继承 JpaRepository 和 JpaSpecificationExecutor 接口,我们可以使用接口中定义的方法进行查询。
♞ CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
♞ PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
♞ JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法,我们一般继承这个。
♞ JpaSpecificationExecutor: 不属于 Repository 体系,实现一组 JPA Criteria 查询相关的方法
☞ JpaSpecificationExecutor 中定义的方法
☞ 接口方法速查
使用 SpringDataJPA 提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用 @Query 注解,结合 JPQL 的语句方式完成查询。@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个 JPQL 查询语句即可。☞ JPQL 详细介绍
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description student repository 接口
*/
public interface StudentRepository extends JpaRepository<Student, Long> {
@Query("from Student")
public List<Student> findStudent();
}
此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询。可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作,注意:JPQL 不支持使用 INSERT 操作。方法的返回值是 int,表示更新语句所影响的行数。默认情况下,SpringDataJPA 的每个方法上有事务, 但都是一个只读事务,他们不能完成修改操作,因此需要在调用的地方必须加事务(添加 @Transactional 注解),没有事务不能正常执行。
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description student repository 接口
*/
public interface StudentRepository extends JpaRepository<Student, Long> {
//**************************************************************************************
// 第一种写法
//**************************************************************************************
@Modifying
// ? 后的数字表示第几个参数,顺序与参数一致可不写
@Query("update Student set sex = ?1, Name = ?2 where id = ?3")
public int updateStudent(Boolean sex, String Name, Long id);
//**************************************************************************************
// 第二种写法
//**************************************************************************************
@Modifying
@Query("update Student set sex = :sex, Name = :Name where id = :id")
public int updateStudent(@Param("sex") Boolean sex, @Param("Name") String Name, @Param("id") Long id);
}
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description student repository 接口
*/
public interface StudentRepository extends JpaRepository<Student, Long> {
// nativeQuery true: 使用 sql 查询; false: 使用 jpql 查询,默认就是 false
// 占位符与参数位置对应可不写 ? 后的数字
@Query(value = "select * from student where id = ?1 or sex = ?2", nativeQuery=true)
public List<Student> updateStudent(Long id, Boolean sex);
}
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照 SpringDataJPA 提供的方法命名规则定义方法的名称,就可以完成查询工作。SpringDataJPA 在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询。按照 SpringDataJPA 定义的规则,查询方法以 findBy 开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
☞ 支持的查询关键字
关键字 | 示例 | JPQL语句 |
---|---|---|
AND | findByLastnameAndFirstname | where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | where x.lastname = ?1 or x.firstname = ?2 |
Between | findByStartDateBetween | where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | where x.age < ?1 |
GreaterThan | findByAgeGreaterThan | 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) |
☞ 示例
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description student repository 接口
*/
public interface StudentRepository extends JpaRepository<Student, Long> {
public List<Student> findByAge(int age);
}
☞ 查询方法流程解析
假如创建如下的查询:findByClassUserAge(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为 Student,先判断 ClassUserAge(根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性则从右往左截取第一个大写字母开头的字符串(此处为Age),然后检查剩下的字符串(classUser)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则继续从右往左截取;假设 class 为查询实体的一个属性,则先判断 class 是否有 userAge 属性,有则按照 Student.class.userAge
查询,没有就会报错。
可能会存在一种特殊情况,比如 Student 包含一个 class 的属性,也有一个 classUser 属性,此时会存在混淆。可以明确在属性之间加上 _
以显式表达意图,比如 findByClass_UserAge()
或者 findByClassUser_Age
。还有使用一些特殊的参数来完成某些操作,比如说分页操作:Page
;排序操作:List
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在 SpringDataJPA 中可以通过 JpaSpecificationExecutor 接口查询。相比 JPQL 其优势是类型安全,更加的面向对象。 JpaSpecificationExecutor 这个接口基本是围绕着 Specification 接口来定义的。我们可以简单的理解为,Specification 构造的就是查询条件。Specification 接口中只定义了如下一个方法:public Predicate toPredicate(Root
参数说明:
♞ root
:Root 接口,代表查询的根对象,可以通过 root 获取实体中的属性;
♞ query
:代表一个顶层查询对象,用来自定义查询;
♞ cb
:用来构建查询,此对象里有很多条件方法。
我们写的 Repository 接口需要继承 JpaSpecificationExecutor 之后才能进行动态查询,如下所示:
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description student repository 接口
*/
public interface StudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> {}
☞ 基于 Specifications 完成条件查询
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description 测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest()
public class Demo {
@Autowired
private StudentRepository studentRepository;
@Test
public void test() {
// 使用匿名内部类的方式,创建一个 Specification 的实现类,并实现 toPredicate 方法
Specification<Student> spec = new Specification<Student>() {
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// cb: 构建查询,添加查询方式 like:模糊匹配
// root:从实体 Student 对象中按照 Name 属性进行查询
return cb.like(root.get("Name").as(String.class), "张");
}
};
List<Student> one = studentRepository.findAll(spec);
System.out.println(one);
}
}
☞ 基于 Specifications 的分页查询
对于 SpringDataJPA 中的分页查询,是其内部自动实现的封装过程,返回的是一个 SpringDataJPA 提供的 pageBean 对象。其中的方法有 int getTotalPages()
:获取总页数;long getTotalElements()
:获取总记录数;List
:获取列表数据
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/7/30
* @description 测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest()
public class Demo {
@Autowired
private StudentRepository studentRepository;
@Test
public void test() {
// PageRequest 实现了 Pageable接口,调用静态方法 of 第一个参数:页码(从0开始); 第二个参数:每页查询条数
Pageable pageable = PageRequest.of(0, 1);
// 分页查询
Page<Student> page = studentRepository.findAll(pageable);
System.out.println(page.getContent());
}
}
☞ CriteriaBuilder 方法对应关系
方法名称 | SQL 对应关系 |
---|---|
equle | filed = value |
gt(greaterThan) | filed > value |
lt(lessThan) | filed < value |
ge(greaterThanOrEqualTo ) | filed >= value |
le(lessThanOrEqualTo) | filed <= value |
notEqule | filed != value |
like | filed like value |
notLike | filed not like value |