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

标签 java hibernate jpa inheritance hibernate-criteria

我有一个实体类层次结构,使用 Hibernate 映射到我的数据库 (并且没有太多空间来更改层次结构)。

这是父表

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

这是 children

Table TMP_CHILD_FOO
| ID | COMMON | FOO |
|----|--------|-----|
| 1  | 123    | 456 |

Table TMP_CHILD_BAR
| ID | COMMON | BAR |
|----|--------|-----|
| 2  | 777    | 888 |

这两个子表共享一个字段COMMON,我无法将其放入根表中,因为 还有其他子表没有它。

我的表映射如下,使用公共(public)属性的抽象类。 (我省略了 getter、setter、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/BarChild 的字段上添加过滤器 (FOO/ BAR 分别),它有效

BUT 如果我尝试在 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 = ?), 但我不知道如何使用 Hibernate 标准查询来实现这一点。

最佳答案

我相信您无法轻松地从 Root 查询(就像在 SQL 中一样)common,因为它在 中不存在>Root (但我必须承认,我实际上并不知道 hibernate-criteria 是如何处理这个问题的,所以可能仍然可以通过某种方式实现,但不太熟悉>).

以下是我的解释和建议。

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

These two children tables share a field COMMON that I can't put in the root table because there are other children tables that do not have it.

另外,我认为如果您想查找具有 common 的实体,您不想搜索 Root 吗?更合适的选择是在使用 common 查找实体时查询 Middle

为了实现这一点 - 拥有现在的数据库并且不更改层次结构 - 您应该稍微更改 Middle

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

更新

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

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

如此明确地告诉在搜索实际上位于其某些子类中的某些字段时如何处理或转换Root

关于java - Hibernate 标准仅映射到层次结构中的一个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47816040/

相关文章:

java - 在 JPA 查询中包含枚举

java - 不是自动创建 Hibernate 模式,而是使用 SQL 脚本

java - 我可以在 application.yml 中为实体写入表名吗?

java - Hibernate 中的 JPA 2.1 NamedSubgraph 忽略子类

java - 将具有输出功能的可在 Eclipse 中运行的 Jar 导出到 xml 文件

java - 如何修复被忽略的嵌入对象中的验证约束?

java - 使用具有相关自动生成 ID 的 hibernate 批量插入

java - 如何使用 java 运行时将密码输入 sudo?

java - 将信息从 servlet 发送到 jsp 页面

java - 在同一命令提示符下运行 JAVA 程序