【Spring 源码深度解析】07数据库链接 JDBC

1 Spring 连接数据库实现(JDBC)

Spring 对 JDBC 做了大量封装,消除了冗余代码,使得开发了大大减少。下面通过一个例子了解下 Spring 中 JDBC 的操作。

1)创建数据表结构

CREATE TABLE user ( 
	id int(1) NOT NULL auto increment,
	name varchar (255) default NULL , 
	age int ( 11 ) default NULL , 
	sex varchar (255 ) default NULL , 
	PRIMARY KEY (id) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2)创建对应的 PO

public class User {

	private int id;
	private String name;
	private int age;
	private String sex;

	// 省略 getter 和 setter 方法
}

3)创建表与实体间的映射

public class UserRowMapper implements RowMapper<User> {

	@Override
	public User mapRow(ResultSet rs, int rowNum) throws SQLException {
		User person = new User(rs.getInt("id"), rs.getString("name"),
				rs.getInt("age"), rs.getString("sex"));
		return person;
	}
}

4)创建数据操作接口

public interface UserService {

	int save(User user);

	List<User> getUser();
}

5)创建数据操作接口实现类

public class UserServiceImpl implements UserService {

	private JdbcTemplate jdbcTemplate;

	/**
	 * 设置数据源
	 */
	public void setDataSource(DataSource dataSource) {
		jdbcTemplate = new JdbcTemplate(dataSource);
	}

	@Override
	public int 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});*/

		// 可以使用SqlParameterValue 用来封装值
		SqlParameterValue name = new SqlParameterValue(Types.VARCHAR, user.getName());
		SqlParameterValue age = new SqlParameterValue(Types.INTEGER, user.getAge());
		SqlParameterValue sex = new SqlParameterValue(Types.VARCHAR, user.getSex());

		return jdbcTemplate.update("INSERT INTO `user` (`name`, age, sex) VALUES (?, ? ,?)", name, age, sex);
	}

	@Override
	public List<User> getUser() {
		//带参数
		/*List<User> list = jdbcTemplate.query("select * from user where age = ?",
				new Object[]{20}, new int[]{Types.INTEGER}, new UserRowMapper());*/
		//不带参数
		List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper());

		return list;
	}
}

6)创建 Spring 配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:tx="http://www.springframework.org/schema/tx"
	   xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/tx
	   http://www.springframework.org/schema/tool/spring-tx.xsd">

	<!-- 配置数据源-->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/study" />
		<property name="user" value="root" />
		<property name="password" value="123456" />
		<!-- 连接池启动的初始值-->
		<property name="initialPoolSize" value="1" />
		<!-- 连接池最大值-->
		<property name="maxPoolSize" value="300" />
		<!-- 连接池最小值-->
		<property name="minPoolSize" value="1" />
	</bean>

	<!-- 配置业 bean-->
	<bean id="userService" class="org.springframework.test.jdbc.UserServiceImpl">
		<property name="dataSource" ref="dataSource" />
	</bean>
</beans>

7)测试

public class Main {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("test/jdbc/bean.xml");
		UserService userService = (UserService) factory.getBean("userService");

		User user = new User();
		user.setName("李四");
		user.setAge(20);
		user.setSex("男");
		userService.save(user);

		List<User> users = userService.getUser();
		System.out.println("获得所有的User");

		for (User tmp : users) {
			System.out.println(tmp);
		}
	}
}

2 save/update 功能的实现

整个功能的实现是由 JdbcTemplate(模板方法设计模式)为基础实现的。JdbcTemplate 中需要设置 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});
}

该方法调用了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 方法后,首先使用 ArgPreparedStatementSetter 对参数进行封装,使用 SimplePreparedStatementCreator 对 sql 语句进行封装。封装完成后就可以进入核心的数据处理了。

/**
 * 执行插入或更新
 * @param psc 封装了 sql  Default implement {@link SimplePreparedStatementCreator}
 * @param pss 封装了 参数
 */
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();
			}
		}
	}));
}

execute 方法是最基础的操作,而其他的操作比如 update,query 等方法则是传入不同的 PreparedStatementCallback 参数来执行不同的逻辑。

2.1 基础方法 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 + "]" : ""));
	}

	// 获取数据库连接
	Connection con = DataSourceUtils.getConnection(obtainDataSource());
	PreparedStatement ps = null;
	try {
		// 创建 PreparedStatement
		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());
	}
}

以上方法对常用操作进行了封装。重要逻辑如下
1)获取数据库连接
获取数据库连接不是直接使用 dataSource.getConnction() 方法,而是考虑了多种情况。

// DataSourceUtils.java
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
	try {
		return doGetConnection(dataSource);
	}
	catch (SQLException ex) {
		throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
	}
	catch (IllegalStateException ex) {
		throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
	}
}

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 需要保证线程中的数据库操作都是使用同一个事务连接。

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时,它可以直接从内存中获取数据而不需要网络交互,提高了效率。但是设置过大会造成内存的上升。

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

protected void handleWarnings(@Nullable SQLWarning warning) throws SQLWarningException {
	if (warning != null) {
		throw new SQLWarningException("Warning not ignored", warning);
	}
}

该方法中使用到了一个 SQLWarning 类,其提供关于数据库访问警告信息的异常。这些异常直接链接到导致报告告警的方法所在的对象。警告可以从 Connection,Statement 和 ResultSet 对象中获取。试图在关闭的连接,关闭的语句,关闭的结果集上获取警告将抛出异常。此外,当关闭语句时也会关闭它可能生成的结果集。

对于警告的处理并不是直接抛出异常,出现警告可能会出现数据错误,但是不一定影响程序的执行,所有用户可以自己设置处理警告的方法。忽略警告的打印日志,否则会抛出异常。

5)资源释放
数据库的连接释放并不是直接调用Connection#close方法。考虑存在事务的情况,如果当前线程存在事务,那么说明当前线程中存在共用的数据库连接,这种情况下是直接使用ConnectionHolder#released方法进行连接减一,而不是真正的释放连接。

// DataSourceUtils.java
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);
}

public static void doCloseConnection(Connection con, @Nullable DataSource dataSource) throws SQLException {
	if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
		con.close();
	}
}

2.2 update 中的回调函数

PreparedStatementCallback 作为一个接口,其中只有一个方法doInPreparedStatement,这个方法是用于调用通用方法 execute 的时候无法处理一些个性化处理方法,在 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();
		}
	}
}));

上述方法其中 ps.executeUpdate() 是执行 sql,是 JDBC 的实现。主要是看pss.setValues(ps)方法进行输入参数的设置。首先回顾下 Spring 中使用 SQL 的执行过程。

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 为我们做了封装,而真正的 JDBC 调用非常复杂,需要:

PreparedStatement updateSales = connection.prepareStatement("INSERT INTO `user` (`name`, age, sex) VALUES (?, ? ,?)");
updateSales.setString(1, user.getName());
updateSales.setInt(2, user.getAge());
updateSales.setString(3, user.getSex());

那么 Spring 是如何进行以上封装的呢?所有的操作都是以pss.setValues(ps);方法为入口的。 在业务逻辑中我们使用 ArgumentTypePreparedStatementSetter 对参数进行封装的。进入查看源码。 ArgumentTypePreparedStatementSetter 该类是指定了对于的数据库参数的封装。

// ArgumentTypePreparedStatementSetter.java
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);
}

// StatementCreatorUtils.java
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?
	// 如果是 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();
	}

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

如果调用 update 方法时没有指定对应的参数类型,即

SqlParameterValue name = new SqlParameterValue(Types.VARCHAR, user.getName());
SqlParameterValue age = new SqlParameterValue(Types.INTEGER, user.getAge());
SqlParameterValue sex = new SqlParameterValue(Types.VARCHAR, user.getSex());

return jdbcTemplate.update("INSERT INTO `user` (`name`, age, sex) VALUES (?, ? ,?)", name, age, sex);

会使用 ArgumentPreparedStatementSetter 进行封装。

public void setValues(PreparedStatement ps) throws SQLException {
	if (this.args != null) {
		// 遍历进行赋值
		for (int i = 0; i < this.args.length; i++) {
			Object arg = this.args[i];
			doSetValue(ps, i + 1, arg);
		}
	}
}

protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException {
	if (argValue instanceof SqlParameterValue) {
		SqlParameterValue paramValue = (SqlParameterValue) argValue;
		StatementCreatorUtils.setParameterValue(ps, parameterPosition, paramValue, paramValue.getValue());
	}
	else {
		// 设置为未知类型的值
		StatementCreatorUtils.setParameterValue(ps, parameterPosition, SqlTypeValue.TYPE_UNKNOWN, argValue);
	}
}

3 Query 功能的实现

查找操作的使用方法如下。

@Override
public List<User> getUser() {
	List<User> list = jdbcTemplate.query("select * from user where age = ?",
			new Object[]{20}, new int[]{Types.INTEGER}, new UserRowMapper());
	return list;
}

跟踪 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)));
}

public <T> T query(String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException {
	// 使用 ArgTypePreparedStatementSetter 封装参数
	return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse);
}

public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
	// 封装 sql 语句
	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) {
					// 设置值与 update 类似
					pss.setValues(ps);
				}
				// 查询
				rs = ps.executeQuery();
				// 封装结果集
				return rse.extractData(rs);
			}
			finally {
				// 关闭结果集
				JdbcUtils.closeResultSet(rs);
				if (pss instanceof ParameterDisposer) {
					((ParameterDisposer) pss).cleanupParameters();
				}
			}
		}
	});
}

可以看到整体处理和 update 差不多,只不过在回调类 PreparedStatementCallback 中的 doInPreparedStatement 方法中执行查询操作,并对结果及进行了处理。

rse.extractData(rs)方法负责将结果进行封装并装换至 POJO,rse当前代表的类为RowMapperResultSetExtractor,而在构造RowMapperResultSetExtractor时,我们将自定义的rowMapper设置了进去。

// RowMapperResultSetExtractor.java
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()) {
		// 将映射的结果放入集合中
		// 调用自定义的 RowMapper 的 mapRow 方法映射值
		results.add(this.rowMapper.mapRow(rs, rowNum++));
	}
	return results;
}

上述代码并没有复杂的操作,只是对返回结果遍历并使用 rowMapper 进行转换。

以上查询方法时带查询参数 “?” 的,如果不带参数则 Spring 使用的是另一种处理方式。例如

List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper());

跟踪代码:

public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
	return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}

/**
 * 不带参数的查询方法
 */
@Override
@Nullable
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());
}

与之前 query 方法最大的不同是减少了参数及参数类型的传递,自然也少了 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 而不是 PrepareStatement
	Statement stmt = null;
	try {
		stmt = con.createStatement();
		// 应用用户设定的输入参数
		// fetchSize 和 maxRows
		applyStatementSettings(stmt);
		// 执行回调函数
		T result = action.doInStatement(stmt);
		handleWarnings(stmt);
		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.
		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 创建 Statement,而之前带有参数的是使用 PreparedStatementCreator 类创建的 PreparedStatement。PreparedStatement 继承自 Statement,两者的差别如下:

  • reparedStatement 实例包含预编译的 SQL 语句。这就是使语句 “准备好”。该语句为每个 IN 参数保留一个 “?” 作为占位符。每个问号的值必须在该语句执行前,进行设置。
  • PreparedStatement 进行了预编译,其执行速度要比 Statement 快。

4 queryForObject

Spring 中不仅为我们提供了 query 方法,还在此基础上做了封装,提供了不同类型的 query 方法。如下图所示。
在这里插入图片描述

以 queryForObject 为例,讨论 Spring 是如何封装在返回结果的基础上进行封装的。

public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
	// 使用 SingleColumnRowMapper
	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);
}

public static <T> T nullableSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
	// This is identical to the requiredSingleResult implementation but differs in the
	// semantics of the incoming Collection (which we currently can't formally express)
	if (CollectionUtils.isEmpty(results)) {
		throw new EmptyResultDataAccessException(1);
	}
	if (results.size() > 1) {
		throw new IncorrectResultSizeDataAccessException(1, results.size());
	}
	return results.iterator().next();
}

其中最大的不同是对于 RowMapper 的使用。SingleColumnRowMapper 类型中的 mapRow 方法,该映射只是取第一列的值作为结果。:

// SingleColumnRowMapper.java
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 getColumnValue(ResultSet rs, int index, @Nullable Class<?> requiredType) throws SQLException {
	if (requiredType != null) {
		return JdbcUtils.getResultSetValue(rs, index, requiredType);
	}
	else {
		// No required type specified -> perform default extraction.
		return getColumnValue(rs, index);
	}
}

对应的类型转换函数:

protected Object convertValueToRequiredType(Object value, Class<?> requiredType) {
	// String 类型
	if (String.class == requiredType) {
		return value.toString();
	}
	else if (Number.class.isAssignableFrom(requiredType)) {
		if (value instanceof Number) {
			// Convert original Number to target Number class.
			// 转换原始 Number 类型的实体到 Number 类
			return NumberUtils.convertNumberToTargetClass(((Number) value), (Class<Number>) requiredType);
		}
		else {
			// Convert stringified value to target Number class.
			// 转换 String 到 Number 类
			return NumberUtils.parseNumber(value.toString(),(Class<Number>) requiredType);
		}
	}
	else if (this.conversionService != null && this.conversionService.canConvert(value.getClass(), requiredType)) {
		// 使用转换器进行转换 默认 GenericConversionService
		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() + "]");
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值