Hibernate 3懒加载问题:调用关联对象get方法未触发SQL查询
最近在处理Hibernate映射对象的数据加载时踩了个坑:用org.hibernate.Criteria查询Document实体后,每个Document实例的documentStatut字段仅设置了idStatut属性,其他属性都是空的。按道理*懒加载(FetchType.LAZY)*的话,调用document.getDocumentStatut()应该会触发SQL查询加载完整的DocumentStatut实例才对,但实际完全没反应,非得手动执行查询才能拿到完整数据。
实体类代码示例
Document类
@Entity @Table(name = "DOCUMENT") public class Document implements java.io.Serializable { private static final long serialVersionUID = -5160452300417105888L; private Long idDocument; private Dossier dossier; private DocumentStatut documentStatut; @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "Document_seq_gen") @SequenceGenerator(name = "Document_seq_gen", sequenceName = "SEQUENCE_DOCUMENT") @Column(name = "ID_DOCUMENT", unique = true, nullable = false, precision = 10, scale = 0) public Long getIdDocument() { return this.idDocument; } public void setIdDocument(final Long idDocument) { this.idDocument = idDocument; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ID_DOSSIER", nullable = false) public Dossier getDossier() { return this.dossier; } public void setDossier(final Dossier dossier) { this.dossier = dossier; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ID_STATUT", nullable = false) public DocumentStatut getDocumentStatut() { return this.documentStatut; } public void setDocumentStatut(final DocumentStatut documentStatut) { this.documentStatut = documentStatut; } }
DocumentStatut类
@Entity @Table(name = "DOCUMENT_STATUT") public class DocumentStatut implements java.io.Serializable { private Long idStatut; private String libStatut; @Id @Column(name = "ID_STATUT", unique = true, nullable = false, precision = 10, scale = 0) public Long getIdStatut() { return this.idStatut; } public void setIdStatut(final Long idStatut) { this.idStatut = idStatut; } @Column(name = "LIB_STATUT", length = 30) public String getLibStatut() { return this.libStatut; } public void setLibStatut(final String libStatut) { this.libStatut = libStatut; } }
使用环境
- Hibernate 3.3.2.GA
- Websphere 8.5
- Spring 3.2.6.RELEASE
- Java 7
问题原因分析
最常见的原因是Session/事务已经关闭:Hibernate的懒加载依赖于活跃的Session,当你用Criteria查询完Document后,如果Session已经被关闭(比如事务提交后Spring自动关闭了Session),此时调用document.getDocumentStatut()就无法触发SQL查询——因为Hibernate没有可用的数据库连接去执行查询,只会返回一个仅包含ID的代理对象(这个ID是从Document的外键字段直接获取的,不需要查询数据库)。
另外,Hibernate 3.3.x的动态代理机制在Session关闭后,调用代理对象的getter方法不会抛出LazyInitializationException(部分环境下可能),只会返回null或者仅有的ID属性,这也会让你误以为懒加载没触发。
解决方案
1. 确保在事务/Session范围内访问懒加载属性
调整代码逻辑,把需要访问documentStatut属性的操作放在Spring事务方法内部执行。比如:
@Transactional public void processDocument(Long docId) { Document doc = documentDao.getById(docId); // 这里Session还活跃,调用getDocumentStatut会触发懒加载 DocumentStatut statut = doc.getDocumentStatut(); System.out.println(statut.getLibStatut()); // 正常获取值 }
2. 手动初始化懒加载对象
如果必须在事务外访问关联对象,可以在事务内提前用Hibernate.initialize()初始化:
@Transactional public Document getDocumentWithStatut(Long docId) { Document doc = documentDao.getById(docId); // 提前初始化关联对象 Hibernate.initialize(doc.getDocumentStatut()); return doc; } // 事务外访问时,documentStatut已经是完整对象了
3. 在Criteria中强制关联查询
修改Criteria查询,直接join加载DocumentStatut,避免懒加载:
Criteria criteria = session.createCriteria(Document.class); // 使用FetchMode.JOIN强制左连接加载 criteria.setFetchMode("documentStatut", FetchMode.JOIN); // 或者创建别名并指定连接类型 criteria.createAlias("documentStatut", "ds", JoinType.LEFT_OUTER_JOIN); List<Document> docs = criteria.list();
4. 调整FetchType为EAGER(不推荐)
如果这个关联在绝大多数场景下都需要加载,可以把@ManyToOne的fetch属性改成FetchType.EAGER,但这会导致每次查询Document都自动加载DocumentStatut,可能影响查询性能:
@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "ID_STATUT", nullable = false) public DocumentStatut getDocumentStatut() { return this.documentStatut; }
内容的提问来源于stack exchange,提问作者ajnfde

