Spring boot JPA的复杂查询
一、 JpaSpecificationExecutor 接口查询方式
1、JpaSpecificationExecutor接口
JPA 提供动态接口JpaSpecificationExecutor,利用类型检查的方式,利用Specification进行复杂的条件查询,比自己写 SQL 更加便捷和安全。
JpaSpecificationExecutor 源码
public interface JpaSpecificationExecutor<T> {
/**
* Returns a single entity matching the given {@link Specification}.
*
* @param spec
* @return
*/
T findOne(Specification<T> spec);
/**
* Returns all entities matching the given {@link Specification}.
*
* @param spec
* @return
*/
List<T> findAll(Specification<T> spec);
/**
* Returns a {@link Page} of entities matching the given {@link Specification}.
*
* @param spec
* @param pageable
* @return
*/
Page<T> findAll(Specification<T> spec, Pageable pageable);
/**
* Returns all entities matching the given {@link Specification} and {@link Sort}.
*
* @param spec
* @param sort
* @return
*/
List<T> findAll(Specification<T> spec, Sort sort);
/**
* Returns the number of instances that the given {@link Specification} will return.
*
* @param spec the {@link Specification} to count instances for
* @return the number of instances
*/
long count(Specification<T> spec);
}
2、Specification 说明
Specification是我们传入进去的查询参数,是一个接口,并且只有一个方法。
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
3、toPredicate 函数参数说明:
- Root: 是查询结果的一个实体对象,也就是查询结果返回的主要对象。 root = query.from(User.class);
- CriteriaQuery:是JPA标准,主要是构建查询条件的,里面的方法都是各种查询方式:distinct、select、where、groupby、having、orderby这些方法。 // query = cb.createQuery(User.class);//
- CriteriaBuilder:主要用来生成Predicate接口 。其中包含between、gt(大于)、lt(小于)、not(非)等等操作。
4、JpaSpecificationExecutor 内部查询原理
第一步,使用EntityManager 获得 CriteriaBuilder 查询工厂;
第二步,使用 CriteriaBuilder 获得查询类 CriteriaQuery;
第三步,组装查询条件数组 List<Predicate> ;
第四步,使用CriteriaQuery where 拼接 or 或and + 条件数组;
第五步,使用 EntityManager 进行查询;
重点说一下:
JPA标准中Hibernate的两个实现方法:
org.hibernate.ejb.criteria.CriteriaBuilderImpl.and(Predicate...)
org.hibernate.ejb.criteria.CriteriaBuilderImpl.or(Predicate...)
这两个方法都有一个关键的接口: Predicate(javax.persistence.criteria.Predicate)
这个接口同为Expression的子接口,作为关联各种Predicate的核心操作接口:
and:是将各个条件作为and来拼接,进行查询。
or:是将各条件作为or来拼接,进行查询。
代码演示查询流程:
@PersistenceContext private EntityManager em; @Override public List<Even> list(Even even) { //查询工厂 CriteriaBuilder cb = em.getCriteriaBuilder(); //查询类 CriteriaQuery<Even> query = cb.createQuery(Even.class); //查询条件 List<Predicate> predicates = new LinkedList<>(); //查询条件设置 predicates.add(cb.equal("id", even.getId())); predicates.add(cb.like("eventTitle", even.getEventTitle())); //拼接where查询 query.where(cb.or(predicates.toArray(new Predicate[predicates.size()]))); //用JPA 2.0的TypedQuery进行查询 TypedQuery<Even> typedQuery = em.createQuery(query); return typedQuery.getResultList(); }
5 、使用步骤:
(1)、对应的Repository需要实现JpaSpecificationExecutor接口
public interface UserRepository extends JpaRepository<User, Long> ,
JpaSpecificationExecutor<User>{
(2)、在业务层编写你具体的复杂查询语句
新建一个条件构造器,重写oPredicate方法:
@Override
public Page<泛型> findRecordList(int couponDetailId, int pageNum, int pageSize, String startTime, String endTime) {
try {
//排序规则和分页
Sort sort = new Sort(new Sort.Order(Sort.Direction.DESC, "createTime"));
PageRequest pageRequest = new PageRequest(pageNum - 1, pageSize, sort);
Specification specification = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
//增加筛选条件
Predicate predicate = cb.conjunction();
predicate.getExpressions().add(cb.equal(root.get("cardId"), couponDetailId));
//起始日期
if (startTime != null && !startTime.trim().equals("")) {
predicate.getExpressions().add(cb.greaterThanOrEqualTo(root.get("createTime").as(String.class), startTime));
}
//结束日期
if (endTime != null && !endTime.trim().equals("")) {
predicate.getExpressions().add(cb.lessThanOrEqualTo(root.get("createTime").as(String.class), endTime));
}
return predicate;
}
};
Page all = discountCouponRecordDao.findAll(specification, pageRequest);
return all;
}
二、自定义SQL
JPA同样允许自己写SQL操作记录。
实例:
接口:
@Query(value = "select * from order_info where user_name = ?1 and user_address = ?2", nativeQuery = true)
List<Order> selectByNameAndAddress(String name, String address);
@Modifying
@Transactional
@Query(value = "update order_info set user_address = ?1 where order_id = ?2", nativeQuery = true)
void updateAddressByOrderId(String address, Integer orderId);
注意: 1). 自定义SQL可以随便命名方法名
2).需要用@Query
注解,如果是更新删除操作还需要有 @Modifying
和@Transactional
注解
3). SQL里的?表示的占位符,编译和执行的时候,取值是取接口的入参,?后的数字是第几个参数,从0开始