Spring源码解析10——数据库连接JDBC

  JDBC(Java Data Base Connectivity,Java数据库连接)是一种用于执行 SQL语句的 Java API可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC为数据库开发人员提供了一个标准的 API,据此可以构建更高级的工具和接口,使数据库开发人员能够用纯JavaAPI编写数据库应用程序,并且可跨平台运行,并且不受数据库供应商的限制。
  JJDBC 连接数据库的流程及其原理如下。
①、在开发环境中加载指定数据库的驱动程序。接下来的实验中,使用的数据库是 MySQL,所以需要去下载 MySQL支持JDBC的驱动程序,将下载得到的驱动程序加载进开发环境中(开发环境是 idea,具体示例时会讲解如何加载)。
②、在 Java程序中加载驱动程序。在Java程序中,可以通过“Class.forName("指定数据库的驱动程序")”的方式来加载添加到开发环境中的驱动程序,例如加载 MySQL 的数据驱动程序的代码为 Class.forName("com.mysql.jdbc.Driver")。
③、创建数据连接对象。通过 DriverManager.class创建数据库连接对象Connection。DriverManager.class作用于 Java 程序和JDBC 驱动程序之间,用于检查所加载的驱动程序是否可以建立连接,然后通过它的 getConnection()函数根据数据库的 URL、用户名和密码,创建一个 JDBC Connection对象,例如:Connection connection=DriverManager.geiConnection("连接数据库的 URL","用户名","密码")。其中,URL=协议名+IP地址(域名)+端口+数据库名称;用户名和密码是指登录数据库时所使用的用户名和密码。具体示例创建MySQL的数据库连接代码如下:

Connection connectMySQL=DriverManager,geiConnection("jdbc:mysgl://localhost:3306/myuser","root","root");

④、创建 Statement 对象。Statement类的主要是用于执行静态 SOL 语句并返回它所生成结果的对象。通过 Connection对象的createStatement()方法可以创建一个 Statement 对象。例如:Statement statament=connection.createStatement()。具体示例创建 Statement 对象代码如下:Statement statamentMySOl =connectMySOL.createStatement();
⑤、调用 Statement 对象的相关方法执行相对应的SQL语句。通过 execuUpdate()方法来对数据更新,包括插入和删除等操作,例如向staff表中插人一条数据的代码:

statement.excuteUpdate("INSERT INTO staff(name, age,sex,address, depart, worklen,wage)"+"VALUES('Tom1',321,'M','china','Personnel','3','3000')");

通过调用Statement对象的executeQuery()函数进行数据的查询,而查询结果会得到ResulSet对象,ResulSet 表示执行查询数据库后返回的数据的集合,ResulSet对象具有可以指向当前数据行的指针。通过该对象的next()函数,使得指针指向下一行,然后将数据以列号或者字段名取出。如果当 next()函数返回null,则表示下一行中没有数据存在。使用示例代码如下:

ResultSet resultSel=statement.executeQuery( "select * from staff" );

⑥、关闭数据库连接。使用完数据库或者不需要访问数据库时,通过Connection的 close()函数及时关闭数据连接。

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

  Spring对 JDBC 做了大量封装,消除了冗余代码,使得开发量大大减小。代码如下:

  • 在mysql数据库创建一张User表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) DEFAULT NULL COMMENT '名字',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `sex` varchar(255) DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT DEFAULT CHARSET=utf8
  • 创建对应数据表的PO
package com.xxx.jdbc;
import java.io.Serializable;
public class User {
//private static final long serialVersionUID = 1L;

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

    public User() {
    }

    public User(int id, String name, int age, String sex) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    //省略setter()、getter()、toString()
}

  • 创建表与实体间的映射
package com.xxx.jdbc;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserRowMapper implements RowMapper {
    @Override
    public Object 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;
    }
}
  • 创建数据操作接口UserService.interface
package com.xxx.jdbc;

import java.util.List;
public interface UserService {
    public void save(User user);

    public List<User> getUsers();
}
  • 创建UserService.interface的实现类UserServiceImpl.class
package com.xxx.jdbc;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.Types;
import java.util.List;
public class UserServiceImpl implements UserService {
    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void save(User user) {
        //这2种向sql中传入参数的方式都可以
        //方式1:直接传参
        jdbcTemplate.update("insert into user(name,age,sex) values(?,?,?)", user.getName(), user.getAge(), user.getSex());
        //方式2:利用数组传参
        jdbcTemplate.update("insert into user(name,age,sex) values(?,?,?)", new Object[]{user.getName(), user.getAge(), user.getSex()}
                , new int[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER, Types.VARCHAR});
    }

    @SuppressWarnings("unchecked")
    public List<User> getUsers() {
        List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper());
        return list;
    }
}
  • 创建Spring的配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
">
    <!--需要调用dbcp2:2.9.0版本-->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <!--数据库连接池启动时的初始值-->
        <property name="initialSize" value="1"/>
        <!--数据库连接池的最大值-->
        <property name="maxTotal" value="300"/>
        <!--最大空闲值,当经过一个高峰时间后,数据库连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止-->
        <property name="maxIdle" value="2"/>
        <!--最小空闲值,当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请-->
        <property name="minIdle" value="1"/>
    </bean>
    <!--配置业务bean:UserServiceImpl.class-->
    <bean id="userService" class="com.xxx.jdbc.UserServiceImpl">
        <!--向属性dataSource注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>
  • 测试
package com.xxx.jdbc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class SpringJDBCTest {
    public static void main(String[] args) {
        ApplicationContext act = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = (UserService) act.getBean("userService");
        User user = new User();
        user.setName("张三");
        user.setAge(20000);
        user.setSex("男");
        userService.save(user);
        List<User> list = userService.getUsers();
        for (User u : list) {
            System.out.println(u.getId() + "  " + u.getName() + "  " + u.getAge() + "  " + u.getSex());
        }
    }
}

执行结果如下所示:
image
image

2、save/update功能的实现

  在UserServicelmpl中jdbcTemplate 的初始化是从 setDataSource()函数开始的,DataSource实例xml配置中的参数注入,DataSource的创建过程是引人第三方的连接池,这里不做过多介绍。DataSource 是整个数据库操作的基础,里面封装了整个数据库的连接信息。我们首先以上文中的save()函数为例进行代码跟踪:
JdbcTemplate.class:

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

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

进入 update方法后,Spring并不是急于进人核心处理操作,而是先做足准备工作,使用ArgumentTypePreparedStatementSetter 对参数与参数类型进行封装,同时又使用 SimplePreparedStatementCreator对SQL语句进行封装。至于为什么这么封装,下文再说。
  经过了数据封装后便可以进人了核心的数据处理代码了。
JdbcTemplate.class:

    protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
            throws DataAccessException {

        logger.debug("Executing prepared SQL update");
        //匿名内部类,等后续execute()函数中的代码进行回调
        return updateCount(execute(psc, ps -> {
            try {
                //设置PreparedStatement所需要的全部参数
                if (pss != null) {
                    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.1、基础函数execute()

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

    @Override
    @Nullable
    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 {
            ps = psc.createPreparedStatement(con);
            //应用用户设定的输入参数
            applyStatementSettings(ps);
            //调用回调函数(入参时候的lambda表达式,在JdbcTemplate.class::update()函数中)
            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());
        }
    }

以上步骤,下文将会逐个拆解。
abstract DataSourceUtils.class

2.1.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();
        }
        // 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.1.2、应用用户设定的输入参数

JdbcTemplate.class

    protected void applyStatementSettings(Statement stmt) throws SQLException {
        int fetchSize = getFetchSize();
        if (fetchSize != -1) {
            stmt.setFetchSize(fetchSize);
        }
        //默认值为-1,取数据库中查询到的所有数据行到内存中
        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对象可以包含的最大行数限制设置为给定数。

2.1.3、调用回调函数

处理一些通用方法外的个性化处理,调用上文中的JdbcTemplate.class::update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)函数

2.1.4、警告处理

JdbcTemplate.class::handleWarnings()

    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,SQLWarning 提供关于数据库访问警告信息的异常。这些警告直接链接到导致报告警告的方法所在的对象。警告可以从Connection、Statement和ResultSet对象中获得。试图在已经关闭的连接上获取警告将导致抛出异常。类似地,试图在已经关闭的语句上或已经关闭的结果集上获取警告也将导致抛出异常。注意,关闭语句时还会关闭它可能生成的结果集。
  很多人不是很理解什么情况下会产生警告而不是异常,在这里给读者提示个最常见的警告DataTruncation:DataTruncation 直接继承 SQLWarning,由于某种原因意外地截断数据值时会以DataTruncation 警告形式报告异常。
  对于警告的处理方式并不是直接抛出异常,出现警告很可能会出现数据错误,但是,并不一定会影响程序执行,所以用户可以自己设置处理警告的方式,如默认的是忽略警告,当出现警告时只打印警告日志,而另一种方式是直接抛出异常

2.1.5、资源释放

  数据库的连接释放并不是直接调用了Connection的API中的close()函数。考虑到存在事务的情况,如果当前线程存在事务,那么说明在当前线程中存在共用数据库连接,这种情况下直接使用 ConnectionHolder 中的released()函数进行连接数减一,而不是真正的释放连接。
DataSourceUtils.class

    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()函数进行连接数-1而不是真正的释放连接
            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.interface作为一个接口,其中只有一个函数 doInPreparedStatement(),这个函数是用于调用通用方法execute 的时候无法处理的一些个性化处理方法,在update 中的函数实现:

    protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
            throws DataAccessException {

        logger.debug("Executing prepared SQL update");
        //匿名内部类,等后续execute()函数中的代码进行回调
        return updateCount(execute(psc, ps -> {//实现了PreparedStatementCallback.interface接口,该接口只有一个函数,此处省略了@Override
            try {
                //设置PreparedStatement所需要的全部参数
                if (pss != null) {
                    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();
                }
            }
        }));
    }

其中用于真正执行 SQL的ps.executeUpdate()函数有太多需要讲解的,因为我们平时在直接使用JDBC方式进行调用的时候会经常使用此方法。但是,对于设置输人参数的函数pss.setValues(ps),我们有必要去深人研究一下。在没有分析源码之前,我们至少可以知道其功能,不妨再回顾下 Spring 中使用 SQL的执行过程,直接使用:

//方式2:利用数组传参
jdbcTemplate.update("insert into user(name,age,sex) values(?,?,?)", new Object[]{user.getName(), user.getAge(), user.getSex()}
                , new int[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER, Types.VARCHAR});

SQL语句对应的参数,对应参数的类型清晰明了,这都归功于Spring 为我们做了封装,而真正的JDBC 调用其实非常繁琐,你需要这么做:
image
Spring 的封装过程是这样,首先,所有的操作都是以 pss.setValues(ps)为人口的。所代表的当前类和函数正是ArgumentTypePreparedStatementSetter.class::setValues()函数,如下:
ArgumentTypePreparedStatementSetter.class::setValues():

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

对单个参数及类型的匹配处理:
ArgumentTypePreparedStatementSetter.class

    protected void doSetValue(PreparedStatement ps, int parameterPosition, int argType, Object argValue)
            throws SQLException {

        StatementCreatorUtils.setParameterValue(ps, parameterPosition, argType, argValue);
    }

abstract StatementCreatorUtils.class

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

3、query功能的实现

  跟踪jdbcTemplate.class::query()函数。

    //不带有?的select语句(比如select * from user) 处理过程
    @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());
    }
    
    //带有?的select语句(比如select * from user where age = ?) 处理过程
    @Nullable
    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();//此处与update()函数不同
                    return rse.extractData(rs);//将结果进行封装并转换至POJO
                }
                finally {
                    JdbcUtils.closeResultSet(rs);
                    if (pss instanceof ParameterDisposer) {
                        ((ParameterDisposer) pss).cleanupParameters();
                    }
                }
            }
        });
    }

可以看到整体套路与update()函数差不多的,只不过在回调类 PreparedStatementCallback 的实现中使用的是 ps.executeQuery()函数执行查询操作,而且在返回方法上也做了一些额外的处理。
  rse.extractData(rsToUse)函数负责将结果进行封装并转换至POJO,rse 当前代表的类为RowMapperResultSetExtractor.class,而在构造 RowMapperResultSetExtractor.class对象的时候我们又将自定义的 rowMapper 设置了进去。调用代码如下:
RowMapperResultSetExtractor.class::extractData()函数

    @Override
    public List<T> extractData(ResultSet rs) throws SQLException {
        List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>());
        int rowNum = 0;
        //遍历结果集并用RowMapper进行转换
        while (rs.next()) {
            results.add(this.rowMapper.mapRow(rs, rowNum++));
        }
        return results;
    }

RowMapperResultSetExtractor.class的UML图如下所示:
image

3.1、不带有?的select语句(比如select * from user) 处理过程

  与之前的 query方法最大的不同是少了参数及参数类型的传递,自然也少了对PreparedStatementSetter.class类型对象的封装。既然少了 PreparedStatementSetter 类型的参数传人,调用的 execute()函数自然也会有所改变了。
JdbcTemplate.class::execute()

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

这个 exexute()函数与之前的 execute()函数并无太大差别,都是做一些常规的处理,诸如获取连接、释放连接等,但是,有一个地方是不一样的,就是statement 的创建。这里直接使用 connection 创建,而带有参数的 SQL使用的是 PreparedStatementCreator.class来创建的。一个是普通的 Statement,另一个是 PreparedStatement,PreparedStatement接口继承Statement接口,两者区别如下:
①、PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句“准备好”。包含于PreparedStatement对象中的SQL语句可具有一个或多个IN参数。IN参数的值在 SOL语句创建时未被指定。相反的,该语句为每个IN参数保留一个问号("?")作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX()函数来提供。
②、由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SOL语句经常创建为PreparedStatement对象,以提高效率。
  作为 Statement的子接口,PreparedStatement 继承了 Statement 的所有功能。另外,它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值。同时,三种方法execute()、executeQuery()和 executeUpdate()已被更改以使之不再需要参数。这些函数的 Statement形式(接受SQL语句参数的形式)不应该用于PreparedStatement对象。

4、JdbcTemplate.class::queryForObject()函数

  queryForObject()函数最大的不同还是对于RowMaper的使用。如下所示:
JdbcTemplate.class::queryForObject()

    @Override
    @Nullable
    public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
        return queryForObject(sql, getSingleColumnRowMapper(requiredType));
    }
    
    protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) {
        return new SingleColumnRowMapper<>(requiredType);
    }
    
    @Override
    @Nullable
    public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        List<T> results = query(sql, rowMapper);//queryForObject()函数最终还是调用query()函数进行查询
        return DataAccessUtils.nullableSingleResult(results);
    }

SingleColumnRowMapper.class实现了RowMapper.interface接口,UML关系图,如下:
image
queryForObject()函数最终还是调用query()函数进行查询,同上文一样,最终在RowMapperResultSetExtractor.class::extractData()函数中用SingleColumnRowMapper.class::mapRow()函数进行转换,如下:
SingleColumnRowMapper.class::mapRow():

    @Override
    @SuppressWarnings("unchecked")
    @Nullable
    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;
    }
    
    //对应的类型转换函数
    @SuppressWarnings("unchecked")
    @Nullable
    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() + "]");
        }
    }
posted @ 2026-01-02 16:18  Carey_ccl  阅读(1)  评论(0)    收藏  举报