SpringBoot 中 JdbcTemplate 数据访问的实现原理
通过 JdbcTemplate 不仅简化了数据库操作,还避免了使用原生 JDBC 带来的代码复杂度和冗余性问题。
那么,JdbcTemplate 在 JDBC 基础上如何实现封装的呢?本文从设计思想出发,讨论 JDBC API 到 JdbcTemplate 的演进过程,并剖析 JdbcTemplate 的部分核心源码。
1)从模板方法模式和回调机制说起
从命名上 JdbcTemplate 显然是一种模板类,为了更好地理解 JdbcTemplate 的实现原理,先对这一设计模式进行简单说明。
1.1 模板方法设计模式
模板方法模式的原理非常简单,主要是利用了面向对象中类的继承机制,目前应用非常广泛且在实现上往往与抽象类一起使用,比如 Spring 框架中也大量应用了模板方法实现基类和子类之间的职责分离和协作。
按照定义,完成一系列步骤时,这些步骤需要遵循统一的工作流程,个别步骤的实现细节除外,这时就需要考虑使用模板方法模式处理了。模板方法模式的结构示意图如下所示:
上图中抽象模板类 AbstractClass 定义了一套工作流程,而具体实现类 ConcreteClassA 和 ConcreteClassB 对工作流程中的某些特定步骤进行了实现。
1.2 回调机制
在软件开发过程中,回调(Callback)是一种常见的实现技巧,回调的含义如下图所示:
上图中,ClassA 的 operation1() 方法调用 ClassB 的 operation2() 方法,ClassB 的 operation2() 方法执行完毕再主动调用 ClassA 的 callback() 方法,这就是回调机制,体现的是一种双向的调用方式。
从上面描述可以看到,回调在任务执行过程中不会造成任何的阻塞,任务结果一旦就绪,回调就会被执行,显然在方法调用上这是一种异步执行的方式。同时,回调还是实现扩展性的一种简单而直接的模式。
在上图中可以看到执行回调时,代码会从一个类中的某个方法跳到另一个类中的某个方法,这种思想同样可以扩展到组件级别,即代码从一个组件跳转到另一个组件。只要预留回调的契约,原则上可以实现运行时根据调用关系动态来实现组件之间的跳转,从而满足扩展性的要求。事实上,JdbcTemplate 正是基于模板方法模式和回调机制,才真正解决了原生 JDBC 中的复杂性问题。
接下来,结合上文《SpringBoot 中使用 JdbcTemplate 访问关系型数据库》中给出的 SpringCSS 案例场景从 JDBC 的原生 API 出发,讨论 JdbcTemplate 的演进过程。
1.3 JDBC API 到 JdbcTemplate 的演变
在整个过程中,不难发现创建 DataSource、获取 Connection、创建 Statement 等步骤实际上都是重复的,只有处理 ResultSet 部分需要针对不同的 SQL 语句和结果进行定制化处理,因为每个结果集与业务实体之间的对应关系不同。
首先想到的是如何构建一个抽象类实现模板方法:
在 JDBC API 中添加模板方法模式
假设将这个抽象类命名为 AbstractJdbcTemplate,那么该类的代码结构应该是这样的:
public abstract class AbstractJdbcTemplate{
@Autowired
private DataSource dataSource;
public final Object execute(String sql) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
Object object = handleResultSet(resultSet);
return object;
} catch (SQLException e) {
System.out.print(e);
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
}
}
if (connection != null) {
try {
connection.close