基本思路分析
Spring JDBC 对数据库的操作大部分都通过 jdbcTemplete 实现,这也是我们分析 Spring JDBC 的切入点。一个简单的 update 函数的调用例程如下:
public void save(User user) {
jdbcTemplate.update("insert into user(name, age, sex) values(?, ?, ?)",
new Object[] {user.getName(), user.getAge(), user.getSex()},
new int[] {Types.VARCHAR, Types.INTEGER, Types.VARCHAR});
}
我们只需要提供 sql 语句和对应的参数和参数类型就可以实现对应的 update 功能。进入 update
函数可以看到如下内容:
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
}
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
return update(new SimplePreparedStatementCreator(sql), pss);
}
通过 ArgTypePreparedStatementSetter
以及 SimplePreparedStatementCreator
分别对参数与参数类型、SQL 语句进行封装。在此之后就进入的核心代码:
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
return updateCount(execute(psc, ps -> {
try {
if (pss != null) {
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
其核心就是调用了 execute
函数实现的基础操作,其他的数据库操作如 query 等方法就是传入不同的 PreparedStatementCallback
参数来执行不同的操作。
基础方法 execute
execute
函数作为数据库操作的核心入口,将大多数数据库操作相同的步骤同意分装,而将个性花得操作使用参数 PreparedStatementCallback
进行回调。(利用了模板方法的设计原则)
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
...
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
// 应用用户设定的输入参数
applyStatementSettings(ps);
// 调用回调函数
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// 释放数据库连接,避免异常转换器没有被初始化的时候可能出现的连接池死锁
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
获取数据库连接
数据库操作的第一步就是获取连接,这步中不仅仅是简单的 get 动作,还考虑的一些特殊情况。
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
// 当前线程支持同步
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
// 记录数据库连接
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
catch (RuntimeException ex) {
// Unexpected exception from external delegation call -> close Connection and rethrow.
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
此处主要考虑的是事务层面的需求,Sping 需要保证线程中的数据库操作都是使用同一个事务连接。
应用用户设定的输入参数
protected void applyStatementSettings(Statement stmt) throws SQLException {
int fetchSize = getFetchSize();
if (fetchSize != -1) {
stmt.setFetchSize(fetchSize);
}
int maxRows = getMaxRows();
if (maxRows != -1) {
stmt.setMaxRows(maxRows);
}
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
}
fetchSize
是为了减少网络交互次数设计的,访问 ResultSet 时,如果每一行都需要从服务器读取,会造成大量的开销。fetchSize
可以一次获取多行,这样在获取下一行数据的时候就可以直接从内存中获取,而不需要网络交互,提高了效率。但这个设置也会导致更大的内存使用量。
maxRows
规定了当前 Statement 对象生成的所有 ResultSet 对象中的最大行数。
调用回调函数
使用不同情况下的个性化操作,即调用 PreparedStatementCallback
类型的 doInPreparedStatement
函数。
警告处理
protected void handleWarnings(Statement stmt) throws SQLException {
// 当设置为忽略警告的时候,只打印日志
if (isIgnoreWarnings()) {
if (logger.isDebugEnabled()) {
// 日志开启
SQLWarning warningToLog = stmt.getWarnings();
while (warningToLog != null) {
logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +
warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
warningToLog = warningToLog.getNextWarning();
}
}
}
else {
handleWarnings(stmt.getWarnings());
}
}
SQLWarning
类型用于承载数据库访问警告信息。警告可以来自 Connection、Statement
和 ResultSet。出现警告很可能会出现数据错误,但程序的执行不一定会受到影响,所以用户可以自定义处理警告的方式,默认是忽略警告,只打印日志,我们也可以设置为抛出异常。
资源释放
数据库连接释放不是直接调用 close
方法。考虑到事务的情况,如果当前线程存在事务,那么说明在当前线程中存在共用数据库连接,这种情况下直接使用 ConnectionHolder 中的 release方法进行连接数减一,而没有真正释放。
public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) {
try {
doReleaseConnection(con, dataSource);
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
if (con == null) {
return;
}
if (dataSource != null) {
// 当前线程存在事务,则使用 ConnectionHolder 中的 release 方法
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && connectionEquals(conHolder, con)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
return;
}
}
doCloseConnection(con, dataSource);
}
Update 中的回调函数
回顾一下,update 使用的回调函数。
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
return updateCount(execute(psc, ps -> {
try {
if (pss != null) {
// 设置 PreparedStatement 所需要的全部参数
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
上述代码的核心之一在于通过 PreparedStatementSetter
设置了相关的参数,不需要繁琐的配置,实际JDBC的操作是比较繁琐的。对于以下的内容:
jdbcTemplate.update("insert into user(name, age, sex) values(?, ?, ?)",
new Object[] {user.getName(), user.getAge(), user.getSex()},
new int[] {Types.VARCHAR, Types.INTEGER, Types.VARCHAR});
需要这么做:
PreparedStatement updateState = con.prepareStatement("insert into user(name, age, sex) values(?, ?, ?)");
updateState.setString(1, user.getName());
updateState.setInt(2, user.getAge());
updateState.setString(3, user.getSex());
那么 Spring 是如何简化操作的呢?
首先,从 pss.setValues(ps) 开始。
public void setValues(PreparedStatement ps) throws SQLException {
int parameterPosition = 1;
if (this.args != null && this.argTypes != null) {
for (int i = 0; i < this.args.length; i++) {
Object arg = this.args[i];
if (arg instanceof Collection && this.argTypes[i] != Types.ARRAY) {
Collection<?> entries = (Collection<?>) arg;
for (Object entry : entries) {
if (entry instanceof Object[]) {
Object[] valueArray = ((Object[]) entry);
for (Object argValue : valueArray) {
doSetValue(ps, parameterPosition, this.argTypes[i], argValue);
parameterPosition++;
}
}
else {
doSetValue(ps, parameterPosition, this.argTypes[i], entry);
parameterPosition++;
}
}
}
else {
doSetValue(ps, parameterPosition, this.argTypes[i], arg);
parameterPosition++;
}
}
}
}
其中 doSetValue(ps, parameterPosition, this.argTypes[i], entry); 完成了对单个参数的匹配。