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 的 checkNotNullcheckState 做参数校验,避免空指针(你也可以用 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() 会抛异常——这是最常见的配置遗漏点。


原始标题:The DAO with Spring 3 and Hibernate | Baeldung