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

时间:2017-12-14 14:43:51

标签: java hibernate jpa inheritance hibernate-criteria

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

这是父表

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,因为 还有其他儿童表没有它。

我的表映射如下,使用common属性的抽象类。 (我省略了getter,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)上添加过滤器,则可以
  • 如果我查询RootFooChild / 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.COMMONfoo.COMMON没有条件。

如果我手动编写SQL,我只会使用WHERE (bar.COMMON = ? OR foo.COMMON = ?), 但我不知道如何使用Hibernate条件查询来实现这一目标。

1 个答案:

答案 0 :(得分:1)

我认为您无法轻易查询SQL common Root来自Root,因为它hibernate-criteria中不存在(但是我我不得不承认我实际上并不知道common如何处理这个问题,所以可能仍然可以某种方式,而不是那么熟悉)。

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

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

  

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

另外,如果您想查找common的实体,我认为您不想搜索Middle?更合适的选项是在common找到实体时查询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

如此明确地告诉我们在搜索实际存在于某些子类中的某个字段时如何处理或转换 level: { type: DataTypes.ENUM('state', 'service-area'), allowNull: false, validate: { isIn: { args: [ ['state', 'service-area'] ], msg: 'level should be one of state,service-area' } } }, assignedStates: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true }, assignedServiceAreas: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true },