这篇文章主要介绍了Spring5源码之JDBC,基于上一篇Spring5之JDBC使用案例来一步步剖析jdbcTemplate操作数据库的源码。需要的朋友可以参考一下。
1、save/update功能实现
在UserServiceImpl中jdbcTemplate的初始化是从setDataSource方法开始的,DataSource实例通过参数注入,DataSource的创建过程是引入第三方的连接池。
DataSource是整个数据库操作的基础,里面封装了整个数据库的连接信息。我们首先以保存实体类为例进行代码跟踪。
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语句以及语句中对应的参数和参数类型,其他操作便可以交由Spring来完成了,这些工作到底包含了什么呢?进入jdbcTemplate中的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);
}
进入update方法后,Spring并不是急于进入核心处理操作,而是先做足准备工作,使用newArgTypePreparedStatementSetter对参数和参数类型进行封装,同时又使用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) {
// 设置PreparedStatement所需的全部参数
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
如果读者了解过其他操作方法,可以知道execute方法是最基础的操作,而其他操作比如update、query等方法则是出入不同的PreparedStatementCallback参数来执行不同的逻辑。
2、基础方法execute
execute作为数据库操作的核心入口,将大多数数据库操作的相同的步骤统一封装,而将个性化的操作使用参数PreparedStatementCallback进行回调。
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
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 + "]" : ""));
}
// 1 获取数据库连接
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
// 2 应用用户设定的输入参数
applyStatementSettings(ps);
// 3 调用回调函数
T result = action.doInPreparedStatement(ps);
// 4 警告处理
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// 5 释放数据库连接避免当异常转换器没有被初始化的时候出现潜在的连接池死锁
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());
}
}
以上方法对常用操作进行了封装,包括如下几项内容
1 获取数据库连接
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();
}
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
// 当前线程支持同步
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// 在事务中使用同一数据库连接
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) {
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
在数据库连接方面,Spring主要考虑的是关于事务方面的处理。基于事务处理的特殊性,Spring需要保证线程中的数据库操作都是还要同一个事务连接。
2 应用用户设定的输入参数
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());
}
setFetchSize最主要是为了减少网络交互次数设计的。访问ResultSet时,如果它每次只从服务器上读取一行数据,则会产生大量的开销。setFetchSize的意思就是当调用rs.next时,ResultSet会一次性从服务器上取得多少行数据回来,这样在下次rs.next时,它可以直接从内存中获取数据而不需要网络交互,提高了效率。这个设置可能会被某些JDBC驱动忽略,而且设置过大也会造成内存的上升。
setMaxRows将此Statement对象生成的所有ResultSet对象可以包含的最大行数限制设置为给定数。
3 调用回调函数
处理一些通用方法外的个性化处理,也就是PreparedStatementCallback类型的参数的doInPreparedStatement方法的回调。
4 警告处理
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());
}
}
5 资源释放
数据库的连接释放并不是直接调用了Connection的API中的close方法,考虑到存在事务的情况,如果当前线程存在事务,那么说明在当前线程中存在共用数据库连接,这种情况下直接使用ConnectionHolder的released方法进行连接数减一,而不是真正的释放连接。
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中的released方法进行连接减一而不是真正的释放连接
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);
}
3、update中的回调函数
PreparedStatementCallback作为一个接口,其中只有一个方法doInPreparedStatement。这个方法是用于通用方法execute的时候无法处理的一些个性化处理方法,在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.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
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++;
}
}
}
}
// 对单个参数及类型的匹配处理
protected void doSetValue(PreparedStatement ps, int parameterPosition, int argType, Object argValue)
throws SQLException {
StatementCreatorUtils.setParameterValue(ps, parameterPosition, argType, argValue);
}
public static void setParameterValue(PreparedStatement ps, int paramIndex, int sqlType,
@Nullable Object inValue) throws SQLException {
setParameterValueInternal(ps, paramIndex, sqlType, null, null, inValue);
}
private static void setParameterValueInternal(PreparedStatement ps, int paramIndex, int sqlType,
@Nullable String typeName, @Nullable Integer scale, @Nullable Object inValue) throws SQLException {
String typeNameToUse = typeName;
int sqlTypeToUse = sqlType;
Object inValueToUse = inValue;
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();
}
if (logger.isTraceEnabled()) {
logger.trace("Setting SQL statement parameter value: column index " + paramIndex +
", parameter value [" + inValueToUse +
"], value class [" + (inValueToUse != null ? inValueToUse.getClass().getName() : "null") +
"], SQL type " + (sqlTypeToUse == SqlTypeValue.TYPE_UNKNOWN ? "unknown" : Integer.toString(sqlTypeToUse)));
}
if (inValueToUse == null) {
setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse);
}
else {
setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse);
}
}
4、query功能的实现
jdbcTemplate中的query方法
public <T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper)));
}
上面方法中与update方法中都同样使用newArgTypePreparedStatementSetter
public <T> T query(String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse);
}
public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
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();
}
}
}
});
}
可以看到整体套路与update差不多的,只不过在回调类PreparedStatementCallback的实现中使用的是ps.executeQuery()执行查询操作,而且在返回方法上也做了一些额外的处理。rse.extractData(rs)方法负责将结果进行封装转换到POJO,rs代表当前类RowMapperResultSetExtractor,而在构造函数又将文明自定义的rowMapper设置进去。调用代码如下:
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>());
int rowNum = 0;
while (rs.next()) {
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}
上面的代码中并没有什么复杂的逻辑,只是对返回结果遍历并以此使用rowMapper进行转换。之前也将了update方法以及query方法,使用了两个方法示例的SQL都是带有参数的,也就是带有"?“的,那么还有另外一种情况就是不带有”?"的,Spring中使用的是另外一种处理方式。
public List<User> getUsers() {
List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper());
return list;
}
跟踪进入:
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
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 + "]");
}
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());
}
与之前的query方法最大的不同是少了参数及参数类型的传递,自然也少了PreparedStatementSetter类型的封装,既然少了PreparedStatementSetter类型
的传入,调用的execute方法自然也有所改变了。
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// 在还没有初始化异常转换器的情况下,尽早释放连接,以避免潜在的连接池死锁
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
这个execute与之前的execute并无太大差别,都是做一些常规的处理,诸如获取连接、释放连接等,但是有一个地方是不一样的,就是statement的创建。这里直接使用connection创建,而带有餐的SQL使用的是PreparedStatementCreator类来创建的。一个是普通的Statement,另外一个是PreparedStatement,两者究竟是何区别呢?
PreparedStatement接口继承Statement,并与之在两方面有所不同。
- PreparedStatement实例包含已编译的SQL语句,这就是使语句"准备好"。包含于PreparedStatement对象中的SQL语句可具有一个或多个IN参数。IN参数的值在SQL语句创建时未被指定。相反的,该语句未每个IN参数保留一个问号("?")作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX方法来提供。
- 由于PreparedStatement对象已经预编译过,所以其执行速度要快于Statement对象。因此,多次执行的SQL语句经常创建为PreparedStatement对象,以提高效率。
作为Statement的之类,PreparedStatement继承了Statement的所有功能。另外,它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值。
5、queryForObject
Spring中不仅仅为我们提供了query 方法,还在此基础上做了封装,提供了不同类型的query方法。
我们以queryForObject为例,来讨论一下Spring是如何在返回结果的基础上进行封装的。
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, rowMapper);
return DataAccessUtils.nullableSingleResult(results);
}
其实最大的不同还是对于RowMapper的使用,SingleColumnRowMapper类中的mapRow:
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
// 验证返回结果数
ResultSetMetaData rsmd = rs.getMetaData();
int nrOfColumns = rsmd.getColumnCount();
if (nrOfColumns != 1) {
throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
}
// 抽取第一个结果进行处理
Object result = getColumnValue(rs, 1, this.requiredType);
if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
// 转换对应的类型
try {
return (T) convertValueToRequiredType(result, this.requiredType);
}
catch (IllegalArgumentException ex) {
throw new TypeMismatchDataAccessException(
"Type mismatch affecting row number " + rowNum + " and column type '" +
rsmd.getColumnTypeName(1) + "': " + ex.getMessage());
}
}
return (T) result;
}
对应的类型转换函数:
protected Object convertValueToRequiredType(Object value, Class<?> requiredType) {
if (String.class == requiredType) {
return value.toString();
}
else if (Number.class.isAssignableFrom(requiredType)) {
if (value instanceof Number) {
// 转换原始Number类型的实体到Number类
return NumberUtils.convertNumberToTargetClass(((Number) value), (Class<Number>) requiredType);
}
else {
// 转换String类型的值到Number类
return NumberUtils.parseNumber(value.toString(),(Class<Number>) requiredType);
}
}
else if (this.conversionService != null && this.conversionService.canConvert(value.getClass(), requiredType)) {
return this.conversionService.convert(value, requiredType);
}
else {
throw new IllegalArgumentException(
"Value [" + value + "] is of type [" + value.getClass().getName() +
"] and cannot be converted to required type [" + requiredType.getName() + "]");
}
}
如果您觉得有帮助,欢迎点赞收藏哦 ~ ~ 多谢~