1. 概述
本文将演示如何使用 Spring 和 Hibernate 实现数据访问对象(DAO)。关于核心的 Hibernate 配置,可参考之前的 Hibernate 5 与 Spring 集成 文章。
本文的重点是:✅ 如何摆脱过时的模板类,✅ 利用现代 Hibernate 和 Spring 的集成机制,写出更简洁、低耦合的 DAO 层。
2. 不再需要 Spring 模板
从 Spring 3.0 和 Hibernate 3.0.1 开始,**HibernateTemplate
已不再是必须的组件**。这主要得益于 Hibernate 原生支持的“上下文会话(contextual sessions)”机制。
✅ 核心变化:Hibernate 现在可以直接管理 Session 的生命周期,只要在事务范围内,
SessionFactory.getCurrentSession()
返回的 Session 由 Spring 自动绑定和管理。
这意味着你可以直接使用原生 Hibernate API,不再依赖 HibernateTemplate
。带来的好处是:
- ❌ 脱离 Spring 模板的封装,减少一层间接调用
- ✅ DAO 层对 Spring 的依赖大幅降低,更容易迁移或测试
- ✅ 代码更直观,接近标准 Hibernate 写法
2.1 没有 HibernateTemplate
,异常还能转换吗?
以前 HibernateTemplate
的一个关键职责是:将 Hibernate 底层异常(如 HibernateException
)转换为 Spring 的统一数据访问异常体系(如 DataAccessException
)。
⚠️ 你可能会担心:不用模板了,异常翻译还有效吗?
答案是:依然有效,但机制变了。
只要你的 DAO 类标注了 @Repository
注解,Spring 会在背后自动启用异常翻译。原理如下:
- Spring 使用一个
BeanPostProcessor
扫描所有@Repository
标记的 Bean - 自动为它们织入(advise)所有可用的
PersistenceExceptionTranslator
(如HibernateExceptionTranslator
) - 所以你仍然能捕获到
DataAccessException
,而不是原始的 Hibernate 异常
📌 注意事项:
- ❌ DAO 类不能声明为
final
,否则 Spring 无法创建代理 - ✅ 推荐使用
@Repository
,它不仅是组件扫描标记,还启用了异常翻译
2.2 没有模板,Session 怎么管理?
Hibernate 的 SessionFactory.getCurrentSession()
是关键。它返回一个“当前会话”,这个会话:
- ✅ 与当前事务绑定
- ✅ 事务提交/回滚后自动关闭
- ✅ 无需手动 open/close
Spring 通过 HibernateTransactionManager
与 Hibernate 协作,确保 Session 的生命周期由事务驱动。
📚 官方说明(摘自 Hibernate 文档): “从 Hibernate 3.0.1 起,事务性数据访问代码可以直接使用原生 Hibernate 风格。因此,新项目建议直接基于
SessionFactory#getCurrentSession()
编写 DAO。”
换句话说:HibernateTemplate
已成为历史,现在是时候拥抱原生风格了。
3. DAO 层实现
我们从一个通用抽象 DAO 开始,封装基本的 CRUD 操作,便于复用。
3.1 抽象 DAO 基类
public abstract class AbstractHibernateDao<T extends Serializable> {
private Class<T> clazz;
@Autowired
protected SessionFactory sessionFactory;
public final void setClazz(final Class<T> clazzToSet) {
clazz = Preconditions.checkNotNull(clazzToSet);
}
// API
public T findOne(final long id) {
return (T) getCurrentSession().get(clazz, id);
}
public List<T> findAll() {
return getCurrentSession().createQuery("from " + clazz.getName()).list();
}
public T create(final T entity) {
Preconditions.checkNotNull(entity);
getCurrentSession().saveOrUpdate(entity);
return entity;
}
public T update(final T entity) {
Preconditions.checkNotNull(entity);
return (T) getCurrentSession().merge(entity);
}
public void delete(final T entity) {
Preconditions.checkNotNull(entity);
getCurrentSession().delete(entity);
}
public void deleteById(final long entityId) {
final T entity = findOne(entityId);
Preconditions.checkState(entity != null);
delete(entity);
}
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
📌 关键点解析:
- ✅ 无模板依赖:直接注入
SessionFactory
,通过getCurrentSession()
获取会话 - ✅ 泛型支持:通过
setClazz()
传入实体类型,避免每个子类重复指定 - ✅ 事务感知:
getCurrentSession()
返回的 Session 自动参与当前事务 - ⚠️ Preconditions:使用 Google Guava 的
checkNotNull
和checkState
做参数校验,避免空指针(你也可以用 Assert 工具类替代)
3.2 具体 DAO 实现示例
以 Foo
实体为例,实现具体 DAO:
@Repository
public class FooDAO extends AbstractHibernateDao<Foo> implements IFooDAO {
public FooDAO() {
setClazz(Foo.class);
}
}
📌 说明:
- ✅
@Repository
:标识为数据访问组件,启用异常翻译 - ✅ 构造函数中调用
setClazz(Foo.class)
,完成泛型绑定 - ✅ 实现了
IFooDAO
接口(可根据项目规范决定是否需要接口)
💡 小贴士:如果你的项目大量使用泛型 DAO,可以考虑将
setClazz
提取到子类注解中(如自定义注解 + 处理器),但对大多数项目,当前写法已足够简洁。
4. 总结
本文展示了如何使用 Spring 3 和 Hibernate 实现现代化的 DAO 层:
- ✅ **摒弃
HibernateTemplate
**:直接使用SessionFactory.getCurrentSession()
,代码更简洁,耦合更低 - ✅ 异常翻译依然有效:依赖
@Repository
和 Spring 的自动代理机制 - ✅ 事务管理无缝集成:Session 与事务绑定,无需手动管理生命周期
- ✅ 最终成果:一个轻量、干净、几乎不依赖 Spring 编译时 API 的 DAO 层
📌 适用场景:
- 新项目推荐直接采用此模式
- 老项目若仍在使用
HibernateTemplate
,可逐步迁移,风险可控
完整示例代码已托管至 GitHub:
https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa-2
踩坑提醒:迁移时务必检查事务配置(
@Transactional
是否启用),否则getCurrentSession()
会抛异常——这是最常见的配置遗漏点。