简介
笔者在前面的文章中提及到过 spring-data-jpa ,什么是 jpa ,什么是 spring-data-jpa 。jpa 是 Java Persistence API的简称,是 javaEE 的 orm 规范,spring-data-jpa 是依照 jap 规范的关于数据持久层的一系列接口,在 spring 中是这样介绍 data-jpa 的:spring-data-jpa 是 spring 数据持久层的一部分,能够更轻松方便实现基于 JPA 的库,更容易构建出 spring 应用。说白了,spring-data-jpa 让我们更方便地操作持久层。
使用
1.使用 data-jpa 主要是要使用 spring 提供的一系列 Repository 接口。
// 接口族
Repository<T, ID extends Serializable>
CrudRepository<T, ID extends Serializable>
PagingAndSortingRepository<T, ID extends Serializable>
JpaRepository<T, ID extends Serializable>
// 用于动态查询
JpaSpecificationExecutor<T>
2.先来看看这些接口声明了那些方法
空接口
public interface Repository<T, ID extends Serializable> {}
CrudRepository 声明了基本的 CRUD 方法
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
// 保存或更新
<S extends T> S save(S var1);
// 批量保存或更新
<S extends T> Iterable<S> save(Iterable<S> var1);
// 根据主键查找
T findOne(ID var1);
// 根据主键判断是否存在
boolean exists(ID var1);
// 查找所有符合条件的
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> var1);
// 统计总量
long count();
// 根据主键或其他条件删除
void delete(ID var1);
void delete(T var1);
void delete(Iterable<? extends T> var1);
void deleteAll();
}
PagingAndSortingRepository 接口继承 CrudRepository ,主要增加了分页和排序功能
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
// 排序查找
Iterable<T> findAll(Sort var1);
// 分页查找
Page<T> findAll(Pageable var1);
}
JpaRepository 接口继承 PagingAndSortingRepository 接口,扩展了一些返回参数类型为 List 的方法
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAll(Iterable<ID> var1);
<S extends T> List<S> save(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
JpaSpecificationExecutor 是一个高级动态查询接口,不是 Repository 系列中的子接口
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> var1);
List<T> findAll(Specification<T> var1);
Page<T> findAll(Specification<T> var1, Pageable var2);
List<T> findAll(Specification<T> var1, Sort var2);
long count(Specification<T> var1);
}
3.如何使用这些接口,你仅需要继承这些接口即可,具体实现由 data-jpa 库帮你实现,和使用 mybatis 差不过。举个栗子:
@Repository
public interface UserRepository extends JpaRepository<User,Long> {}
这样就可以通过 spring 自动注入到类中使用了,你可以调用上述接口中声明的任意方法完成持久化操作。一般上述方法就可以满足一般的需求,但是我们可不可以扩展这些接口呢,答案是可以的,不过在扩展接口时的方法命名需要遵循 data-jpa 的规范约束,例如:
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
// 根据 userId 查找 user
User findByUserId(String userId);
// 根据 username 查找 user
User findByUsername(String username);
User findByUsernameAndAge(String username,int age);
}
基本是使用:findBy,deleteBy,countBy,existsBy,orderBy,between ,distinct,and,or…,实体类属性名 等这些关键字拼接起来。当这些都不满足需求时,就可以通过使用 @Query 注解自己拼 hql 了。例如:
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
User findByUserId(String userId);
User findUserByUsername(String username);
@Query("select u from User u join u.department dept where dept.departmentId = :deptId")
Page<User> findByDeptId(@Param("deptId") String deptId, Pageable pageable);
@Query("select u from User u join u.roles role where role.roleId = :roleId")
Page<User> findByRoleId(@Param("roleId") Long roleId, Pageable pageable);
@Query("select count(u.userId) from User u join u.department dept where dept.departmentId = :deptId")
int countByDeptId(@Param("deptId") String deptId);
/*注意update需要modifying*/
@Modifying
@Query("update User u set u.flag = :flag where u.userId = :userId")
int updateUserFlag(@Param("flag") Integer flag, @Param("userId") String userId);
}
一般在做联表查询是就需要使用 hql 了,hql 也是面向对象的,实体类等价于对应的表,通过打点访问类中的关联实体类属性即可做到联表。此外,很多时候我们需要动态查询,以上方案 sql 都是固定的,怎样才能动态拼接 sql 实现动态查询呢,这就要使用到 JpaSpecificationExecutor 了。再来看看这个接口的方法:
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> var1);
List<T> findAll(Specification<T> var1);
Page<T> findAll(Specification<T> var1, Pageable var2);
List<T> findAll(Specification<T> var1, Sort var2);
long count(Specification<T> var1);
}
在声明的方法中有三个参数:Sort,Pageable ,Specification ,关键是最后的这个。Specification 就是动态拼接的 sql 的数据结构,在使用动态查询,我们需要做的是如何创建 Specification 对象。
public interface Specification<T> {
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}
Specification 也是一个接口,其中仅有一个方法 toPredicate , 实际上 Predicate 才真正是动态拼接 sql 的数据结构。在组装 sql 时,使用的是 root,criteriaQuery,criteriaBuilder 这三个对象。例如:
@Repository
public interface UserRepository extends JpaRepository<User,Long>,JpaSpecificationExecutor<User> {
public static class Specs{
/**
* 根据查询条件构造动态查询sql
* username
* userId
* deptId
* roleId
*
*root : 可以获取实体类中的属性名(等价数据库中的表,列)
*criteriaQuery:构建子查询(等价于使用 orderBy,groupBy 等)
*criteriaBuilder:构建 predicate 对象,构建 sql 片段
*
*/
public static Specification<User> newQueryParams(Map<String,String> paramsMap){
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
// 创建 Predicate
Predicate predicate = criteriaBuilder.conjunction();
// 组装条件
if(StringUtils.isNotBlank(paramsMap.get("username"))){
predicate.getExpressions().add(criteriaBuilder.like(root.<String>get("username")
,"%"+ paramsMap.get("username") + "%"));
}
if(StringUtils.isNotBlank(paramsMap.get("userId"))){
predicate.getExpressions().add(criteriaBuilder.equal(root.<String>get("userId"),paramsMap.get("userId")));
}
if(StringUtils.isNotBlank(paramsMap.get("deptId"))){
// 注意联表的问题
predicate.getExpressions().add(criteriaBuilder.equal(root.get("department").get("departmentId"),paramsMap.get("deptId")));
}
if(StringUtils.isNotBlank(paramsMap.get("roleId"))){
predicate.getExpressions().add(criteriaBuilder.equal(root.join("roles", JoinType.LEFT).get("roleId").as(String.class), paramsMap.get("roleId")));
}
return predicate;
}
};
}
}
}
PS: 在 hibernate 中的级联注解中,一般使用 CascadeType.ALL,如@OneToMany(cascade={CascadeType.ALL}) , 但是往往不需要级联删除,这是可以使用 hibernate 提供的注解:@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})