我在JPA中存储了一个Map,它存储了每种语言的关键字翻译。比如一个对象存储Locale.ENGLISH -> "Father" , Locale.CHINESE -> "PaPa"
。另一个对象存储Locale.ENGLISH -> "Mother" , Locale.CHINESE -> "MaMa"
;
这是我的工作设计:
public class Relation {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ElementCollection
@MapKeyColumn(name="locale")
@Column(name="value")
@CollectionTable(name = "RelationName", joinColumns = @JoinColumn(name = "relation_id"))
private Map<Locale, String> langMap = new HashMap<>();
// other fields skipped
}
效果很好,我可以将很多关键字翻译存储到DB。但是当使用JPQL查询时,它有一些问题:
例如,我想找到哪个Relation的英文键值为“Father”:
这是我的代码:
Relation r = em.createQuery("select r from Relation r join r.langMap m where ( KEY(m) = :locale and VALUE(m) = :value ) " , Relation.class)
.setParameter("locale" , locale)
.setParameter("value" , value)
.getSingleResult();
它会生成这个奇怪/无法运行的SQL:
Hibernate:
select
relation0_.id as id1_18_
from
Relation relation0_
inner join
RelationName langmap1_
on relation0_.id=langmap1_.relation_id
where
langmap1_.locale=?
and (
select
langmap1_.value
from
RelationName langmap1_
where
relation0_.id=langmap1_.relation_id
)=?
00:16:12.038 WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1242, SQLState: 21000
00:16:12.038 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Subquery returns more than 1 row
我不知道它为什么会生成那个奇怪的子查询。
我可以通过Criteria解决这个问题:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Relation> criteria = builder.createQuery(Relation.class);
Root<Relation> root = criteria.from(Relation.class);
criteria.select(root);
MapJoin<Relation , Locale , String> mapJoin = root.joinMap("langMap");
criteria.where(builder.and(
builder.equal(mapJoin.key(), locale) ,
builder.equal(mapJoin.value() , value))
);
return em.createQuery(criteria).getSingleResult();
它生成正确的SQL(where langmap1_.locale=? and langmap1_.value=?
)并且运行良好。
但我觉得Criteria太复杂了。我想知道为什么JPQL失败了?如何更正JPQL?
感谢。
环境:
JPA2,Hibernate 4.2.3,MySQL方言
答案 0 :(得分:17)
我遇到了同样的问题。看起来像通过ref访问map(没有VALUE())已经为你提供了一个map条目值,即下一个JPQL应该被转换为一个有效的SQL:
select r from Relation r join r.langMap m where ( KEY(m) = :locale and m = :value )
答案 1 :(得分:12)
我在使用带有Hibernate的JPQL VALUE()运算符时遇到了类似的问题。似乎Hibernate实现了VALUE()运算符,就像Java中的 java.util.Map.values()方法一样。它生成一个子查询,返回映射中的所有值,即映射表中与保存Map属性的实体相关的所有行。只要在映射中有多个键/值对,比较表达式(将标量表达式视为操作数)将失败。
您可以做的是转换比较表达式并将其转换为IN表达式。
而不是:
select r from Relation r join r.langMap m
where ( KEY(m) = :locale and VALUE(m) = :value )
你可以写:
select r from Relation r join r.langMap m
where ( KEY(m) = :locale and :value in (VALUE(m)) )
我希望这样你的查询能够正常运作。
答案 2 :(得分:1)
正确的JPQL可能是这样的:
SELECT r FROM Relation r JOIN r.langMap map
WHERE map[:locale] = :value