使用JPA Specification EntityManager 多条件 复杂动态查询 子查询 组合 and or 分组 分页 排序 案例

使用JPA Specification EntityManager 多条件 复杂动态查询 子查询 组合 and or 分组 分页 排序 案例_第1张图片

前言

  • 注意: sql的and的优先级比or高,默认先查and的,比如:
 SELECT 3 > 1 or 3 > 4 and 3 > 4   //结果为true

等价于 SELECT 3 > 1 or	( 3 > 4 and 3 > 4)
	

实例1(基于Specification)

原生sql为

SELECT * from A A WHERE 
(a = 'xx' and b = 'xx' and c in (xx1,xx2)) 
and 
(d = '真(写死)' or ( d= '假(写死)' and 0 < select count(*) from B where e = 'xx' and f = A.a))

对应jpa语句为

  • xx代表动态传参数
Specification<T> specification = (root, cQ, cB)->{
			// 条件集合
            List<Predicate> predicates = new LinkedList<>();
              //动态生成条件a
            if (StringUtils.isNotBlank(a)){
                predicates.add(cB.equal(root.get("a"), xx));
            }
            // 动态生成条件b
            if (StringUtils.isNotBlank(b)){
                predicates.add(cB.equal(root.get("b"), xx));
            }
            // 动态生成范围查询条件c     c是一个数组  比如: string[] c
            if ( c != null && c.length>0){
                cB.In<String> in = cB.in(root.get("c"));
                for (String e : c) {
                    in.value(e);
                }
                predicates.add(in);
            }
            // 生成第一个条件d
            Predicate p3 = cB.equal(root.get("d"), "真(写死)");
            // 生成第二个条件d
            Predicate p1 =  cB.equal(root.get("d"), "假(写死)");
       
  			// 创建子查询 =========   即select count(*) from B where e = 'xx' and f = A.a)部分==============
            Subquery<Long> subquery = cQ.subquery(Long.class); //子查询select的输出类型 (这里是求count)
            Root<ExamStaffList> subRoot = subquery.from(B.class);  //  相当于 from B 表
  					
  			// 生成子查询条件1
            Predicate subP1 = cB.equal(subRoot.get("e"), xx);
  
  			// 生成子查询条件2
            Predicate subP2 = cB.equal(subRoot.get("f"), root.get("a"));
  					
  			// 将 subP1 和 subP1 用 and拼接生成新的条件,  并作为subquery的where条件
            subquery.where(cB.and(subPredicate1,subPredicate2));
            // 选择select出什么
            subquery.select(cB.count(subRoot.get("e"))); // 相当于 select count(e)

  			// 构建复合条件  0 < select count(*) from B where e = 'xx' and f = A.a)
            Predicate p4 = cB.greaterThan(subquery,(long)0);
            ==================================================================================================
         
			//  用 and 将条件集合所有条件用and拼接生成新的条件  
			Predicate tp1 = cB.and(predicates.toArray(new Predicate[0]));
			
			// 相当于拼接 (d = '真(写死)' or ( d= '假(写死)' and 0 < select count(*) from B where e = 'xx' and f = A.a))
  			Predicate tp2 =cB.or(p1,cB.and(p3,p4))
			
			// 最后用and拼接 tp1 和 tp2 后把最终条件返回, 等价于原sql最外层的那个 and 操作
			return cB.and(tp1,tp2);
        };
return specification;

实例2:(基于EntityManager)

原生sql:

SELECT m.name,m.type , count(m.name), (SELECT count(*) from E WHERE  name = e.name and username = xx) as rightCOunt
from E e INNER JOIN M m on e.username = m.name
WHERE m.type in ('网优') and m.name like "%2%" 
GROUP BY m.name
limit 0,10

假设E和M关系为

class E {
   private  Long id;
    
  private String username;
	
  @ManyToOne
  @JoinColumn(name = "name")
  private M m;
}

class M{
	private String name;
	private String type;
}

对应jpa为:

  • 首先创建出要接收结果集的对象,根据原生sql的select字段:
    如:
class ResultDto  {
	private String name;
	private String type;
	private Integer nameCount;
	private Integer roghtCount;
	//  注意: 一定要创建构造函数而且构造函数的参数数量要跟select的个数一样,不然结果集无法注入
	省略构造函数......
}




// 注入 EntityManager
  @PersistenceContext
 private EntityManager entityManager;

//
int page = 0;
int size = 10;

 CriteriaBuilder cB = entityManager.getCriteriaBuilder();
 // 泛型是设置要查询的结果集	
 CriteriaQuery<ResultDto> query = cB.createQuery(ResultDto.class);
 // 从哪张表查
 Root<E> root = query.from(E.class);
// 动态拼接where条件
List<Predicate> predicateList = new ArrayList<>();
if (StringUtils.isNotBlank(examName)){
    predicateList.add(cB.like(root.get("examMission").get("name").as(String.class),"%"+examName+"%"));
}
if (type != null && type.length > 0){
    CriteriaBuilder.In<Object> in = cB.in(root.get("examMission").get("type"));
    for (String e : type) {
        in.value(e);
    }
    predicateList.add(in);
}

if (predicateList.size() > 0)
      query.where(predicateList.toArray(new Predicate[0]));

// 创建子查询,对应原生sql中select的(SELECT count(*) from E WHERE  name = e.name and username = xx) as rightCOunt
Subquery<Long> subquery = query.subquery(Long.class);  // 子查询的结果集类型 ,这里是count(*)所以用long或者int都行
Root<E> subRoot =  subquery.from(E.class); // 子查询从哪张表查,这里是  from E

// 先把原生sql要select的字段列出来
Expression<String> nameField = root.get("m").get("name");
Expression<String> typeField = root.get("m").get("type").as(String.class);
Expression<Integer> nameCountFidld = cB.count(root).as(Integer.class);

Predicate subP1 = cB.equal(subRoot.get("m").get("name"), nameField); // 构建查询条件: name = e.name
Predicate subP2 = cB.equal(subRoot.get("username").as(String.class), xx); // 构建查询条件:  username = xx

// 拼接subquery的select和where条件
subquery.select(cB.count(subRoot).as(Long.class)).where(cB.and(subP1,subP2));

// 拼接各部分
query.multiselect(nameField,typeField,nameCountFidld,subquery.getSelection())
	 .groupBy(root.get("examMission").get("name"));;
	 

List<ResultDto> counts = entityManager.createQuery(query).getResultList();
int totalCount = counts.size();  // 分页返回的总记录数

// 执行查询
TypedQuery<ExamResultStaDto> typedQuery = entityManager.createQuery(query);
typedQuery.setFirstResult(page * size); // 设置分页limit a,b 的a
typedQuery.setMaxResults(size);  // 设置分页limit a,b 的b

// 获得结果集
List<ExamResultStaDto> resultList = typedQuery.getResultList();

// 构造分页对象并返回
PageRequest pageRequest = PageRequest.of(page, size);
return new PageImpl<>(resultList,pageRequest,totalCount);

实例3(基于Entitymanger的原生SQL)

  • 如果还有更复杂的需求,直接上拼接原生SQL字符串吧

这里就不给对比原生sql和jpa实现的对比了, 就是自定义根据条件动态条件拼接sql字符串

// 假设这里是动态拼接后的字符串
String sql = "select ......case when..... where ...... group by...order by limie ....";

// 一般需要分页就要计算总记录数,这个sql跟上面不同是只select count即查出总记录数
String countSql =  "select count(*) ....."; 

// 使用entityManager 创建原生sql查询
Query nativeQuery = entityManager.createNativeQuery(sql); 
Query countQuery = entityManager.createNativeQuery(countSql); 

// 获得结果, 返回的结果集会用二维集合保存,即数据库select出来的每一行记录用Object[]保存
List<Object[]> resultList = nativeQuery.getResultList();
Object totalSize = countQuery.getSingleResult(); // 总记录数

// 一般需要将 resultList 转换为对象存储 省略....... 
 假设resultList 被转换为  List<XXXX> xxlist 对象集合 

// 返回自定义Page对象
return new  PageImpl<XXXX>(xxlist,PageRequest.of(page,size),Long.value(totalSize+""));

总结

  • 人生苦短,在动态拼接sql语句时jpa使用起来异常麻烦,还是选择mybatis 好…

你可能感兴趣的:(javaEE)