我有一个使用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
在其字段(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 = ?)
,
但我不知道如何使用Hibernate条件查询来实现这一目标。
答案 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
},
。