JPA exists查询,使用Specification或原生Sql两种方式

JPA的Specification方式查询单表是十分方便的,但是如果需要使用多表连接查询时,项目里没有找到可供参考的样本。

需求:包含多查询条件(条件为空时不参与查询),查询语句包含了exists从表,且需要分页的典型页面查询场景。

在数据库中编写好SQL语句,样例如下

select * from resolve_assign t1 --- 主表
  where t1.assign_status = 2 and not exists
  (select t2.id from budget_adjust t2 where t2.publish_state = 0 and t1.id = t2.resolve_assign_id )  --- 从表

使用JPA的Specification的单表查询,在这里仅查询主表作为基础

Pageable pageable = PageRequest.of(pageNum, pageSize);
Page<ResolveAssign> page = hibernateDao.findAll(getQuery(condition), pageable);

// 拼接Specification
private Specification<ResolveAssign> getQuery(ResolveAssignVO condition) {
     return (Specification<ResolveAssign>) (root, query, criteriaBuilder) -> {
        Predicate predicate = criteriaBuilder.conjunction();
        
        if(!StringUtils.isEmpty(condition.getName())){
            predicate.getExpressions().add(criteriaBuilder.like(roo.get("name"), "%"+condition.getName()+"%"));
        // 查询条件等等
        }

        Order sort = criteriaBuilder.asc(root.get("sort"));
        return query.orderBy(sort).where(predicate).getRestriction();
    }
}

当这种简单的单表查询不满足要求后,需要有新的解决方案。

方案一:使用原生sql

优点:对于经常操作数据库或从mybatis项目经验的开发者来说,直接使用sql比较直观

缺点:查询出的结果key是数据库命名(往往由下划线区分单词名,如project_name)而实体类需要驼峰命名,并且如果项目前端一直使用驼峰字段取值,不作转换会导致取不到值

// 由于项目里没有采用过这种方式,因此这里是临时写法,需要整合的时候需要进行封装处理
StringBuilder sql = new StringBuilder("select t1.* from resolve_assign t1");
StringBuilder sqlCount = new StringBuilder("select count(t1.id) from resolve_assign t1");

StringBuilder where = new StringBuilder();
where.append("where t1.assign_status = 2 and not exists ");
where.append("(select t2.id from budget_adjust t2 where t2.publish_state = 0 and t1.id = t2.resolve_assign_id ) ");

if(StringUtils.isNotEmpty(condition.getName())) {
    where.append(" and t1.name like :name");
}
// 其他查询条件省略

Query query = entityManager.createNativeQuery(sql.append(where).toString());
Query queryCount = entityManager.createNativeQuery(sqlCount.append(where).toString());
if(StringUtils.isNotEmpty(condition.getName())) {
    query.setParameter("name", "%"+conditon.getName()+"%");
}

query.setFirstResult((pageNum - 1) * pageSize);
query.setMaxResults(pageSize);

List<Map> list = query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).getResultList();
// 这里查出来的是key为数组库字段名,即带下划线的形式,需要转换为驼峰
List<Map> camelList = (List<Map>)list.stream.map(MapUtil::toCamelCaseMap).collect(Collectors.toList());
Long count = ((BigInteger)queryCount.getSingleResult()).longValue();

方案二:改造现有的多表查询Specification

优点:不需要编写sql,也不需要进行key驼峰转换,无须使用map转对象或调整前端接收值的格式调整,更重要的是符合代码规范

缺点:对于更复杂的sql语句来说难度较高,且不如sql直观

Pageable pageable = PageRequest.of(pageNum, pageSize);
Page<ResolveAssign> page = hibernateDao.findAll(getQuery(condition), pageable);

// 拼接Specification
private Specification<ResolveAssign> getQuery(ResolveAssignVO condition) {
     return (Specification<ResolveAssign>) (root, query, criteriaBuilder) -> {
        Predicate predicate = criteriaBuilder.conjunction();
        
        if(!StringUtils.isEmpty(condition.getName())){
            predicate.getExpressions().add(criteriaBuilder.like(roo.get("name"), "%"+condition.getName()+"%"));
        // 查询条件等等
        }

        // 开始子查询
        Subquery<BudgetAdjust> subQuery = query.subquery(BudgetAdjust.class);
        Root<BudgetAdjust> subRoot = subQuery.from(BudgetAdjust.class);
        
        // 从表查询条件
        Predicate sub_state = criteriaBuilder.equal(subRoot.get("publishState"), 0);
        // 从表.resolve_assign_id = 主表.id
        Predicate sub_equals_main = criteriaBuilder.equals(root.get("id"), subRoot.get("resolveAssignId"));
        subQuery.where(sub_state, sub_equals_main);
        
        // 从表查询的字段,这个必须定义
        subQuery.select(subRoot.get("resolveAssignId"));
        
        // not exists
        Predicate notExists = criteriaBuilder.not(criteriaBuilder.exists(subQuery));
        // 主表查询添加上这个not exists子查询
        predicate.getExpressions().add(notExists);

        Order sort = criteriaBuilder.asc(root.get("sort"));
        query.orderBy(sort).where(predicate);
        return query.getRestriction();
    }
}

如果查询sql不是可变的,可以在Dao层定义时,使用@Query注解

@Query(nativeQuery=true, value="select * from resolve_assign where id = ?1")
public List<Map> findById(String id);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用JPA进行原生SQL查询时,可以借助Spring Data JPA的`@Query`注解和`nativeQuery=true`属性来实现。`@Query`注解是用来声明查询语句的,而`nativeQuery=true`则表示使用原生SQL查询。具体步骤如下: 1. 在Repository接口中定义一个方法,并使用`@Query`注解来声明原生SQL查询语句。例如:`@Query(value = "SELECT * FROM table_name WHERE condition", nativeQuery = true)` 2. 在方法中使用JPA的命名参数或占位符来传递参数。例如:`@Query(value = "SELECT * FROM table_name WHERE column_name = :param", nativeQuery = true)` 3. 如果需要返回实体对象,可以在Repository接口中定义一个与查询结果对应的构造函数,并在查询语句中使用`NEW`关键字来创建实体对象。例如:`@Query(value = "SELECT NEW com.example.EntityName(column1, column2) FROM table_name WHERE condition", nativeQuery = true)` 4. 调用Repository接口中定义的方法来执行原生SQL查询并获取结果。 需要注意的是,使用原生SQL查询可能会降低代码的可移植性,并且需要仔细处理SQL注入等安全问题。因此,在使用原生SQL查询时,建议谨慎使用,并遵循安全编码规范。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [spring boot jpa原生sql报Cannot resolve table错误解决方法](https://download.csdn.net/download/weixin_38622149/12744996)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [JPA用法与原声SQL](https://blog.csdn.net/qq_40206199/article/details/84860945)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值