文章目录
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() + "]");
}
}