我想通过EclipseLink(2.6.1)JPA标准生成以下SQL。
SELECT
zonecharge0_.charge AS col_0_0_
FROM
projectdb.zone_charge zonecharge0_
WHERE
(
EXISTS (
SELECT
country1_.country_id
FROM
projectdb.country country1_
WHERE
country1_.country_id=?
and zonecharge0_.zone_id=country1_.zone_id
)
)
AND (
zonecharge0_.weight_id IN (
SELECT
MAX(weight2_.weight_id)
FROM
projectdb.weight weight2_
WHERE
weight2_.weight BETWEEN 0 AND 60
)
)
在Hibernate(最终的4.3.7)上运行以下两个条件和JPQL查询会产生上述干净的查询,人们可以直观地预期。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<BigDecimal>criteriaQuery=criteriaBuilder.createQuery(BigDecimal.class);
Metamodel metamodel = entityManager.getMetamodel();
Root<ZoneCharge> root = criteriaQuery.from(metamodel.entity(ZoneCharge.class));
criteriaQuery.select(root.get(ZoneCharge_.charge));
Subquery<Long> countrySubquery = criteriaQuery.subquery(Long.class);
Root<Country> countryRoot = countrySubquery.from(metamodel.entity(Country.class));
countrySubquery.select(countryRoot.get(Country_.countryId));
Predicate[] predicates=new Predicate[2];
predicates[0]=criteriaBuilder.equal(countryRoot, country);
predicates[1]=criteriaBuilder.equal(root.get(ZoneCharge_.zoneId), countryRoot.get(Country_.zoneId));
countrySubquery.where(predicates);
Subquery<Long> weightSubquery = criteriaQuery.subquery(Long.class);
Root<Weight> weightRoot = weightSubquery.from(metamodel.entity(Weight.class));
weightSubquery.select(criteriaBuilder.max(weightRoot.get(Weight_.weightId)));
weightSubquery.where(criteriaBuilder.between(weightRoot.get(Weight_.weight), BigDecimal.ZERO, weight));
predicates=new Predicate[2];
predicates[0]=criteriaBuilder.exists(countrySubquery);
predicates[1]=criteriaBuilder.in(root.get(ZoneCharge_.weightId).get(Weight_.weightId)).value(weightSubquery);
criteriaQuery.where(predicates);
BigDecimal charge = entityManager.createQuery(criteriaQuery).getSingleResult();
相应的JPQL:
SELECT zc.charge
FROM ZoneCharge AS zc
WHERE EXISTS(SELECT c.countryId
FROM Country AS c
WHERE zc.zoneId = c.zoneId
AND c.countryId = 1)
AND zc.weightId.weightId IN(SELECT MAX(w.weightId)
FROM Weight AS w
WHERE w.weight BETWEEN 0 AND 60)
运行这些查询(条件和JPQL)而不对EclipseLink(2.6.1)进行任何修改会产生以下非常丑陋/混乱的查询。
SELECT t0.charge
FROM projectdb.zone_charge t0,
projectdb.weight t1 /*Unnecessary table listing.*/
WHERE ((EXISTS (SELECT t2.country_id
FROM projectdb.zone_table t3, /*Unnecessary table listing.*/
projectdb.country t2
WHERE ((( t2.country_id = ?)
AND( t0.zone_id = t3.zone_id))
AND(t3.zone_id = t2.zone_id))) /*Duplicate join.*/
AND t1.weight_id IN (SELECT MAX(t4.weight_id)
FROM projectdb.weight t4
WHERE (t4.weight BETWEEN ? AND ?)))
AND (t1.weight_id = t0.weight_id)) /*Duplicate join.*/
我只想获取类型为BigDecimal
的标量值。它不需要在这里加入,也没有提到过,虽然它产生了两个完全不必要的多余连接。
这些冗余连接的原因是给定谓词中的两个嵌套属性。
root.get(ZoneCharge_.weightId).get(Weight_.weightId)
和
root.get(ZoneCharge_.zoneId), countryRoot.get(Country_.zoneId)
查看Predicate
数组。
我一直试图知道很长一段时间的原因,但我没有在EclipseLink中找到任何关于这种现象的提及。
我认为这不是一种疏忽。它看起来很刻意。
看到这种陈述,我感到非常失望。有没有理由让EclipseLink生成这种冗余连接?是否有一些设置或其他东西可以摆脱它们,我可能会在EclipseLink中丢失它们?他们还能避免吗?
其他信息:
表结构很简单。这里涉及三个表格。
zone_charge
表有一列weight_id
,它是引用weight
表中相应主键的外键。
country
和zone_charge
表都有一个zone_id
列,zone_id
列中的zone_table
列的外键(此处未提及)在两个表格中。
查询根据权重值返回特定区域中zone_charge
的费用值。假设用户不知道区域。因此,此查询基于用户通过选择下拉框中的国家/地区提供的国家/地区在后台选择区域。
更新:
有些不同意见和观点的问题:
所有这些都意味着同样的事情。 Hibernate在生成整洁的SQL语句时往往非常聪明。我不认为那些使用EcliplseLink(反过来,这是一个广泛使用的ORM框架)就像在数据库上执行这种不整洁的SQL语句一样。
你真的只是跟着他们继续吗?