我正在使用Spring Boot,Groovy和JPA / Hibernate来迁移旧的应用程序。唯一的限制是不能更改数据库模型,我发现自己处于OneOnOne关系的奇怪情况:
请查看以下型号设置:
@Entity
@Table(name='table1')
class Table1 {
@Id
Table1Id id
@Column(name='sequence_num')
Integer seqNum
@Column(name='item_source')
String itemSource
@Column(name='source_type')
String sourceType
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumn(name='key_field_2', insertable=false, updatable=false)
@NotFound(action=NotFoundAction.IGNORE)
// @Fetch(FetchMode.JOIN)
Table2 table2
}
@Embeddable
class Table1Id implements Serializable {
@Column(name='key_field_1')
String key1
@Column(name='key_field_2')
String key2
}
@Entity
@Table(name='table2')
class Table2 {
@Id
@Column(name='key_id')
String keyId
@Column(name='field1')
String field1
@Column(name='field2')
String field2
@Column(name='field3')
String field3
}
我的Spock测试如下:
def "Try out the JOIN select with Criteria API"() {
given:
CriteriaBuilder cb = entityManager.getCriteriaBuilder()
CriteriaQuery<Object[]> cQuery = cb.createQuery(Object[].class)
Root<Table1> t1 = cQuery.from(Table1.class)
Path<Table2> t2 = t1.get('table2')
Join<Table1, Table2> lanyonLeftJoin = t1.join('table2', JoinType.INNER)
Predicate where = cb.equal(t1.get('itemSource'), 'ABC')
cQuery.multiselect(t1, t2)
cQuery.where(where)
when:
List<Object[]> result = entityManager.createQuery(cQuery).getResultList()
then:
result.each{ aRow ->
println "${aRow[0]}, ${aRow[1]}"
}
}
此配置在Table1和Table2之间成功生成INNER JOIN,注意即使是&#34;中的常量,其中&#34;子句被正确解释。
然而,由于某些奇怪的原因,在第一个查询中返回的每一行都会重新查询Table2。
我看到的输出是:
Hibernate:
select
table10_.key_field_1 as key_field_11_3_0_,
table10_.key_field_2 as key_field_22_3_0_,
table21_.key_id as key_id1_5_1_,
table10_.item_source as item_source3_3_0_,
table10_.sequence_num as sequence_num4_3_0_,
table10_.source_type as source_type5_3_0_,
table21_.field2 as field23_5_1_,
table21_.field3 as field34_5_1_,
table21_.field1 as field15_5_1_
from
table1 table10_
inner join
table2 table21_
on table10_.key_field_2=table21_.key_id
where
table10_.item_source=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
// 500+ more of these
正如我们所看到的,第一个查询成功返回两个表中的所有行,它实际上是我正在寻找的确切查询。但是,正在执行所有不必要的额外查询。
有没有理由为什么JPA会做这样的事情并且有办法防止它?
我的印象是我遗漏了一些非常明显的东西。
提前感谢您的帮助
更新1
如果我更换
cQuery.multiselect(t1, t2)
的
cQuery.multiselect(t1.get('id').get('key1'), t1.get('id').get('key2'),
t1.get('fieldX'), t1.get('fieldY'), t1.get('fieldZ'),
t2.get('fieldA'), t2.get('fieldB'), t2.get('fieldC') ...)
它生成完全相同的内连接查询,并且不再重新查询Table2。
换句话说,看起来(至少在这种情况下)我需要明确列出两个表中的所有字段。不是一个很好的解决方法,因为对于有很多字段的表来说,它会很快变得非常难看。我想知道是否有办法检索所有@Column注释字段/ getter而无需资源到一堆反射内容?
答案 0 :(得分:0)
我想我已经拥有它了!
@JoinFormula:
Table2中的主键是INT,Table1中用作FK的字段是String(我完全错过了!duh!)。因此,解决方案是以以下形式应用@JoinFormula而不是@JoinColumn:
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumnsOrFormulas([
@JoinColumnOrFormula(formula=@JoinFormula(value='CAST(key_field_2 AS INT)'))
])
@NotFound(action=NotFoundAction.IGNORE)
Table2 table2
这奇怪地返回List&lt; Object []&gt; List的每个项目都包含一个包含2个元素的数组:Table1的一个实例和Table2的一个实例。
加入抓取:
根据您的建议,我在查询中添加了“join fetch”,如下所示:
select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
这会导致Hibernate正确返回List&lt; Table1&gt;
单独使用@JoinFormula或@JoinFormula +“join fetch”hibernate停止生成n + 1个查询。
调试Hibernate代码我发现它在第一次使用连接查询查询数据库时正确地在Session中检索和存储两个实体,但是PK和FK数据类型之间的区别导致Hibernate再次重新查询数据库,在第一个查询中检索到的每一行一次。