Skip to content

DAO 支持 (DAO Support)

Spring 中的数据访问对象 (DAO) 支持旨在简化以一致的方式使用数据访问技术(如 JDBC、Hibernate 或 JPA)。这使你可以相当轻松地在上述持久化技术之间进行切换,并且还可以让你编写代码而无需担心捕获特定于每种技术的异常。

一致的异常层次结构 (Consistent Exception Hierarchy)

Spring 提供了一种从技术特定的异常(如 SQLException)到其自己的异常类层次结构(以 DataAccessException 作为根异常)的便捷转换。这些异常包装了原始异常,因此你永远不会丢失有关可能出错的任何信息。

除了 JDBC 异常之外,Spring 还可以包装 JPA 和 Hibernate 特定的异常,将它们转换为一组集中的运行时异常 (Runtime Exceptions)。这使你可以在适当的层中处理大多数不可恢复的持久化异常,而无需在 DAO 中包含烦人的样板 catch-and-throw 块和异常声明。(当然,你仍然可以在任何需要的地方捕获和处理异常。)如上所述,JDBC 异常(包括特定于数据库方言的异常)也被转换为相同的层次结构,这意味着你可以在一致的编程模型中使用 JDBC 执行某些操作。

前面的讨论适用于 Spring 对各种 ORM 框架支持中的各种模板类。如果你使用基于拦截器的类,则应用程序必须通过委托给 SessionFactoryUtilsconvertHibernateAccessException(..)convertJpaAccessException(..) 方法来自己处理 HibernateExceptionsPersistenceExceptions。这些方法将异常转换为与 org.springframework.dao 异常层次结构中的异常兼容的异常。由于 PersistenceExceptions 是非受检异常 (unchecked),它们也可以被抛出(不过在异常方面牺牲了通用的 DAO 抽象)。

下图显示了 Spring 提供的异常层次结构。(请注意,图中详细说明的类层次结构仅显示了整个 DataAccessException 层次结构的一个子集。)

DataAccessException

用于配置 DAO 或 Repository 类的注解

保证你的数据访问对象 (DAO) 或存储库 (Repository) 提供异常转换的最佳方法是使用 @Repository 注解。此注解还允许组件扫描支持查找和配置你的 DAO 和存储库,而无需为它们提供 XML 配置条目。以下示例显示了如何使用 @Repository 注解:

java
@Repository // (1)
public class SomeMovieFinder implements MovieFinder {
	// ...
}
kotlin
@Repository // (1)
class SomeMovieFinder : MovieFinder {
	// ...
}

(1) @Repository 注解。

任何 DAO 或存储库实现都需要访问持久化资源,具体取决于所使用的持久化技术。例如,基于 JDBC 的存储库需要访问 JDBC DataSource,而基于 JPA 的存储库需要访问 EntityManager。实现此目的的最简单方法是使用 @Autowired, @Inject, @Resource@PersistenceContext 注解之一来注入此资源依赖项。以下示例适用于 JPA 存储库:

java
@Repository
public class JpaMovieFinder implements MovieFinder {

	@PersistenceContext
	private EntityManager entityManager;

	// ...
}
kotlin
@Repository
class JpaMovieFinder : MovieFinder {

	@PersistenceContext
	private lateinit var entityManager: EntityManager

	// ...
}

如果你使用经典的 Hibernate API,你可以注入 SessionFactory,如下例所示:

java
@Repository
public class HibernateMovieFinder implements MovieFinder {

	private SessionFactory sessionFactory;

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	// ...
}
kotlin
@Repository
class HibernateMovieFinder(private val sessionFactory: SessionFactory) : MovieFinder {
	// ...
}

我们展示的最后一个例子是针对典型的 JDBC 支持。你可以将 DataSource 注入到初始化方法或构造函数中,并在其中使用此 DataSource 创建 JdbcTemplate 和其他数据访问支持类(如 SimpleJdbcCall 等)。以下示例自动装配了一个 DataSource

java
@Repository
public class JdbcMovieFinder implements MovieFinder {

	private JdbcTemplate jdbcTemplate;

	@Autowired
	public void init(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	// ...
}
kotlin
@Repository
class JdbcMovieFinder(dataSource: DataSource) : MovieFinder {

	private val jdbcTemplate = JdbcTemplate(dataSource)

	// ...
}

注意

有关如何配置应用程序上下文以利用这些注解的详细信息,请参阅每种持久化技术的具体说明。


补充教学

1. 为什么 DataAccessException 是运行时异常?

在 Java 早期,JDBC 强制要求捕获 SQLException(受检异常)。但这带来了两个大问题:

  1. 无法恢复:绝大多数数据库异常(如 SQL 语法错误、连接失败、约束冲突)在代码层面是无法自动恢复的。强制捕获只会导致到处都是 try-catch 打印日志,严重污染业务代码。
  2. 泄露实现细节:如果在 Service 层捕获 SQLException,那么 Service 层就与 JDBC 耦合了。将来切换到 JPA 或 NoSQL 时,所有异常处理代码都要重写。

Spring 的天才设计在于将所有底层的受检异常(Checked Exceptions)包装为 DataAccessException 及其子类,而它们都是 RuntimeExceptions (Unchecked)。 这不仅让代码变得干净,还践行了“只捕获你能处理的异常”这一最佳实践。

2. @Repository 的隐藏技能:异常转换

很多人认为 @Repository 只是 @Component 的一个别名,用来标记 DAO 层。 其实它不仅仅是一个标记。当你的 Bean 被标记为 @Repository 时,Spring 会自动注册一个 PersistenceExceptionTranslationPostProcessor它的作用是

  • 捕获 Bean 中抛出的所有原生异常(如 JPA 的 PersistenceException、Hibernate 的 HibernateException)。
  • 利用底层的异常转换器,将它们统一抛出为 Spring 的 DataAccessException 子类。

这意味着:你的 Service 层只需要处理 DataIntegrityViolationException(Spring 的异常),而不需要关心底层到底是 Hibernate 抛出的 ConstraintViolationException 还是 JDBC 抛出的 SQLIntegrityConstraintViolationException。实现了真正的技术解耦。

3. DAO 层的依赖注入最佳实践

虽然示例中展示了 Field Injection(@PersistenceContext private EntityManager em;)和 Setter Injection,但在现代 Spring 开发中,我们强烈推荐使用构造函数注入 (Constructor Injection)

java
@Repository
public class MyRepository {
    private final JdbcTemplate jdbcTemplate;

    // Spring 4.3+ 自动注入,无需 @Autowired
    public MyRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

优点:

  • 不可变性 (Immutability):字段可以设为 final
  • 测试友好:单元测试时可以直接 new MyRepository(mockTemplate),不需要启动 Spring 容器。

Based on Spring Framework.