8 数据库连接 JDBC

felix.shao2025-03-02

8 数据库连接 JDBC

 JDBC 链接数据库的流程及其原理
  1. 加载指定数据库的驱动程序。
  2. 在 Java 程序中加载驱动程序。如 Class.forName("com.mysql.jdbc.Driver")。
  3. 创建数据连接对象。如 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname", "root", "root")。
  4. 创建 Statement 对象。如 Statement statement = conn.createStatement()。
  5. 调用 Statement 对象的相关方法执行相对应的 SQL 语句。
  6. 关闭数据库连接。

8.1 Spring 连接数据库程序实现(JDBC)

 相信都很熟悉了,这块代码就省略了,直接看源码分析介绍。

8.2 save/update 功能的实现

 核心类为 org.springframework.jdbc.core.JdbcTemplate。

 JdbcTemplate 的初始化
// JdbcTemplate
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
    // DataSource 实例通过参数注入,其创建过程是引入第三方的连接池,里面封装了整个数据库的连接信息。
    setDataSource(dataSource);
    // 默认 lazyInit = true
    setLazyInit(lazyInit);
    afterPropertiesSet();
}
 update 功能分析

 service 里面调用了 JdbcTemplate 的 update 方法。

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
    public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
		return update(new SimplePreparedStatementCreator(sql), pss);
	}
    
    public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
		return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
	}

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

 我们跟踪上面 update 方法进行分析。其首先使用了 newArgTypePreparedStatementSetter 对参数与参数类型进行封装,然后又使用 SimplePreparedStatementCreator 对 SQL 语句进行封装。再进去 update 核心处理逻辑(里面使用了 execute 基础方法)。

8.2.1 基础方法 execute

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

基础方法 execute
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	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. 调用回调函数
			// 调用一些统一方法外的个性化处理,也就是 PreparedStatementCallback 类型的参数的 doInPreparedStatement 方法的回调。
			T result = action.doInPreparedStatement(ps);
			// 4. 封装警告信息
			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 {
			// 5. 资源释放
			if (psc instanceof ParameterDisposer) {
				((ParameterDisposer) psc).cleanupParameters();
			}
			JdbcUtils.closeStatement(ps);
			/**
			 * 数据库的连接释放并不是直接调用了 Connection 的 API 的 close 方法。考虑到存在事务的情况,
			 * 如果当前线程存在事务,那么说明在当前线程中存在公用数据库连接,这种情况下直接使用
			 * ConnectionHandle 中的 released 方法进行连接数减一,而不是真正的释放连接。
			 */
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}
}
 1. 获取数据库连接

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

public abstract class DataSourceUtils {
	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 {
		/**
		 * 在数据库连接方面,Spring 主要考虑的是关于事务方面的处理。基于事务处理的特殊性,Spring 需要保证线程中的数据库操作都是使用同一个事务连接。
		 */
		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;
	}
}
 2. 应用用户设定的输入参数

 setFetchSize 参数的使用,最主要是为了减少网络交互次数设计的。

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	protected void applyStatementSettings(Statement stmt) throws SQLException {
		int fetchSize = getFetchSize();
		if (fetchSize != -1) {
			// setFetchSize 最主要是为了减少网络交互次数设计的。访问 ResultSet 时,如果它每次只从服务器上读取一行数据,则会产生大量的开销。
			// setFetchSize 是当调用 re.next 时,ResultSet 会一次性从服务器上取得多少行数据回来,这样在下次 rs.next 时,它可以直接从内存中
			// 获取数据而不需要网络交互,提高了效率。这个设置可能会被某些 JDBC 驱动忽略,而且设置过大也会造成内存的上升。
			stmt.setFetchSize(fetchSize);
		}
		int maxRows = getMaxRows();
		if (maxRows != -1) {
			stmt.setMaxRows(maxRows);
		}
		DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
	}
}
 3. 调用回调函数

 调用一些通用方法外的个性化处理,也就是 PreparedStatementCallback 类型的参数的 doInPreparedStatement 方法的回调。

 4. 警告处理

 出现警告很可能会出现数据错误,但是,并不一定会影响程序运行。因此用户可以自己设置处理警告的方式,如默认的是忽略警告,当出现警告时只打印警告日志,而另一种方式只直接抛出异常。

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	protected void handleWarnings(Statement stmt) throws SQLException {
		// 当设置为忽略警告时值尝试打印日志
		if (isIgnoreWarnings()) {
			if (logger.isDebugEnabled()) {
				// 如果日志开启的情况下打印日志
				/**
				 * SQLWarning:提供关于数据库访问警告信息的异常。这些警告直接连接到导致报告警告的方法所在的对象。
				 *   警告可以从 Connection、Statement 和 ResultSet 对象中获得。
				 * 什么情况下会产生警告而不是异常:最常见的是 DataTruncation 直接继承 SQLWarning,由于某种原因意外地
				 *   截断数据值时会以 DataTruncation 警告形式报告异常。
				 * 出现警告很可能会出现数据错误,但是,并不一定会影响程序运行。因此用户可以自己设置处理警告的方式,如默认的是忽略警告,
				 *   当出现警告时只打印警告日志,而另一种方式只直接抛出异常。
				 */
				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 方法。考虑到存在事务的情况,如果当前线程存在事务,那么说明在当前线程中存在公用数据库连接,这种情况下直接使用 ConnectionHandle 中的 released 方法进行连接数减一,而不是真正的释放连接。

public abstract class DataSourceUtils {
	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);
	}
}

8.2.2 Update 中的回调函数

Update 中的回调函数

 PreparedStatementCallback 作为一个接口,其中只有一个函数 doInPreparedStatement,这个函数是用于调用通用方法 execute 的时候无法处理的一些个性化处理方法。
 再分析下面这个方法,第二个参数就是回调函数。

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	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();
				}
			}
		}));
	}
}
 设置 PreparedStatement 所需的全部参数

 pss 所代表的当前类是 ArgumentTypePreparedStatementSetter,直接查看其 setValues 源码。

public class ArgumentPreparedStatementSetter implements PreparedStatementSetter, ParameterDisposer {
	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);
		}
	}

	public static void setParameterValue(PreparedStatement ps, int paramIndex, SqlParameter param,
			@Nullable Object inValue) throws SQLException {

		setParameterValueInternal(ps, paramIndex, param.getSqlType(), param.getTypeName(), param.getScale(), 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);
		}
	}
}

8.3 query 功能的实现

带有参数的 query()
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	// 代码顺序是跟踪的代码来的
	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 {	
		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");

		// 有参的 execute 方法
		return execute(psc, new PreparedStatementCallback<T>() {
			@Override
			@Nullable
			public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
				ResultSet rs = null;
				try {
					if (pss != null) {
						// 设置 PreparedStatement 所需的全部参数
						pss.setValues(ps);
					}
					// 和 update 方法不同的地方,大部分逻辑是一致的
					rs = ps.executeQuery();
					// 返回额外处理,即将结果封装并转换至 POJO。rse 当前代表的类为 RowMapperResultSetExtractor,它是初始化时设置进去的。
					// RowMapperResultSetExtractor.extractData
					return rse.extractData(rs);
				} finally {
					JdbcUtils.closeResultSet(rs);
					if (pss instanceof ParameterDisposer) {
						((ParameterDisposer) pss).cleanupParameters();
					}
				}
			}
		});
	}
}

public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
	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;
	}
}

 query 方法中也是用了 newArgTypePreparedStatementSetter。
 跟踪 query 方法,整体的思路和 update 差不多的,只不过在回调类 PreparedStatementCallback 的实现中使用的是 ps.executeQuery() 执行查询操作,而且在返回方法上也做了一些额外的处理(将结果进行封装并转换至 POJO)。

不带参数的 query()
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	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 + "]");
		}

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

		// 无参的 execute 方法
		return execute(new QueryStatementCallback());
	}

	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) {
			// 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 方法也不一样。主要是 PreparedStatement 和 Statement 使用的不同。

  • PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句应该使用其提供效率。
  • PreparedStatement 可以防止 SQL 注入问题。

8.4 queryForObject

queryForObject()

 Spring 中不仅仅为我们提供了 query 方法,还基于此进行了封装,提供了不同类型的 query 方法。

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	// 有多个重载方法,以这个为例来说明
	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);
	}
}

public class SingleColumnRowMapper<T> implements RowMapper<T> {
	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) {
				// 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)) {
			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() + "]");
		}
	}
}

 最大的不同是 RowMapper 的使用。

Last Updated 3/3/2025, 9:29:04 PM