JdbcTemplate和NamedParameterJdbcTemplate是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的使用可以参考
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#update
或JdbcTemplate#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);
评论区