Hibernate标准仅映射到层次结构中的一个类



我有一个用冬眠映射到我的DB的实体类的层次结构(并且没有太大的更改层次结构的余地)。

这是父表

Table TMP_ROOT
| ID | NAME  |
|----|-------|
| 1  | A FOO |
| 2  | A BAR |

这是孩子

Table TMP_CHILD_FOO
| ID | COMMON | FOO |
|----|--------|-----|
| 1  | 123    | 456 |
Table TMP_CHILD_BAR
| ID | COMMON | BAR |
|----|--------|-----|
| 2  | 777    | 888 |

这两个孩子表共享一个我不能放在根表中的字段COMMON,因为还有其他儿童桌子没有它。

使用抽象类用于常见属性,我的表被映射如下。(我省略Geters,Setters,equals等):

@Entity
@Table(name = "TMP_ROOT")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Root {
    @Id
    @NotNull
    @Column(name = "ID")
    private Long id;
    @Column(name = "NAME")
    private String name;
}
@MappedSuperclass
public abstract class Middle extends Root {
    @Column(name = "COMMON")
    private Long common;
}
@Entity(name = "TMP_CHILD_FOO")
public class FooChild extends Middle {
    @Column(name = "FOO")
    private Integer foo;
}
@Entity(name = "TMP_CHILD_BAR")
public class BarChild extends Middle {
    @Column(name = "BAR")
    private Integer bar;
}

映射作品主要是预期的:

  • 如果我查询Root在其字段上添加过滤器(IDNAME),则可以工作
  • 如果我在Root上查询FooChild/BarChildFOO/BAR Resp。)的字段上添加过滤器,它可以工作

但是如果我尝试在COMMON字段上查询,则生成的SQL查询仅在两个表中的一个上过滤器完全忽略了另一个。

例如,在此测试中,发生这种情况(顺便说一句,我知道session.createCriteria(clazz)是 弃用,但这是我无法轻易更改的代码的一部分):

@RunWith(Arquillian.class)
public class HibernateHierarchyTest {
    @Inject
    private EntityManager em;
    @Deployment
    public static WebArchive createDeployment() {
        System.out.println("--DEPLOY--");
        WebArchive archive = ShrinkWrap.create(WebArchive.class, "Test.war")
                .addClasses(Root.class,Middle.class,FooChild.class,BarChild.class, EntityManager.class)
                .addAsResource("META-INF/persistence.xml")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
        return archive;
    }
    @Before
    public void preparePersistenceTest() throws Exception {
        insertData();
        em.getTransaction().begin();
    }
    private void insertData() throws Exception {
        em.getTransaction().begin();
        insertFooBar();
        em.flush();
        em.getTransaction().commit();
        em.clear();
    }
    @After
    public void commitTransaction() throws Exception {
        em.getTransaction().commit();
    }
    private void insertFooBar() {
        FooChild foo = new FooChild();
        foo.setId(1L);
        foo.setName("THE FOO");
        foo.setCommon(456L);
        foo.setFoo(42);
        em.merge(foo);
        BarChild bar = new BarChild();
        bar.setId(2L);
        bar.setName("THE BAR");
        bar.setCommon(789L);
        bar.setBar(666);
        em.merge(bar);
    }
    @Test
    public void findRootAndChildren() {
        Class<Root> clazz = Root.class;
        Session session = em.unwrap(Session.class);
        findCommonEqualTo(clazz, session, 456L);
        findCommonEqualTo(clazz, session, 789L);
    }
    private void findCommonEqualTo(Class<Root> clazz, Session session, long value) {
        Criteria criteria = session.createCriteria(clazz);
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.add(Restrictions.eq("common", value));
        List results = criteria.list();
        printAll(results);
    }
    private static void printAll(List results) {
        System.err.println(results.size());
        results.forEach(System.err::println);
    }
}

两个查询是

  • findCommonEqualTo(clazz, session, 456L);:应该找到使用COMMON=456的所有实体
  • findCommonEqualTo(clazz, session, 789L);:应使用COMMON=789
  • 找到所有实体

最终的SQL仅在一个表上过滤,而完全忽略了另一个表的同一字段。

SELECT
  root.ID       AS ID1_56_0_,
  root.NAME     AS NAME2_56_0_,
  bar.COMMON    AS COMMON1_54_0_,
  bar.BAR       AS BAR2_54_0_,
  foo.COMMON    AS COMMON1_55_0_,
  foo.FOO       AS FOO2_55_0_,
  CASE WHEN bar.ID IS NOT NULL
    THEN 1
  WHEN foo.ID IS NOT NULL
    THEN 2
  WHEN root.ID IS NOT NULL
    THEN 0 END   AS clazz_0_
FROM TMP_ROOT root
  LEFT OUTER JOIN TMP_CHILD_BAR bar ON root.ID = bar.ID
  LEFT OUTER JOIN TMP_CHILD_FOO foo ON root.ID = foo.ID
WHERE bar.COMMON = ?;

如您所见,生成的查询仅使用bar.COMMON,并且foo.COMMON上没有条件。

如果我要手动编写SQL,我只会使用WHERE (bar.COMMON = ? OR foo.COMMON = ?),但是我不知道如何使用冬眠条件查询。

我相信您无法轻易查询Rootcommon(例如在SQL中),因为它在Root中不存在()知道hibernate-criteria如何处理此操作,因此仍然可能是可能的,对此不太熟悉)。

以下是我的解释&amp;建议。

common仅存在于Middle中。即使查询之后,结果集都用实际类型填充。

这两个孩子表共享一个我不能放在根表中的字段COMMON,因为还有其他孩子桌没有它。

我认为,如果您想找到具有common的实体,我不想搜索Root?更合适的选项是查询Middle,以查找使用common的实体。

并实现这一目标 - 拥有现在的数据库,并且可以更改层次结构 - 您应该更改 Middle stic

@Entity // so not @MappedSuperClass
// TABLE_PER_CLASS prevents creating Middle
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Middle extends Root { .. }

update

但是,如果可以使用JPA2.1,也可以使用treat(..)来实现这一点,例如

cq.where( cb.equals( cb.treat( root, Middle.class).get("common") ) );

在搜索实际上在其某些子类中的某些字段时,明确地告诉如何处理或施放Root

最新更新