Spring5.0源码:jdbcTemplate

数据库连接JDBC

jdbcTemplate的初始化是从DataSource开始的。
DataSource实例通过参数注入,DataSource的创建过程是可引入第三方的连接池。
DataSource是整个数据库操作的基础,其封装了整个数据库的连接信息。

例:
配置好dataSource后
jdbcTemplate.update---->update—>execute

  • update可使用insert语句,具体jdbcTemplate可网上查找
 jdbcTemplate.update(  
               "insert into user_test(name,password) values(?,?)",   
                new Object[]{user.getUsername(),user.getPassword()},   
               new int[]{java.sql.Types.VARCHAR,java.sql.Types.VARCHAR}  
                ); 

这里的数据库都是基于当事务。

save/update

进入jdbcTemplate中的update

@Override
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
   return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
}
@Override
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
   return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
}

@Override
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
   return update(new SimplePreparedStatementCreator(sql), pss);
}

代码可分析得出,进入update后Spring并不急于进入核心处理操作,而是做充足准备。

  • 使用ArgTypePreparedStatement对参数与参数类型进行封装
  • 同时使用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方法是最基础的操作,而其他的操作如update,query等方法则是传入不同的PreparedStatementsCallback参数来执行不同的逻辑

基础方法execute

execute为数据库核心入口,将大多数数据库相同的步骤统一封装,将个性化操作使用参数PreparedStatementCallback进行回调。

@Override
@Nullable
public  T execute(PreparedStatementCreator psc, PreparedStatementCallback 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 + "]" : ""));
   }

//获取数据库连接
   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());
   }
}

以上的封装

获取数据库连接

获取数据库连接池并非直接使用dataSource.getConnection()方法那么简单,同样。
getConnection—doGetConnection

DataSourceUtils.java

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

在数据库连接中,Spring主要是考虑关于事务方面的处理,基于事务处理的特殊性,Spring需要保证线程中的数据库操作都是使用同一个事务连接。

应用用户设定的输入参数

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对象可以包含的最大行数限制设置为给定数。

调用回调函数

处理一些通用方法外的个性处理,也就是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,其提供关于数据库访问警告信息的异常。
产生的时警告而非异常:DataTruncation直接继承于SQLWarning,用户可以自己设置。
其会由MVC的HandlerExceptionResolver类的resolveException方法处理。

资源释放

这里并非直接使用Connection的API的close方法。因为存在事务,如果当前线程存在事务,那么说明在当前的线程中存在共用数据库连接,这种情况直接使用ConnectionHolder中的released方法进行连接数减一,而非真正的释放。

public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
   if (con == null) {
      return;
   }
   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;
      }
   }
   doCloseConnection(con, dataSource);
}

Update中的回调函数

preparedStatementCallback作为一个接口,其只有一个函数doInPreparedStatement,这个函数是用于调用通用方法execute的时候无法处理的一些个性化处理方法,在update函数中的函数实现:

@Override
public final Integer doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
   LobCreator lobCreator = this.lobHandler.getLobCreator();
   try {
      setValues(ps, lobCreator);
      return ps.executeUpdate();
   }
   finally {
      lobCreator.close();
   }
}

我们先看一下update的使用

 jdbcTemplate.update(  
               "insert into user_test(name,password) values(?,?)",   
                new Object[]{user.getUsername(),user.getPassword()},   
               new int[]{java.sql.Types.VARCHAR,java.sql.Types.VARCHAR}  
                ); 

原始的api使用是

PreparedStatement stat = con.prepareStatement(sql);
		stat.setString(1, "1"); 
		stat.setInt(2, 3); 

那Spring是如何封装的。
所有的操作都是以setValue(ps,lobCreator)为入口的。接下来的代表类是ArgumentTypePreparedStatementSetter,其setValues是
ArgumentTypePreparedStatementSetter.java

@Override
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;

   // override type info?
   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);
   }
}

query

针对于查找操作

query—>executeQuery----->return rse.extractData(rsToUse);
同update差不多,回调类PreparedStatementCallback的实现使用的是ps.executeQuery()执行查询操作。
返回是将结果封装成POJO,rse为代表类为RowMapperResultSetExtractor,而在构造RowMapperResultSetExtractor的时候又将自定义的rowMapper设置了进去。


@Override
public List extractData(ResultSet rs) throws SQLException {
   List 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进行封装

query也有两种,对参数区别。对参数及参数类型的传递。以此。少了对PreparedStatement的封装,少了其封装,所以调用execute方法就改变了。
一个是普通的Statement,另一个是PreparedStatement。也就是预编译的Statement。

queryForObject

Spring提供了很多类型的query方法。
queryForObject,在返回结果的基础上进行封装。

@Override
@Nullable
public  T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException {
   List results = query(sql, rowMapper);
   return DataAccessUtils.nullableSingleResult(results);
}

@Override
@Nullable
public  T queryForObject(String sql, Class requiredType) throws DataAccessException {
   return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}

最大的不同是对于RowMapper的使用,SingleColumnRowMapper类中的mapRow

public T mapRow(ResultSet rs, int rowNum) throws SQLException {
   // Validate column count.
   ResultSetMetaData rsmd = rs.getMetaData();
   int nrOfColumns = rsmd.getColumnCount();
   if (nrOfColumns != 1) {
      throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
   }

   // Extract column value from JDBC ResultSet.
   Object result = getColumnValue(rs, 1, this.requiredType);
   if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
      // Extracted value does not match already: try to convert it.
      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) {
         // Convert original Number to target Number class.
         return NumberUtils.convertNumberToTargetClass(((Number) value), (Class) requiredType);
      }
      else {
         // Convert stringified value to target Number class.
         return NumberUtils.parseNumber(value.toString(),(Class) 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() + "]");
   }
}

小结

  • jdbcTemplate------>update(下面的回调函数对参数处理)----->execute(基于事务)
  • update回调函数preparedStatementCallback接口------>函数doInPreparedStatement--------->ArgPreparedStatementSetter对所有的参数处理setValues----对每个参数及类型匹配处理----doSetValue
  • query等同,也有很多封装的query方法。

SpringBoot的自动配置jdbc

在JdbcTemplateAutoConfiguration这个函数中

@Configuration
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcTemplateAutoConfiguration {

   @Configuration
   static class JdbcTemplateConfiguration {

      private final DataSource dataSource;

      private final JdbcProperties properties;

      JdbcTemplateConfiguration(DataSource dataSource, JdbcProperties properties) {
         this.dataSource = dataSource;
         this.properties = properties;
      }

省略

这里有@ConditionalOnXXX 这样的注解,
进入此注解后会发现它的底层就是使用@Conditional而在这个注解后会跟上一个类。

@ConditionalOnSingleCandidate,意容器中是否指定一个单实例的Bean,或者找一个是首选的Bean

平时在配置SpringBoot的properties的可以选定jdbc或dataSource来选择。
在这个函数中也可以看到
他可以使用JdbcProperties.class或者是DataSourceAutoConfiguration.class里面的DataSourceProperties.class

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
      DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {


或者

@EnableConfigurationProperties(JdbcProperties.class)

这里注解@EnableConfigurationProperties后面指定了一个class
进入jdbcProperties.class函数中

@ConfigurationProperties(prefix = "spring.jdbc")
public class JdbcProperties {

也就是这里的前缀。根据application.properties中前缀使用配置。

我们最后,进入EnableConfigurationProperties注解中

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

这里的EnableConfigurationPropertiesImportSelector的函数,在SpringBoot源码分析里。也就是自动配置Starter中。

你可能感兴趣的:(Spring)