Use JPA Specification EntityManager multi-condition complex dynamic query sub-query combination and or paging sort case

Insert picture description here

Preface

  • note: SQL's and has a higher priority than or, and the default is to check and first,such as:
 SELECT 3 > 1 or 3 > 4 and 3 > 4   //结果为true

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

Example 1 (Based on Specification)

Native sql is

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))

The corresponding jpa statement is

  • xx stands for dynamic parameter transmission
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;

Example 2: (Based on EntityManager)

Native 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

Suppose the relationship between E and M is

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

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

The corresponding jpa is:

  • First create the object to receive the result set, according to the select field of native SQL:
    such as:
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);

Example 3 (based on Entitymanger's native SQL)

  • If there are more complex needs, just splice the native SQL string directly

The comparison between native SQL and jpa implementation is not given here, it is to customize SQL strings based on dynamic conditions.

// 假设这里是动态拼接后的字符串
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+""));

to sum up

  • Life is short, and jpa is very troublesome to use when dynamically splicing SQL statements. It is better to choose mybatis...

Guess you like

Origin blog.csdn.net/weixin_41347419/article/details/105098113