目 录CONTENT

文章目录

spring-jdbcTemplate

FatFish1
2025-01-23 / 0 评论 / 0 点赞 / 76 阅读 / 0 字 / 正在检测是否收录...

JdbcTemplate和NamedParameterJdbcTemplate是spring提供的jdbc框架,前置知识点参考:

http://www.chymfatfish.cn/archives/spring-jdbc

分析JdbcTemplate源码

spring-jdbc体系源码

JdbcTemplate

JdbcTemplate类是spring-jdbc的对外统一接口

update

public int update(PreparedStatementCreator psc) throws DataAccessException {
    return update(psc, (PreparedStatementSetter) null);
}
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();
          }
       }
    }, true));
}

实际调用的是execute方法。在execute方法中创建并配置好ps,调用钩子方法

钩子方法的实现核心是pss.setValues(ps)int rows = ps.executeUpdate() ,即配置参数setValues,以及执行executeUpdate

配置参数可以通过PreparedStatement创建推断出来里面一定是通过ps.setxxx实现的,具体怎么实现则看PreparedStatementSetter

看这个代码架构,可以知道是这个钩子方法实际实现的是execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources) 中的PreparedStatementCallback即提前定义好了回调,方便开发者使用

query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper)

带参的query

public <T> T query(
       PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
       throws DataAccessException {
    Assert.notNull(rse, "ResultSetExtractor must not be null");
    logger.debug("Executing prepared SQL query");
    return execute(psc, new PreparedStatementCallback<T>() {
       @Override
       @Nullable
       public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
          ResultSet rs = null;
          try {
             if (pss != null) {
                pss.setValues(ps);
             }
             rs = ps.executeQuery();
             return rse.extractData(rs);
          }
          finally {
             JdbcUtils.closeResultSet(rs);
             if (pss instanceof ParameterDisposer) {
                ((ParameterDisposer) pss).cleanupParameters();
             }
          }
       }
    }, true);
}

跟update基本一致,差别在于doInPreparedStatement钩子方法中调用的执行方法是rs = ps.executeQuery(),然后调用return rse.extractData(rs)又基于rowMapper做了封装

其中比update多的就在于extractData方法对结果ResultSet的封装。这里上层创建的是RowMapperResultSetExtractor

query(final String sql, final ResultSetExtractor<T> rse)

不带参query

与带参query调用的execute方法有区别,其他是完全一致的

public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    Assert.notNull(rse, "ResultSetExtractor must not be null");
    if (logger.isDebugEnabled()) {
       logger.debug("Executing SQL query [" + sql + "]");
    }
    // Callback to execute the query.
    class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
       @Override
       @Nullable
       public T doInStatement(Statement stmt) throws SQLException {
          ResultSet rs = null;
          try {
             rs = stmt.executeQuery(sql);
             return rse.extractData(rs);
          }
          finally {
             JdbcUtils.closeResultSet(rs);
          }
       }
       @Override
       public String getSql() {
          return sql;
       }
    }
    return execute(new QueryStatementCallback(), true);
}

queryForObject(String sql, RowMapper<T> rowMapper)

public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
    return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}

底层调用到不带参query

除此之外就是提供了快捷的类型封装能力,即getSingleColumnRowMapper

protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) {
    return new SingleColumnRowMapper<>(requiredType);
}

它是通过SingleColumnRowMapper实现的,RowMapper的使用可以参考

http://www.chymfatfish.cn/archives/spring-jdbc#%E6%9F%A5%E8%AF%A2%E5%A4%9A%E5%88%97%E5%B9%B6%E5%B0%81%E8%A3%85%E6%88%90%E5%A4%8D%E6%9D%82%E5%AF%B9%E8%B1%A1

execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources)

带占位符预编译execute,是spring-jdbc体系中真正核心的方法

query、update等方法都是在底层调用execute方法

execute方法的实现有五个主要步骤:

获取连接

Connection con = DataSourceUtils.getConnection(obtainDataSource());

创建连接部分调用到DataSourceUtils#getConnection中,参考DataSourceUtils

获取可执行对象

ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);

获取ps同时完成各类设置,参考applyStatementSettings

这里的psc是PreparedStatementCreator,可以是自行实现的

执行钩子方法

T result = action.doInPreparedStatement(ps);

这里action是PreparedStatementCallback,也是可以自行实现的

处理告警

handleWarnings(ps);

为什么生成告警而非异常?

因为告警可能不会出现数据错误,不影响业务。例如DataTruncation直接继承SQLWarning,是由于某种原因意外地截断数据值时会以DataTruncation 警告形式报告异常。所以⽤户可以⾃⼰设置处理警告的⽅式,如默认的是忽略警告。

关闭连接

JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());

这里对事务进行了适配,在DataSourceUtils#releaseConnection 方法中,参考DataSourceUtils

applyStatementSettings

应用开发者设定的输入参数

  • setFetchSize参数设置最主要是为了减少⽹络交互次数设计的。访问ResultSet时,如果它每次只从服务器上读取⼀⾏数据,则会产⽣⼤量的开销。setFetchSize的意思是当调⽤rs.next时,ResultSet会⼀次性从服务器上取得多少⾏数据回来,这样在下次rs.next时,它可以直接从内存中获取数据⽽不需要⽹络交互,提⾼了效率。但使用不当可能导致内存上升

  • setMaxRows将此Statement对象⽣成的所有ResultSet对象可以包含的最⼤⾏数限制设置为给定数。

两个参数都可以在JdbcTemplate构造时通过set方法直接配置

DataSourceUtils

doGetConnection

doGetConnection方法是用于实际操作获取Connection的核心方法。与事务关联性非常强

这块事务的机制可以参考事务部分,补链接

首先第一块,如果当前线程存在一个绑定的Connection

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();
}

如果当前线程存在一个相应的Connection,那么则有当前的事务管理分配。

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 ...

从源代码中可以得出,如果不存在与当前线程绑定的Connection,则新建一个全新的Connection,如果当前线程的事务同步处于活动状态,那么为刚刚创建的Connection添加Spring事务管理的支持

doReleaseConnection

同样对事务做了适配

if (dataSource != null) {
	ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
	if (conHolder != null && connectionEquals(conHolder, con)) {
		// It's the transactional Connection: Don't close it.
		conHolder.released();
		return;
	}
}

当前线程存在事务的情况下说明存在共⽤数据库连接直接使用ConnectionHolder中的released⽅法进⾏连接数减⼀⽽不是真正的释放连接。

ArgumentTypePreparedStatemnetSetter

sql参数处理器

在一些传可变长度参数的方法中,使用ArgumentTypePreparedStatemnetSetter作为参数处理器,例如:

public int update(String sql, @Nullable Object... args) throws DataAccessException {
    return update(sql, newArgPreparedStatementSetter(args));
}

public <T> T query(String sql, @Nullable Object[] args, ResultSetExtractor<T> rse) throws DataAccessException {
	return query(sql, newArgPreparedStatementSetter(args), rse);
}

它里面的核心成员变量有

// 参数列表
private final Object[] args;
// 参数类型列表,常数项可以基于java.sql.Types
private final int[] argTypes;

以query方法为例,最终调用到如下方法:

public <T> T query(
       PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
       throws DataAccessException {
    Assert.notNull(rse, "ResultSetExtractor must not be null");
    logger.debug("Executing prepared SQL query");
    return execute(psc, new PreparedStatementCallback<T>() {
		@Override
		@Nullable
		public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
			ResultSet rs = null;
			try {
				if (pss != null) {
					pss.setValues(ps);
				}
				rs = ps.executeQuery();
				return rse.extractData(rs);
			}
			finally {
				JdbcUtils.closeResultSet(rs);
				if (pss instanceof ParameterDisposer) {
					((ParameterDisposer) pss).cleanupParameters();
				}
			}
		}
	}, true);
}

这里PreparedStatementSetter 的作用是在钩子方法中调用pss.setValues(ps) 配置参数

setValues

setValues是tade

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方法

protected void doSetValue(PreparedStatement ps, int parameterPosition, int argType, Object argValue)
       throws SQLException {
    StatementCreatorUtils.setParameterValue(ps, parameterPosition, argType, argValue);
}

调用到StatementCreatorUtils#setParameterValue 方法中,点我跳转补链接

StatementCreatorUtils

setParameterValueInternal

public static void setParameterValue(PreparedStatement ps, int paramIndex, int sqlType,
       @Nullable Object inValue) throws SQLException {
    setParameterValueInternal(ps, paramIndex, sqlType, null, null, inValue);
}

StatementCreatorUtils#setParameterValue 调用过来

方法中传入的inValue就是JdbcTemplate#updateJdbcTemplate#query方法参数二传递进来的new Object[]中元素的遍历,或者是Obejct... object的遍历。对于inValue,方法中做了两个判断方向:

如果是SqlParameterValue类型

if (inValue instanceof SqlParameterValue) {
    SqlParameterValue parameterValue = (SqlParameterValue) inValue;
    if (logger.isDebugEnabled()) {
       logger.debug("Overriding type info with runtime info from SqlParameterValue: column index " + paramIndex +
             ", SQL type " + parameterValue.getSqlType() + ", type name " + parameterValue.getTypeName());
    }
    if (parameterValue.getSqlType() != SqlTypeValue.TYPE_UNKNOWN) {
       sqlTypeToUse = parameterValue.getSqlType();
    }
    if (parameterValue.getTypeName() != null) {
       typeNameToUse = parameterValue.getTypeName();
    }
    inValueToUse = parameterValue.getValue();
}

否则调用setValue方法

if (inValueToUse == null) {
    setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse);
}
else {
    setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse);
}

setValue

到这里就已经可以看到选择对应的类型做对应的配置了,底层就是我们熟悉的PreparedStatement#setxxx方法。以VARCHAR为例:

else if (sqlType == Types.VARCHAR || sqlType == Types.LONGVARCHAR ) {
	ps.setString(paramIndex, inValue.toString());
}

这里判断传进来的如果是VARCHAR类型,就直接调用setString方法处理。选择的方法完全依赖update接口方法调用时的入参,因此这里对编码准确性有较强的要求。

而如果是INTEGER的数字类型,实际上是匹配不到任何一个类型的,最后会按setObject走

else {
	// Fall back to generic setObject call with SQL type specified.
	ps.setObject(paramIndex, inValue, sqlType);
}

ResultSetExtractor及实现类

ResultSetExtractor的不同实现类实现了对应的extractData数据解析方法

RowMapperResultSetExtractor

用于表多列封装,较多用于全表查询。核心成员变量:

// 开发自行实现的rowMapper实现类型
private final RowMapper<T> rowMapper;

参考其extractData方法:

while (rs.next()) {
	results.add(this.rowMapper.mapRow(rs, rowNum++));
}

核心部分其实是resultSet的循环。借助开发者自行实现的rowMappe实现类中的mapRow方法进行封装。把所有的参数校验、选取等都交给开发者自行处理。这里参考SingleColumnRowMapper,实现比较简单

RowMapper及实现类

SingleColumnRowMapper - 单行结果查询和封装rowMapper

核心成员变量包括:

// 开发者预期的类型,可能是基本类型,String、也可能是复杂的引用类型
private Class<?> requiredType;
// 转换器,正常情况下是默认转换器
private ConversionService conversionService = DefaultConversionService.getSharedInstance();

mapRow方法的实现如下:

首先校验查询结果,不是1行则报错

if (nrOfColumns != 1) {
	throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
}

然后是取值流程

Object result = getColumnValue(rs, 1, this.requiredType);

流程中委托给JdbcUtils#getResultSetValue进行处理,其中根据requiredType分成了两个版本执行:

JdbcUtils.getResultSetValue(rs, index, requiredType);
JdbcUtils.getResultSetValue(rs, index);

有requredType的版本会根据requiredType进行类型转换,如果没有requiredType则将date、Timestamp、BLOB、CLOB相关类型做转换,其他全按Object返回。

最后是转换流程

return (T) convertValueToRequiredType(result, this.requiredType);

如果是String,或Number的子类,转换比较简单,主要是当requiredType是自定义类型的时候,委托给专门的ConversionService

return this.conversionService.convert(value, requiredType);

0

评论区