JPA(hibernate)一對多根據多的一方某屬性進行過濾查詢

天涯淚小武發表於2017-11-22

我們經常會碰上某個欄位是集合元素(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對多,根據多的一方的某屬性進行過濾匹配。

相關文章