JPA(hibernate)一对多根据多的一方某属性进行过滤查询

spring boot 同时被 3 个专栏收录
43 篇文章 12 订阅
12 篇文章 4 订阅

我们经常会碰上某个字段是集合元素(List,Set)的情况,并且我们要过滤出集合中包含某个或某些元素的数据。
譬如一个类User

	/**
     * 检索人
     */
    private Long userId;
    /**
     * 省、直辖市集合
     */
    @ElementCollection
    @CollectionTable
    private List<String> provinces;
    /**
     * 市、区集合
     */
    @ElementCollection
    @CollectionTable
    private List<String> cities;
    /**
     * 行业集合
     */
    @ElementCollection
    @CollectionTable
    private List<Integer> vocations;

包含一个String型的集合,我们希望能查询出province="1"或者"2"的User集合。
倘若使用Hql或者原生sql是比较简单的,但是使用Criteria查询就不那么简单了,尤其是当User中包含多个集合元素,并且查询条件不确定时。
Jpa中Criteria用来构建复杂查询,之前我的文章中(http://blog.csdn.net/tianyaleixiaowu/article/details/72876732)已经讲过了如何构建动态条件查询,里面就有如何实现查询集合元素中是否包含某元素的功能。
重点看一下那篇文章中的SimpleExpression.java,里面的case IS_MEMBER,调用了CriteriaBuilder的isMember方法,该方法就能查询出你的集合中是否包含某个元素。
请注意,我定义User类时,注解写的是:@ElementCollection,映射的是基本类型不是一个javaBean类,所以无法使用表关联的写法如user.address.id=XXX,这样的hibernate表关联写法。
那么就需要使用isMember这样的写法(注意:需要导入上面提到的那篇文章的几个类,才能用下面的写法):

        Criteria<PtSearchCondition> criteria = new Criteria<>();
        criteria.add(Restrictions.hasMembers("provinces", "110000", "120000"));
        Page page = userRepository.findAll(criteria, new PageRequest(0, 10));
        return page.getContent();

有个地方需要说明一下,@ElementCollection这个注解代表该属性是一个集合属性,它和one-to-many类似,但不是同一个东西,one-to-many注解的另一方也要是一个表,不能只是一个普通的基本类型的集合。
如果你的@ElementCollection注解的集合对象也是一个JavaBean,不是String或者Integer时,譬如User有多个Address,Set《Address》 addressSet,那么Address类需要加@Embeddable注解,否则报错。@Embeddable代表是一个嵌入式的对象,不是一个表映射对象。如果你用的是one-to-many,那么Address就需要加上@Entity,代表需要映射到数据库表。
下面还看查询的问题:
如果你的属性是一个对象的集合,并且是@ElementCollection注解的,那么如何查询呢?
很简单,同样还是使用

	Criteria<PtSearchCondition> criteria = new Criteria<>();
    criteria.add(Restrictions.hasMembers("address.name", "北京"));
    Page page = userRepository.findAll(criteria, new PageRequest(0, 10));

在我的SimpleExpression.java中,有这样一段代码来处理一对多的查询

@Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query,
                                 CriteriaBuilder builder) {
        Path expression;

        //此处是表关联数据,注意仅限一层关联,如user.address,
        //查询user的address集合中,address的name为某个值
        if (fieldName.contains(".")) {
            String[] names = StringUtils.split(fieldName, ".");
            //获取该属性的类型,Set?List?Map?
            expression = root.get(names[0]);
            Class clazz = expression.getJavaType();
            if (clazz.equals(Set.class)) {
                SetJoin setJoin = root.joinSet(names[0]);
                expression = setJoin.get(names[1]);
            } else if (clazz.equals(List.class)) {
                ListJoin listJoin = root.joinList(names[0]);
                expression = listJoin.get(names[1]);
            } else if (clazz.equals(Map.class)) {
                MapJoin mapJoin = root.joinMap(names[0]);
                expression = mapJoin.get(names[1]);
            } else {
                //是many to one时
                expression = expression.get(names[1]);
            }

        } else {
            //单表查询
            expression = root.get(fieldName);
        }

里面使用了SetJoin来完成对多的一方的某字段的匹配查询。
在Restrictions.java中,做了判断多的一方是基本类型还是JavaBean的判断:

/**
     * 集合包含某几个元素,譬如可以查询User类中Set<String> set包含"ABC","bcd"的User集合,
     * 或者查询User中Set<Address>的Address的name为"北京"的所有User集合
     * 集合可以为基本类型或者JavaBean,可以是one to many或者是@ElementCollection
     * @param fieldName
     * 列名
     * @param value
     * 集合
     * @return
     * expresssion
     */
    public static LogicalExpression hasMembers(String fieldName, Object... value) {
        SimpleExpression[] ses = new SimpleExpression[value.length];
        int i = 0;
        //集合中对象是基本类型,如Set<Long>,List<String>
        Criterion.Operator operator = Criterion.Operator.IS_MEMBER;
        //集合中对象是JavaBean
        if (fieldName.contains(".")) {
            operator = Criterion.Operator.EQ;
        }
        for (Object obj : value) {
            ses[i] = new SimpleExpression(fieldName, obj, operator);
            i++;
        }
        return new LogicalExpression(ses, Criterion.Operator.OR);
    }

同理,如果是使用one to many来映射的1对多表关系,同样可以使用上面的方法,写法也完全相同。

Criteria<PtSearchCondition> criteria = new Criteria<>();
        criteria.add(Restrictions.hasMembers("address.name", "北京"));
        criteria.add(Restrictions.hasMembers("provinces", "110000", "120000"));
        Page page = userRepository.findAll(criteria, new PageRequest(0, 10));
        

以上就能完成Jpa中1对多,根据多的一方的某属性进行过滤匹配。

  • 3
    点赞
  • 1
    评论
  • 3
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:酷酷鲨 设计师:CSDN官方博客 返回首页

打赏作者

天涯泪小武

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值