Hibernate查询 - 选择一个只有一个符合条件的Set元素的Object

时间:2017-04-06 13:28:04

标签: hibernate criteria hibernate-criteria

我有3个班级:

public class A{
  private String name;
}

public class B{
  private A aObj;
}

public class C{
  private Set<B> bObj;
}

以及以下标准:

Session session = openSession();
Criteria c1 = session.createCriteria(C.class);
Criteria c2 = c1.createCriteria("bObj");
Criteria c3 = c2.createCriteria("aObj");
c2.add(Restrictions.eq("name",name));

哪个工作正常,c1.uniqueResult()是预期的一个(名称是唯一的)

现在的问题是: 有没有办法让C对象只有Set中的一个元素,它只包含满足条件c2的bObj? (假设Set bObj有多个元素)

更新1: 实际结果是(C.class as JSON)

{ bObj : [ 
   1: { aObj : 
     { name : name1}},
   2: { aObj : 
     { name : name2}},
   3: { aObj : 
     { name : name3}}
 ]} 

name = name1的预期结果是:

{ bObj : [ 
   1: { aObj : 
     { name : name1}}
   ]
}

所以在查询之后,bObj将只有一个符合条件的元素name = name1

1 个答案:

答案 0 :(得分:0)

有很多方法可以做到这一点,也许您可​​以使用以下最适合您需求的方法之一。

E.g。如果你有一个B方便的示例实例,请使用CriteriaBuilder。只需传递您希望成为关联成员的示例对象(bObj)并指定关联大小:

final Root<C> selection = createQuery.from(C.class);
createQuery.select(selection).where(
            criteriaBuilder.isMember(bObj, selection.<Set<B>>get("bObj")),
            criteriaBuilder.equal(criteriaBuilder.size(selection.<Set<B>>get("bObj")), 1));

如果您更喜欢Criteria,只需为选择定义(隐式)连接行为并添加限制:

final Criteria criteria = entityManager.unwrap(Session.class).createCriteria(C.class, "c");
criteria.createAlias("c.bObj", "b");
criteria.createAlias("b.aObj", "a");
criteria.add(Restrictions.sizeEq("c.bObj", 1));
criteria.add(Restrictions.eq("a.name", "test"));

或者如果你喜欢JPQL,只需使用:

final TypedQuery<C> query = entityManager.createQuery(
                "SELECT c from C c JOIN c.bObj b JOIN b.aObj a where c.bObj.size = 1 AND a.name = :name ", C.class);
query.setParameter("name", "test");

如果你需要一些东西可以玩,这是一个尝试的尝试。只需更改名称(&#34; test&#34;)或取消注释第二个关联成员以测试限制:

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class ListTests {

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    public void criteriaBuilderTest() {
        final A aObj = new A();
        aObj.setName("test");
        entityManager.persist(aObj);

        final B bObj = new B();
        bObj.setaObj(aObj);
        entityManager.persist(bObj);

        final B bObj2 = new B();
        bObj2.setaObj(aObj);
        entityManager.persist(bObj2);

        final C cObj = new C();
        cObj.getbObj().add(bObj);
        // cObj.getbObj().add(bObj2);
        entityManager.persist(cObj);

        final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        final CriteriaQuery<C> createQuery = criteriaBuilder.createQuery(C.class);
        final Root<C> selection = createQuery.from(C.class);
        createQuery.select(selection).where(criteriaBuilder.isMember(bObj, selection.<Set<B>>get("bObj")),
                criteriaBuilder.equal(criteriaBuilder.size(selection.<Set<B>>get("bObj")), 1));
        final List<C> resultList = entityManager.createQuery(createQuery).getResultList();
        assertThat("Should get exactly one result!", resultList.size(), is(1));
    }

    @Test
    public void criteriaTest() {
        final A aObj = new A();
        aObj.setName("test");
        entityManager.persist(aObj);

        final B bObj = new B();
        bObj.setaObj(aObj);
        entityManager.persist(bObj);

        final B bObj2 = new B();
        bObj2.setaObj(aObj);
        entityManager.persist(bObj2);

        final C cObj = new C();
        cObj.getbObj().add(bObj);
        // cObj.getbObj().add(bObj2);
        entityManager.persist(cObj);

        final Criteria criteria = entityManager.unwrap(Session.class).createCriteria(C.class, "c");
        criteria.createAlias("c.bObj", "b");
        criteria.createAlias("b.aObj", "a");
        criteria.add(Restrictions.sizeEq("c.bObj", 1));
        criteria.add(Restrictions.eq("a.name", "test"));
        final List<C> resultList = criteria.list();

        assertThat("Should get exactly one result!", resultList.size(), is(1));

    }

    @Test
    public void jpqlTest() {
        final A aObj = new A();
        aObj.setName("test");
        entityManager.persist(aObj);

        final B bObj = new B();
        bObj.setaObj(aObj);
        entityManager.persist(bObj);

        final B bObj2 = new B();
        bObj2.setaObj(aObj);
        entityManager.persist(bObj2);

        final C cObj = new C();
        cObj.getbObj().add(bObj);
        // cObj.getbObj().add(bObj2);
        entityManager.persist(cObj);

        final TypedQuery<C> query = entityManager.createQuery(
                "SELECT c from C c JOIN c.bObj b JOIN b.aObj a where c.bObj.size = 1 AND a.name = :name ", C.class);
        query.setParameter("name", "test");
        final List<C> resultList = query.getResultList();

        assertThat("Should get exactly one result!", resultList.size(), is(1));
    }

}

要测试的映射类似于:

@Entity
public class A {

    @Id
    @GeneratedValue
    private long id;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

@Entity
public class B {

    @Id
    @GeneratedValue
    private long id;

    @OneToOne
    private A aObj;

    public void setaObj(A aObj) {
        this.aObj = aObj;
    }

}

@Entity
public class C {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany
    private Set<B> bObj = new HashSet<>();

    public Set<B> getbObj() {
        return bObj;
    }

    public void setbObj(Set<B> bObj) {
        this.bObj = bObj;
    }

}

我倾向于使用类型安全的解决方案,可能是第一个或最后一个。或者考虑使用MetaModel Generator

<强>更新

在确定评论中的问题之后,我将添加另一个测试以使问题更加清晰(并保留原始答案以用于历史记录)。

访问未经过滤的关联将始终为您提供所有结果(要么急于查询时间,要么懒得访问它)。一种解决方案是在访问之前对其进行过滤并将其转换为投影或其他内容。也许你可以玩这个测试找到一种方法:

@Test
public void jpqlTestWithDelayedFilterQuery() {
    final String filterFieldName = "name";
    final String filterFieldValue = "test";
    // this is the one we want to get the Bs for
    final A aObj = new A();
    aObj.setName(filterFieldValue);
    entityManager.persist(aObj);

    // this is the B which should be pass the filter
    final B bObj = new B();
    bObj.setaObj(aObj);
    entityManager.persist(bObj);

    // A not matching the filter
    final A aObj2 = new A();
    aObj2.setName("testXXX");
    entityManager.persist(aObj2);

    // we don't want to get that B here
    final B bObj2 = new B();
    bObj2.setaObj(aObj2);
    entityManager.persist(bObj2);

    // only another B to test the first query
    final B bObj3 = new B();
    bObj3.setaObj(aObj2);
    entityManager.persist(bObj3);

    // this is the one returned by first query
    final C cObj = new C();
    cObj.getbObj().add(bObj);
    cObj.getbObj().add(bObj2);
    entityManager.persist(cObj);

    // only another C to test the first query
    final C cObj2 = new C();
    cObj2.getbObj().add(bObj3);
    entityManager.persist(cObj2);

    // let's get only the Cs we need
    final Session session = entityManager.unwrap(Session.class);
    final Query cQuery = session.createQuery(
            "SELECT c from C c INNER JOIN c.bObj b INNER JOIN b.aObj a where a.name = :name ");
    cQuery.setParameter(filterFieldName, filterFieldValue);
    final List cResults = cQuery.list();
    assertThat("Should get exactly one C result!", cResults.size(), is(1));

    // sadly the B collection is initialized fully, at latest on accessing it, so two Bs here :/
    final C cResult = (C)cResults.iterator().next();
    assertThat("Should get two already initialized B results here!", cResult.getbObj().size(), is(2));

    // the only way is getting our needed Bs with a filter (imagine you did not use it before)
    final Query query = session.createFilter(cResult.getbObj(), "where this.aObj.name = :name");
    query.setParameter(filterFieldName, filterFieldValue);
    final List bResult = query.list();
    assertThat("Should get exactly one filtered B result here!", bResult.size(), is(1));
}