
问题现象:自定义根实体别名失效
在使用Hibernate Criteria API构建查询时,开发者通常可以通过getSession().createCriteria(Entity.class, "alias")为实体设置一个自定义的表别名。然而,在Hibernate 3.6.10.Final等特定版本中,即使显式设置了别名,生成的SQL查询中根实体的表别名仍然是默认的this_,而非期望的自定义别名。
例如,以下代码尝试为Vehicle实体设置别名为temp:
import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.transform.Transformers; import javax.persistence.*; import java.util.ArrayList; import java.util.Date; import java.util.List; // 假设这是一个通用的DAO方法 public class GenericDao{ protected Session getSession() { // 实际应用中应从SessionFactory获取 throw new UnsupportedOperationException("Implement getSession() method"); } protected void begin() { // 实际应用中应开启事务 } protected void commit() { // 实际应用中应提交事务 } protected List findByProjectionCriteria() { // 尝试设置自定义别名 "temp" Criteria cr = getSession().createCriteria(Vehicle.class, "temp"); cr.setResultTransformer(Transformers.aliasToBean(Vehicle.class)); ProjectionList projectionList = Projections.projectionList(); projectionList.add(Projections.property("vehicleId"), "vehicleId"); projectionList.add(Projections.property("vin"), "vin"); projectionList.add(Projections.property("initialRegistration"), "initialRegistration"); cr.setProjection(projectionList); cr.add(Restrictions.eq("vin", "WVW29343249702776")); begin(); List list = null; try { list = cr.list(); } catch (Exception e) { e.printStackTrace(); } finally { commit(); } if (list == null) { list = new ArrayList<>(); } return list; } } // 实体定义 @Entity @Table(name = "vehicle") class Vehicle { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "vehicle_id", unique = true, nullable = false) private int vehicleId; @Column(nullable = false, length = 17) private String vin; @Temporal(TemporalType.TIMESTAMP) @Column(name = "initial_registration") private Date initialRegistration; // Getter和Setter省略 public int getVehicleId() { return vehicleId; } public void setVehicleId(int vehicleId) { this.vehicleId = vehicleId; } public String getVin() { return vin; } public void setVin(String vin) { this.vin = vin; } public Date getInitialRegistration() { return initialRegistration; } public void setInitialRegistration(Date initialRegistration) { this.initialRegistration = initialRegistration; } }
期望生成的SQL:
select temp.vehicle_id as y0_, temp.vin as y1_,temp.initial_registration as y2_ from vehicle temp where temp.vin=?
实际生成的SQL:
select this_.vehicle_id as y0_, this_.vin as y1_,this_.initial_registration as y2_ from vehicle this_ where this_.vin=?
可以看到,vehicle表的别名依然是this_,而非自定义的temp。
深入剖析:Hibernate内部别名机制
为了理解为何自定义别名会被覆盖,我们需要深入研究Hibernate在处理Criteria查询时内部别名生成的逻辑。问题根源在于Hibernate 3.6.x版本中CriteriaQueryTranslator类的createCriteriaSQLAliasMap()方法。
当执行Criteria查询的list()方法时,它会通过SessionImpl调用CriteriaLoader,进而创建CriteriaQueryTranslator对象。在CriteriaQueryTranslator的构造函数中,会调用createCriteriaSQLAliasMap()方法来设置查询中各个Criteria对象的SQL别名。
createCriteriaSQLAliasMap()方法的核心逻辑如下(简化版,基于问题描述中的源码片段):
private void createCriteriaSQLAliasMap() {
int i = 0;
// 遍历所有Criteria对象(包括根Criteria和关联Criteria)
Iterator criteriaIterator = criteriaEntityNames.entrySet().iterator();
while ( criteriaIterator.hasNext() ) {
Map.Entry me = ( Map.Entry ) criteriaIterator.next();
Criteria crit = ( Criteria ) me.getKey(); // Criteria对象作为key
String alias = crit.getAlias(); // 获取用户为该Criteria设置的别名
if ( alias == null ) {
alias = ( String ) me.getValue(); // 如果用户未设置,则使用实体名作为别名
}
// 将Criteria对象及其生成的SQL别名存入map
criteriaSQLAliasMap.put( crit, StringHelper.generateAlias( alias, i++ ) );
}
// 关键步骤:在这里,根Criteria的别名被强制设置为rootSQLAlias
criteriaSQLAliasMap.put( rootCriteria, rootSQLAlias );
}根据上述代码分析:
- 第一次赋值:在while循环中,当处理到根Criteria对象时,crit.getAlias()会获取到用户设置的自定义别名(例如temp)。此时,criteriaSQLAliasMap.put(crit, StringHelper.generateAlias("temp", 0))会将根Criteria对象作为键,temp0_(或其他基于temp生成的别名)作为值存入criteriaSQLAliasMap。
- 第二次赋值(覆盖):while循环结束后,紧接着执行的criteriaSQLAliasMap.put( rootCriteria, rootSQLAlias );是问题的关键。这里的rootCriteria与while循环中处理的根Criteria对象是同一个实例。rootSQLAlias是Hibernate内部为根实体预设的默认别名(通常是this_)。由于Map不允许重复的键,这次put操作会使用相同的rootCriteria键,将之前用户自定义的别名值(temp0_)替换为默认的rootSQLAlias(this_)。
因此,无论用户在createCriteria()中设置了什么别名,对于根实体而言,最终都会被CriteriaQueryTranslator内部逻辑强制覆盖为默认的this_。
解决方案与应对策略
鉴于此行为是Hibernate 3.6.x版本内部实现所致,我们可以采取以下几种策略来应对:
1. 接受默认别名并调整查询
最直接的解决方案是接受根实体别名始终为this_的现实,并相应地调整查询逻辑。在Criteria API中,对属性的引用通常是直接通过属性名完成的,Hibernate会自动将其映射到正确的表别名。
// 即使表别名是this_,以下代码通常也能正常工作
// Projections.property("vin") 会被Hibernate内部翻译为 this_.vin
projectionList.add(Projections.property("vin"), "vin");
cr.add(Restrictions.eq("vin", "WVW29343249702776"));对于大多数简单的查询,这种方法是可行的,因为它不影响查询的正确性。自定义别名更多是为了SQL的可读性或与外部工具的集成。
2. 升级Hibernate版本
这是一个长期的、推荐的解决方案。Hibernate的后续版本(如Hibernate 4.x、5.x及更高版本)对Criteria API进行了改进,并且其内部别名处理机制可能已经优化,能够正确支持根实体的自定义别名。升级可以解决许多旧版本中存在的内部问题,并带来性能和功能上的提升。
在升级之前,请务必仔细阅读目标版本的迁移指南,以了解可能存在的API变更和兼容性问题。
3. 考虑HQL或原生SQL
如果对SQL别名有严格的控制需求,且无法升级Hibernate版本,那么HQL(Hibernate Query Language)或原生SQL提供了更直接的别名控制能力。
使用HQL示例:
// HQL允许你直接在FROM子句中指定别名
String hql = "SELECT v.vehicleId, v.vin, v.initialRegistration " +
"FROM Vehicle v WHERE v.vin = :vin";
List vehicles = getSession().createQuery(hql, Vehicle.class)
.setParameter("vin", "WVW29343249702776")
.list(); 在HQL中,你可以自由地为实体指定别名(如Vehicle v中的v),并且这个别名会在生成的SQL中得到体现。
使用原生SQL示例:
// 原生SQL提供完全的控制
String sql = "SELECT temp.vehicle_id as vehicleId, temp.vin as vin, temp.initial_registration as initialRegistration " +
"FROM vehicle temp WHERE temp.vin = :vin";
List vehicles = getSession().createNativeQuery(sql, Vehicle.class)
.setParameter("vin", "WVW29343249702776")
.list(); 原生SQL允许你完全控制SQL语句的每一个细节,包括表别名。然而,使用原生SQL会牺牲一部分ORM的便利性,例如实体映射、跨数据库兼容性等。
注意事项
- 版本特异性:本文讨论的问题主要存在于Hibernate 3.6.x等较旧的版本。在排查问题时,首先确认您正在使用的Hibernate版本。
- ResultTransformer的影响:Transformers.aliasToBean()通常依赖于ProjectionList中定义的投影别名(例如projectionList.add(Projections.property("vehicleId"), "vehicleId")),而不是底层的表别名。只要投影别名与Java Bean的属性名匹配,aliasToBean就能正常工作,不受根实体表别名是this_还是temp的影响。
- 代码可读性:虽然默认别名this_在功能上通常没有问题,但自定义别名可以提高SQL语句的可读性,尤其是在复杂的查询或调试时。
总结
Hibernate 3.6.x版本中根实体自定义别名失效的问题,是由于其内部CriteriaQueryTranslator在构建SQL别名映射时,会用默认的rootSQLAlias(通常是this_)覆盖用户为根Criteria设置的自定义别名。理解这一内部机制有助于我们更好地诊断和解决问题。
针对这一问题,最推荐的解决方案是升级到更新的Hibernate版本,因为这不仅可以解决别名问题,还能带来更多功能和性能优化。如果升级不可行,则可以接受默认别名并相应地调整代码,或者在对别名控制有严格要求时,考虑使用HQL或原生SQL作为替代方案。在选择解决方案时,应权衡开发效率、代码可读性、维护成本和对SQL别名控制的实际需求。










