一. 概述
Spring Data JPA 是 Java Persistence API (JPA) 规范的实现,底层是对Hibernate 5.x 操作数据库的封装,它简化了在java开发中使用 JPA 访问数据库的操作。
二. 使用 Spring Data Repositories
Spring Data repository 抽象的目的就是显著减少各种数据访问层实现技术的样板代码:
// Spring Data repository 是最基础的接口,用于获取repository管理的domain对象的类型以及id的类型
// 注意泛型的使用
public interface Repository<T, ID extends Serializable> {
}
CrudRepository
接口继承了Repository
接口,并提供了它所管理的domain对象的基本的 CRUD 方法,新版本中还添加了很多新的api,具体信息可以查询api:
// domain对象CRUD操作的泛型接口
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
// 保存一个实体对象 insert into...
<S extends T> S save(S entity);
// 根据id获取domain对象,select * from xxx where id = ?
Optional<T> findById(ID primaryKey);
// 查询表中所有记录,将返回的结果集封装到一个集合对象中(List),select * from xxx
Iterable<T> findAll();
// 统计表中记录数
long count();
// 删除记录
void delete(T entity);
// ... lookup api for more details
}
PagingAndSortingRepository
接口继承了CrudRepository
接口并添加了分页查询的方法:
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
// Pageable对象封装了分页查询的参数,用于SQL中limit子句的查询参数 page & size
Page<T> findAll(Pageable pageable);
}
JpaRepository
接口又继承了PagingAndSortingRepository
,开发中用户自定义repository可以直接继承该接口,同时JpaRepository
还继承了一个QueryByExampleExecutor
接口,该接口提供对QBE查询方法的支持,比较非主流。
根据 JPA 规范,项目中的领域对象要添加注解,用于建立domain和数据库表之间的静态映射关系:
@Entity
@Table(name = "tb_user")
public class User implements Serializable {
// id可以设置生成策略,这里选择通过id生成器手动设置
@Id
private String id;
@Column // 属性名称如果和表中字段名称一致可以不加注解
private String username;
@Column
private String password;
@Column
private String email
@Column
private Date birthday;
// ... constructor & methods
}
三. JPA Repositories
在使用 JPA 时Query对象的获取有两种方法,一种是手写SQL,另一种是从方法名称中派生(神奇)。从方法名称中派生的方式适用于查询条件不太复杂的情况,否者方法名称会变得很长很难看,还有在涉及关联查询的时候手写SQL更方便。
从查询方法名称中派生Query对象
public interface UserRepository extends JpaRepository<User, Long> {
// 创建query对象操作JPA criteria api,该对象对应如下的JPQL查询语句
// select from User u where u.emailAddress = ?1 and u.lastname = ?2
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
JPA使用“关键字”机制分割方法名称,建立多条件查询对象Query,该对象对应一条JQPL查询语句片段,关键字用于组合各种查询条件,JPA中关键字一共有二十多个,以下为常用的:
Keyword | Sample | JPQL snippet |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is , Equals | findByFirstname , findByFirstnameEquals | … where x.firstname = ?1 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
GrateThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
Like | findByFirstnameLike | … where x.firstname like ?1 |
方法名称中可以使用关键字top或者first来过滤出查询结果的前n条记录:
List<User> findFirst10ByLastname(String lastname);
@Query 的使用方法
Spring Data JPA 中可以在查询方法上添加@Query注解来定义一条 JPQL 查询语句:
public interface UserRepository extends JpaRepository<User, Long> {
// 注意模糊查询的使用
@Query("select from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
@Query 注解允许创建原生的SQL查询语句而不是JPQL,使用原生的SQL查询语句的好处是可以在SQL图形化界面中调试好了之后再copy到代码中,防止出错。返回的结果集被自动映射封装为方法返回值声明类型的对象中。
public interface UserRepository extends JpaRepository<User, Long> {
// 使用 nativeQuery = true 开启原生SQL语句查询
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
}
@Modifying 注解
在@Query注解中编写 JPQL 实现 UPDATE 和 DELETE 的时候必须在方法上加上@Modifying 注解,通知 Spring Data 这是删除或者更新操作,同时在Service层需要添加事务的支持@Transactional,注意JPQL是不支持INSERT操作的,代码如上。
四. Specifications
JPA 2 规范中引入了一个条件查询的api,这个 criteria api 可以使用编程的方式手动设置查询的条件(where子句)。在 Spring Data JPA 中使用 criteria api 只需要继承接口JpaSpecificationExecutor
,该接口中的查询方法接收一个Specification 类型的对象,这个对象中封装了where子句中的查询条件:
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor {
// 继承JpaSpecificationExecutor接口,实现条件查询,这个方法特别使用于页面多参数的
// 条件查询,需要在运行中动态构建SQL语句,类似于mybatis中动态SQL语句
}
构建动态查询条件对象:
/**
* 动态条件构建
* @param searchMap
* @return
*/
private Specification<Problem> createSpecification(Map searchMap) {
return new Specification<Problem>() {
@Nullable
@Override
public Predicate toPredicate(Root<Problem> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicateList = new ArrayList<Predicate>();
// ID
if (searchMap.get("id")!=null && !"".equals(searchMap.get("id"))) {
predicateList.add(cb.like(root.get("id").as(String.class), "%"+(String)searchMap.get("id")+"%"));
}
// 标题
if (searchMap.get("title")!=null && !"".equals(searchMap.get("title"))) {
predicateList.add(cb.like(root.get("title").as(String.class), "%"+(String)searchMap.get("title")+"%"));
}
// 内容
if (searchMap.get("content")!=null && !"".equals(searchMap.get("content"))) {
predicateList.add(cb.like(root.get("content").as(String.class), "%"+(String)searchMap.get("content")+"%"));
}
// 用户ID
if (searchMap.get("userid")!=null && !"".equals(searchMap.get("userid"))) {
predicateList.add(cb.like(root.get("userid").as(String.class), "%"+(String)searchMap.get("userid")+"%"));
}
// 昵称
if (searchMap.get("nickname")!=null && !"".equals(searchMap.get("nickname"))) {
predicateList.add(cb.like(root.get("nickname").as(String.class), "%"+(String)searchMap.get("nickname")+"%"));
}
// 是否解决
if (searchMap.get("solve")!=null && !"".equals(searchMap.get("solve"))) {
predicateList.add(cb.like(root.get("solve").as(String.class), "%"+(String)searchMap.get("solve")+"%"));
}
// 回复人昵称
if (searchMap.get("replyname")!=null && !"".equals(searchMap.get("replyname"))) {
predicateList.add(cb.like(root.get("replyname").as(String.class), "%"+(String)searchMap.get("replyname")+"%"));
}
return cb.and( predicateList.toArray(new Predicate[predicateList.size()]));
}
};
}
然后调用条件查询:
/**
* 条件查询+分页
*
* */
public Page<Problem> pageQuery(Map searchMap, int page, int size){
Specification<Problem> specification = createSpecification(searchMap);
Pageable pageable = PageRequest.of(page - 1, size);
return problemDao.findAll(specification, pageable);
}