我想通过 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)
在 EclipseLink (2.6.1) 上运行这些查询(标准和 JPQL)而不进行任何修改会产生以下非常丑陋/混乱的查询。
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.*/
我只想获取 type 的标量值BigDecimal
。它不需要在这里加入,我也没有提到过,尽管它产生了两个完全不必要的多余连接。
这些冗余连接的原因是给定谓词中的两个嵌套属性。
root.get(ZoneCharge_.weightId).get(Weight_.weightId)
和
root.get(ZoneCharge_.zoneId), countryRoot.get(Country_.zoneId)
看看Predicate
数组。
我一直试图知道原因很长时间,但我在 EclipseLink 中没有发现任何关于这种现象的任何内容。
我不认为这可能是一个疏忽。看起来很刻意。
看到这样的言论,我感到非常失望。是否有任何理由让 EclipseLink 生成这种冗余连接?是否有一些设置或其他东西可以摆脱我在 EclipseLink 中可能缺少的?无论如何可以避免它们吗?
附加信息 :
表结构很简单。这里即涉及三个表。
- zone_charge
- 国家
- 重量
该zone_charge
表有一列weight_id
是引用表中相应主键的外键weight
。
和表都有一个列country
,其中列的外键(此处未提及)在两个表中。zone_charge
zone_id
zone_id
zone_table
查询zone_charge
根据重量值返回特定区域中的费用值。假定用户不知道区域。因此,该查询基于用户通过在下拉框中选择国家提供的国家在后台选择区域。
更新 :
一些不同观点和看法的问题:
- https://bugs.eclipse.org/bugs/show_bug.cgi?id=246211
- https://bugs.eclipse.org/bugs/show_bug.cgi?id=298494
- https://bugs.eclipse.org/bugs/show_bug.cgi?id=395792
- https://bugs.eclipse.org/bugs/show_bug.cgi?id=300625
- https://bugs.eclipse.org/bugs/show_bug.cgi?id=433126
所有这些都暗示着同样的事情。Hibernate 在生成整洁的 SQL 语句方面往往非常聪明。我不认为那些使用 EcliplseLink(又是广泛使用的 ORM 框架之一)的人喜欢在数据库上执行这种不整洁的 SQL 语句。
你真的只是继续和他们在一起吗?