一、阅读线索
开始我们今天的对Spring的【模块阅读】,来看看Data Access的JDBC模块是怎么设计的。
源码阅读前,我们要先思考下本次的阅读线索:
- JDBC模块有什么作用
- 该模块是怎么设计的
- 我们从这个模块可以学到什么
二、探索
关于阅读线索一,我们可以很轻松地从官网文档里找到答案。
Data Access with JDBC
也就是提供了数据访问的能力,我们可以使用其来进行各种数据库访问与操作。
先整体看下代码的组织结构,探索下阅读线索二。
主要分为了4个部分,结合官方文档,我们总结下。
core
:包含JdbcTemplate
及其各种回调接口。datasource
:包含一个易于DataSource
访问的实用程序类和各种简单的DataSource
实现。子包embedded
提供了对使用 Java 数据库引擎(例如 HSQL、H2 和 Derby)创建嵌入式数据库的支持。object
:包含将 RDBMS 查询、更新和存储过程表示为线程安全、可重用对象的类。support
:提供SQLException
翻译功能和一些实用程序类。
也就是说,核心设计为datasource包提供数据源的访问实现,而core包提供JDBC规范的数据库访问模板。
1.数据源访问实现
先来看看datasource是如何实现数据源访问的,既然是要访问,那首先需要初始化,再建立连接。
我们定位初始化类DataSourceInitializer的exectute方法。
public class DataSourceInitializer implements InitializingBean, DisposableBean {
@Nullable
private DataSource dataSource;
/**
* The {@link DataSource} for the database to populate when this component
* is initialized and to clean up when this component is shut down.
* <p>This property is mandatory with no default provided.
* @param dataSource the DataSource
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private void execute(@Nullable DatabasePopulator populator) {
Assert.state(this.dataSource != null, "DataSource must be set");
if (this.enabled && populator != null) {
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
}
}
发现其实数据源是从外部传入的,可以看到DatabasePopulatorUtils.execute的入参是dataSource。
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String var1, String var2) throws SQLException;
PrintWriter getLogWriter() throws SQLException;
void setLogWriter(PrintWriter var1) throws SQLException;
void setLoginTimeout(int var1) throws SQLException;
int getLoginTimeout() throws SQLException;
default ConnectionBuilder createConnectionBuilder() throws SQLException {
throw new SQLFeatureNotSupportedException("createConnectionBuilder not implemented");
}
}
DataSource对象提供了获取连接的接口,我们来看看是如何获取到连接,从而实现数据源访问的。
查看getConnection方法的子类实现。
那获取连接的具体实现应该就在子类AbstractDriverBasedDataSource中。
/**
* Getting a Connection using the nasty static from DriverManager is extracted
* into a protected method to allow for easy unit testing.
* @see java.sql.DriverManager#getConnection(String, java.util.Properties)
*/
protected Connection getConnectionFromDriverManager(String url, Properties props) throws SQLException {
return DriverManager.getConnection(url, props);
}
我们最终在AbstractDriverBasedDataSource的调用链上找到了该方法。可以看到是通过传入数据库url和各类参数如密码等来获取连接,从而实现数据源的访问。
2.数据源访问模板
到这我们就完成了线索二的第一部分,我们再看看看线索二的下一部分:
core包提供JDBC规范的数据库访问模板。
我们直接找到JdbcTemplate实现类,看下类的组织结构。
这其中的每一个查询、更新方法都是遵循JDBC规范的模板方法,而Spring就是通过这些模板方法来对数据源数据进行操作。
到这我们就解决了阅读线索二。
阅读线索二:该模块是怎么设计的
jdbc模块的核心设计分为两部分:
- 一是实现对数据源的访问连接。
- 二是通过JDBC规范的模板对数据源数据进行操作。
三、总结
我们再来看看阅读线索三,这方面我们从设计模式进行入手。
阅读线索三:从这个模块可以学到什么
我们看下以下代码,PreparedStatement实例的是由PreparedStatementCreator实现的。
@Nullable
private <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
......
}
再来看看PreparedStatementCreator接口,一共有三个子类实现。
@FunctionalInterface
public interface PreparedStatementCreator {
/**
* Create a statement in this connection. Allows implementations to use
* PreparedStatements. The JdbcTemplate will close the created statement.
* @param con the connection used to create statement
* @return a prepared statement
* @throws SQLException there is no need to catch SQLExceptions
* that may be thrown in the implementation of this method.
* The JdbcTemplate class will handle them.
*/
PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}
也就是说PreparedStatement的三种不同实现被封装到三个子类中,而具体需要哪种实现,只需要传入不同的PreparedStatementCreator实现即可。
这种把不同实现封装起来,需要哪种实现由行为模式决定,也就是入参决定,这种模式被称为策略模式。
而createPreparedStatement接口也称为策略接口。
未完待续。。。
好了,今天的分享就到这📚。大家能否感受到通过按【模块阅读】这种方式来阅读源码的乐趣呢 !
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️