我有一个实体类层次结构,使用 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
上查询,并在其字段(ID
、NAME
)上添加过滤器,它就会起作用 - 如果我在
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/