我有一个用冬眠映射到我的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
在其字段上添加过滤器(ID
,NAME
),则可以工作 - 如果我在
Root
上查询FooChild
/BarChild
(FOO
/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 = ?)
,但是我不知道如何使用冬眠条件查询。
我相信您无法轻易查询Root
的common
(例如在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
。