关于Spring Data Jpa 动态查询

前言

通常来说网站的后台管理系统对于数据的查询需要提供多种查询条件,并且要求在用户任意的选择条件进行查询:
1
这种情况下后端需要通过不同的条件组合对数据库进行查询,Mybatis的<if>标签能够很好地实现这个功能:只需要在if标签中对参数进行判空,非空则拼接where 条件即可。
但是对于Spring Data Jpa 这种高度封装的框架来说可能会稍微复杂一些,总结了几种使用Spring Data Jpa 实现动态条件数据库查询的方案,主要有以下四种:

  • 智障方案:列举所有条件组合
  • 局限方案:利用SpringData提供的JpaSpecificationExecutor接口 构建查询对象
  • 常规方案:代码拼接hql语句,通过EntityManager执行
  • 天秀方案:利用sql本身逻辑表达式拼接hql 或 sql

列举所有条件组合

这种方法简单粗暴:列举所有查询条件的所有组合情况并根据不同的近况进行不同条件的数据库查询:

//伪代码
if(name = null){
	if(age = null){
		findAll();
	}else{
		findByAge(age);
	}
}else{
	if(age = null){
		findByName(name);
	}else{
		findByNameAndAge(name,age)
	}
}

这种做法在参数极少的情况下才有可能被使用,3个参数就会出现6种组合,4个参数就有16种组合。组合情况会随着参数的增多出现指数式的上升,非常难以维护。

JpaSpecificationExecutor

Spring Data 提供了JpaSpecificationExecutor接口继承这个接口提供了findAll findOne等重载方法,在查询的时候可以通过代码来构建不同的查询条件,并且支持分页行为

public interface UserRepository extends 
	JpaRepository<User ,Long>,JpaSpecificationExecutor<User>{

}

具体的使用方式不做赘述,感兴趣的可以自行检索。

这种方法的缺陷在于:虽然同样支持多表查询,但是关联的方式非常局限(需要在实体中有外键关系映射才能使用)无法指定通过两张表的哪两个字段是用于关联的

如果出于某些原因,两张表在业务上是关联的,但是在数据库和实体中并没有通过外键和@OneToOne OneToMany等注解进行关联的话 ,这种Specification是无法进行关联查询的

不过在少量数据的情况下可以通过先查询一张表在拼接IN查询来完成某些性能要求不高的查询

EntityManager执行动态拼接的hql

Spring Data Jpa底层的实现是Hibernate ,而且通过源码可以发现其实Jpa底层的数据库操作其实都是基于EntityManager来完成的,EntityManager 是 相当于Hibernate中的Session对象。我们可以从容器中获取EntityManager对象 然后根据条件动态的拼接hql语句最终 通过EntityManager对象来执行:

public class CustomBatchSubscriberDAOImpl implements CustomBatchSubscriberDAO {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public PageVO<BatchSubscribeVO> customQuery1(String name, Integer age, int pageNo, int pageSize) {
        Map<String, Object> param = new HashMap<>();
        String countHql = "select count(msb.id) from User u ";
        String datahql = "select u from User u ";

        String whereHql = "where 1=1 ";

        //动态拼接where条件
        if (null != name && !"".equals(name)) {
            whereHql += "and u.name like :mrName ";
            param.put("name ", "%" + name + "%");
        }
        if (null != age) {
            whereHql += "and u.age =:age ";
            param.put("age ", age );
        }
        //...
        datahql = datahql + whereHql;
        Query query = entityManager.createQuery(datahql);
        //注入参数
        for (Map.Entry<String, Object> entry : param.entrySet()) {
            query = query.setParameter(entry.getKey(), entry.getValue());
        }
        int start = (pageNo - 1) * pageSize + 1;
        query = query.setFirstResult(start).setMaxResults(pageSize);
        List resultList = query.getResultList();


        //查询count
        countHql = countHql + whereHql;
        Query countQuery = entityManager.createQuery(countHql);
        for (Map.Entry<String, Object> entry : param.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            countQuery = countQuery.setParameter(entry.getKey(), entry.getValue());
        }
        Object count = countQuery.getSingleResult();
        //组装分页对象
        PageVO<BatchSubscribeVO> pageVO = new PageVO(pageNo, pageSize);
        pageVO.setTotal((Long) count);
        pageVO.setDataList(resultList);
        return pageVO;
    }

这种方案是最被经常使用的方法 , 通常在使用Spring Data Jpa来创建项目的时候会 抽取出持久层接口基类和实现来提供 基于hql的查询,而hql的拼接则放在业务层进行处理即可,所以并不会使代码变得特别更加臃肿难以维护。

利用sql语法

示例如下:

public interface UserRepository extend JpaRepository<User,Long>{
	
	@Query(
	"select u from User u "+
		"where (u.name = ?1 or ?1 is null) "+
		"and(u.age = ?2 or ?2 is null) "+
	)
	Page<User> costomSearch(String name ,Integer age ,Pageable pageable)
}

其实就是将参数拼到hql或sql里利用逻辑表达式OR来实现参数为空时条件不生效:

name参数为空 则第一个where条件为:

# name参数为空 则第一个where条件为
where (u.name = null or null is null)

可见null is null 条件永远成立,所以改条件不会生效

反之当name参数不为空的时候 第一个where条件为:

# name参数为 “李四”
where (u.name = '李四' or '李四' is null)

可见'李四' is null 这一条件永远不成立,所以只有前面的条件生效。

利用这种方式可以非常方便的实现类似于动态查询的效果,但是这种方式只能判空 , 所以在某些情况下可能需要先在业务层进行判断,不符合条件的话就把参数置为空在调用

总结

Spring Data的有点在于简化持久层开发,高度的封装在极大程度上隐藏了数据库操作的细节,当然这并不意味着无法实现灵活的数据操作。

以上几种动态查询的方法各有优缺,需要根据具体的业务场景选择最合适的方案

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值