Hibernate 查询 - select 一个只有一个符合条件的 Set 元素的对象

Hibernate query - select one Object which have only one Set element which meets 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() 是预期的(名称是唯一的)

现在的问题是: 有什么方法可以让 Set 中只有一个元素的 C 对象只包含满足条件 c2 的 bObj? (假设 Set bObj 有超过 1 个元素)

更新 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

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

例如如果您手边有 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");

如果您需要一些东西来玩,这里有一个测试来尝试一下。只需更改名称 ("test") 或取消注释第二个关联成员以测试限制:

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));
}