Spring源码深度解析之数据库链接JDBChtml
JDBC(Java Data Base Connectivity,Java数据库链接)是一种用于执行SQL语句的Java API,能够为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC为数据库开发人员提供了一个标准的API,据此能够构建更高级的工具和接口,是数据库开发人员可以用纯Java API编写数据库应用程序,而且可跨平台运行,而且不受数据库供应商的限制。java
JDBC链接数据库流程及原理以下:mysql
(1)在开发环境中加载指定数据库的驱动程序。接下来的试验中,使用的数据库是MySQL,因此须要去下载MySQl支持JDBC的驱动程序,将下载获得的驱动程序加载进开发环境中。web
(2)在Java程序中加载驱动程序。在Java程序中,能够经过Class.forName(“指定数据库的驱动程序”)的方式来嘉爱添加到开发环境中的驱动程序,例如加载MySQL的数据驱动程序的代码为Class.forName(“com.mysql.jdbc.Driver”)。spring
(3)建立数据链接对象。经过DriverManager类建立数据库链接对象Connection。DriverManager类做用于Java程序和JDBC驱动程序之间,用于检查所加载的驱动程序是否能够创建链接,而后经过它的getConnection方法根据数据库的URL、用户名和密码,建立一个JDBC Connection对象。例如:Connection connection = DriverManager.getConnection(“链接数据库的URL”,”用户名”,”密码”)。其中URL=协议名+IP地址(域名)+端口+数据库名称;用户名和密码是指登陆数据库时所使用的用户名和密码。具体示例建立MySQL的数据库链接代码以下:sql
Connection connectMySQL = DriverManager.getConnection(“jdbc:mysql://localhost:3306/myuser”,”root”,”root”);
(4)建立Statement对象。Statement类的主要是用于执行静态SQL语句并返回它所生产结果的对象。经过Conncetion对象的createStatement()方法能够建立一个Statement对象。例如:Statement statement = connection.createStatement()。具体示例建立Statement对象代码以下:数据库
Statemetn statementMySQL = connectMySQL.createStatement();
(5)调用Statement对象的相关方法执行相应的SQL语句。经过executeUpdate()方法来对数据更新,包括插入和删除等操做,例如向staff表中插入一条数据的代码:apache
statement.excuteUpdate(“INSERT INTO staff(name, age, sex, address, depart, worklen, wage)”+ “VALUES(‘Tom1’, 321, ‘M’,’China’,’Personnel’,’3’,’3000’)”);
经过调用Statement对象的executeQuery()方法进行数据的查询,而查询的结果会获得ResultSet对象,ResultSet表示执行查询数据库后返回的数据的集合,ResultSet对象具备科研指向当前数据行的指针。经过该对象的next()方法,使得指针指向下一行,而后将数据以列号或者字段名取出。若是当next()方法返回null,则表示下一行中没有数据存在。使用示例代码以下:服务器
ResultSet resultSet = statement.executeQuery(“select * from staff”);
(6)关闭数据库链接。使用完数据库或者不须要访问数据库时,经过Connection的close()方法及时关闭数据库。网络
1、Spring链接数据库程序实现(JDBC)
Spring中的JDBC链接与直接使用JDBC去链接仍是有所差异的,Spring对JDBC作了大量的封装,消除了冗余代码,使得开发量大大减少。下面经过一个小例子让你们简单认识Spring中的JDBC操做。
(1)建立数据表结构
1 CREATE table 'user'( 2 'id' int(11) NOT NULL auto_increment, 3 'name' varchar(255) default null, 4 'age' int(11) default null, 5 'sex' varchar(255) default null, 6 primary key ('id') 7 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(2)建立对应数据表的PO。
1 public class User{ 2 private int id; 3 private String name; 4 private int age; 5 private String set; 6 //省略get/set方法 7 }
(3)建立表与实体间的映射
1 public class UserRowMapper implements RowMapper{ 2 @Override 3 public Object mapRow(ResultSet set, int index) throws SQLException { 4 User person = new User(set.getInt("id"), set.getString("name"), set.getInt("age"), set.getString("sex")); 5 return person; 6 } 7 }
(4)建立数据操做接口
1 public interface UserService{ 2 public void save(User user); 3 public List<User> getUsers(); 4 }
(5)建立数据操做接口实现类
1 public class UserServiceImpl implements UserService{ 2 private JdbcTemplate jdbcTemplate; 3 4 //设置数据源 5 public void setDataSource(DataSource dataSource){ 6 this.jdbcTemplate = new JdbcTemplate(dataSource); 7 } 8 9 public void save(User user){ 10 jdbcTemplate.update("insert into user(name, age, sex) value(?, ?, ?)", 11 new Object[] {user.getName(), user.getAge(), user.getSex()}, 12 new int[] {java.sql.Types.VARCHAR, java.sql.Types,INTEGER, java.sql.Types.VARCHAR}); 13 } 14 15 @SuppressWarnings("unchecked") 16 public List<User> getUser() { 17 List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper()); 18 return list; 19 } 20 }
(6)建立Spring配置文件
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.Springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.Springframework.org/schema/beans 5 http://www.Springframework.org/schema/beans/Spring-beans-2.5.xsd"> 6 <!--配置数据源--> 7 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 8 <property name="driverClassName" value="com.mysql.jdbc.Driver"/> 9 <property name="uri" value="jdbc:mysql://localhost:3306/lexueba"/> 10 <property name="username" value="root"/> 11 <property name="password" value="haojia0421xixi" /> 12 <!--链接池启动时的初始值--> 13 <property name="initialSize" value="1"/> 14 <!--链接池的最大值--> 15 <property name="maxActive" value="300"/> 16 <!--最大空闲值,当通过一个最高峰时间后,链接池能够慢慢将已经用不到的链接慢慢释放一部分,一直减小到maxIdle为止--> 17 <property name="maxIdle" value="2"/> 18 <!--最小空闲值,当空闲的链接数少于阀值时,链接池就会预申请去一些链接,以避免洪峰来时来不及申请--> 19 <property name="minIdle" value="1"/> 20 </bean> 21 22 <!--配置业务bean:PersonServiceBean--> 23 <bean id="userService" class="service.UserServiceImpl"> 24 <!--向属性DataSource注入数据源--> 25 <property name="dataSource" ref="dataSource"/> 26 </bean> 27 </beans>
(7)测试
1 public class SpringJDBCTest{ 2 public static void main(String[] args) { 3 ApplicationContext act = new ClassPathXmlApplicationContext("bean.xml"); 4 UserService userService = (UserService) act.getBean("userService"); 5 User user = new User(); 6 user.setName("张三"); 7 user.setAge(20); 8 user.setSex("男"); 9 //保存一条记录 10 userService.save(user); 11 12 List<User> person1 = userService.getUser(); 13 Systemout.out.println("获得全部的User"); 14 for (User person2:person1) { 15 System.out.println(person2.getId() + " " + person2.getName() + " " + person2.getAge() + " " + person2.getSex()); 16 } 17 } 18 }
2、sava/update功能的实现
咱们以上面的例子为基础开始分析Spring中对JDBC的支持,首先寻找整个功能的切入点,在示例中咱们能够看到全部的数据库操做都封装在了UserServiceImpl中,而UserServiceImple中的全部数据库操做又以其内部属性jdbcTemplate为基础。这个jdbcTemplate能够做为源码分析的切入点,咱们一块儿看看它是如何实现定义又是如何被初始化的。
在UserServiceImple中jdbcTemplate的初始化是从setDataSource函数开始的,DataSource实例经过参数注入,DataSource的建立过程是引入第三方的链接池,这里不作过多的介绍。DataSource是整个数据库操做的基础,里面封装了整个数据库的链接信息。咱们首先以保存实体类为例进行代码跟踪。
1 public void save(User user){ 2 jdbcTemplate.update("insert into user(name, age, sex) value(?, ?, ?)", 3 new Object[] {user.getName(), user.getAge(), user.getSex()}, 4 new int[] {java.sql.Types.VARCHAR, java.sql.Types,INTEGER, java.sql.Types.VARCHAR}); 5 }
对于保存一个实体类来说,在操做中咱们只须要提供SQL语句及语句中对应的参数和参数类型,其余操做即可以交由Spring来完成了,这些工做到底包括什么呢?进入jdbcTemplate中的update方法:
1 @Override 2 public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException { 3 return update(new SimplePreparedStatementCreator(sql), pss); 4 } 5 6 @Override 7 public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException { 8 return update(sql, newArgTypePreparedStatementSetter(args, argTypes)); 9 }
进入update方法后,Spring并非急于进入核心处理操做,而是作足了准备工做,使用ArgPreparedStatementSetter对参数与参数类型进行封装,同时又使用Simple PreparedStatementCreator对SQL语句进行封装。
通过数据封装后即可以进入了核心的数据处理代码了。
1 protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss) 2 throws DataAccessException { 3 4 logger.debug("Executing prepared SQL update"); 5 6 return updateCount(execute(psc, ps -> { 7 try { 8 if (pss != null) { 9 //设置PreparedStatement所需的所有参数 10 pss.setValues(ps); 11 } 12 int rows = ps.executeUpdate(); 13 if (logger.isTraceEnabled()) { 14 logger.trace("SQL update affected " + rows + " rows"); 15 } 16 return rows; 17 } 18 finally { 19 if (pss instanceof ParameterDisposer) { 20 ((ParameterDisposer) pss).cleanupParameters(); 21 } 22 } 23 })); 24 }
若是读者了解过其余操做方法,能够知道execute方法是最基础的操做。而其余操做好比update、query等方法则是传入不一样的PreparedStatementCallback参数来执行不一样的逻辑。
(一)基础方法execute
execute做为数据库操做的核心入口,将大多数数据库操做相同的步骤统一封装,而将个性化的操做使用参数PreparedStatementCallback进行回调。
1 public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) 2 throws DataAccessException { 3 4 Assert.notNull(psc, "PreparedStatementCreator must not be null"); 5 Assert.notNull(action, "Callback object must not be null"); 6 if (logger.isDebugEnabled()) { 7 String sql = getSql(psc); 8 logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); 9 } 10 11 //获取数据库链接 12 Connection con = DataSourceUtils.getConnection(obtainDataSource()); 13 PreparedStatement ps = null; 14 try { 15 ps = psc.createPreparedStatement(con); 16 //应用用户设定的输入参数 17 applyStatementSettings(ps); 18 //调用回调函数 19 T result = action.doInPreparedStatement(ps); 20 handleWarnings(ps); 21 return result; 22 } 23 catch (SQLException ex) { 24 // Release Connection early, to avoid potential connection pool deadlock 25 // in the case when the exception translator hasn't been initialized yet. 26 //释放数据库链接避免当异常转换器没有被初始化的时候出现潜在的链接池死锁 27 if (psc instanceof ParameterDisposer) { 28 ((ParameterDisposer) psc).cleanupParameters(); 29 } 30 String sql = getSql(psc); 31 psc = null; 32 JdbcUtils.closeStatement(ps); 33 ps = null; 34 DataSourceUtils.releaseConnection(con, getDataSource()); 35 con = null; 36 throw translateException("PreparedStatementCallback", sql, ex); 37 } 38 finally { 39 if (psc instanceof ParameterDisposer) { 40 ((ParameterDisposer) psc).cleanupParameters(); 41 } 42 JdbcUtils.closeStatement(ps); 43 DataSourceUtils.releaseConnection(con, getDataSource()); 44 } 45 }
以上方法对经常使用操做进行了封装,包括以下几项内容。
一、获取数据库链接
获取数据库链接池也并不是直接使用dataSource.getConnection()方法那么简单,一样也考虑了诸多状况。
1 public static Connection doGetConnection(DataSource dataSource) throws SQLException { 2 Assert.notNull(dataSource, "No DataSource specified"); 3 4 ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); 5 if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { 6 conHolder.requested(); 7 if (!conHolder.hasConnection()) { 8 logger.debug("Fetching resumed JDBC Connection from DataSource"); 9 conHolder.setConnection(fetchConnection(dataSource)); 10 } 11 return conHolder.getConnection(); 12 } 13 // Else we either got no holder or an empty thread-bound holder here. 14 15 logger.debug("Fetching JDBC Connection from DataSource"); 16 Connection con = fetchConnection(dataSource); 17 18 //当前线程支持同步 19 if (TransactionSynchronizationManager.isSynchronizationActive()) { 20 try { 21 // Use same Connection for further JDBC actions within the transaction. 22 // Thread-bound object will get removed by synchronization at transaction completion. 23 //在事务中使用同一数据库链接 24 ConnectionHolder holderToUse = conHolder; 25 if (holderToUse == null) { 26 holderToUse = new ConnectionHolder(con); 27 } 28 else { 29 holderToUse.setConnection(con); 30 } 31 //记录数据库链接 32 holderToUse.requested(); 33 TransactionSynchronizationManager.registerSynchronization( 34 new ConnectionSynchronization(holderToUse, dataSource)); 35 holderToUse.setSynchronizedWithTransaction(true); 36 if (holderToUse != conHolder) { 37 TransactionSynchronizationManager.bindResource(dataSource, holderToUse); 38 } 39 } 40 catch (RuntimeException ex) { 41 // Unexpected exception from external delegation call -> close Connection and rethrow. 42 releaseConnection(con, dataSource); 43 throw ex; 44 } 45 } 46 47 return con; 48 }
在数据库链接方面,Spring主要考虑的是关于事务方面的处理,基于事务处理的特殊性,Spring须要保证线程中的数据库操做都是使用同一事务链接。
二、应用用户设定的输入参数。
1 protected void applyStatementSettings(Statement stmt) throws SQLException { 2 int fetchSize = getFetchSize(); 3 if (fetchSize != -1) { 4 stmt.setFetchSize(fetchSize); 5 } 6 int maxRows = getMaxRows(); 7 if (maxRows != -1) { 8 stmt.setMaxRows(maxRows); 9 } 10 DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); 11 }
setFetchSize最主要是为了减小网络交互次数设计的。访问ResultSet时,若是它每次只从服务器上读取一行数据,则会产生大量的开销。setFetchSize的意思是当调用rs.next时,ResultSet会一次性从服务器上取得多少行数据回来,这样在下次rs.next时,它能够直接从内存中获取数据而不须要网络交互,提升效率。这个设置可能会被某些JDBC驱动忽略,并且设置过大会形成内存的上升。
setMaxRows将此Statement对象生成的全部ResultSet对象能够包含的最大行数限制设置为给定数。
三、调用回调函数
处理一些通用方法外的个性化处理,也就是PreparedStatementCallback类型的参数的doInPreparedStatement方法的回调。
四、警告处理
1 protected void handleWarnings(Statement stmt) throws SQLException { 2 //当设置为忽略警告时只尝试打印日志 3 if (isIgnoreWarnings()) { 4 if (logger.isDebugEnabled()) { 5 //若是日志开启的状况下打印日志 6 SQLWarning warningToLog = stmt.getWarnings(); 7 while (warningToLog != null) { 8 logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" + 9 warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]"); 10 warningToLog = warningToLog.getNextWarning(); 11 } 12 } 13 } 14 else { 15 handleWarnings(stmt.getWarnings()); 16 } 17 }
这里用到了一个类SQLWarning,SQLWarning提供关于数据库访问警告信息的异常。这些警告直接连接到致使报告警告的方法所在的对象。警告能够从Connection、Statement和ResultSet对象中得到。试图在已经关闭的链接上获取警告将致使抛出异常。相似地,试图在已经关闭的语句上或已经关闭的结果集上获取警告也将致使抛出异常。注意,关闭语句时还会关闭它可能生成的结果集。
不少人不是很理解什么状况下会产生警告而不是异常,在这里给读者提示个最多见的警告:DataTruncation,DataTruncation直接继承SQLWaring,因为某种缘由意外地截断数据值时会以DataTruncation警告形式报告异常。
对于警告的处理方式并非直接抛出异常,出现警告极可能会出现数据错误,可是,并不必定会影响程序执行,因此用户能够本身设置处理警告的方式,如默认的是忽略警告,当出现警告时只打印警告日志,而另外一种方式只直接抛出异常。
五、资源释放
数据库的链接释放并非直接调用了Connection的API的close方法。考虑到存在事务的状况,若是当前线程存在事务,那么说明在当前线程中存在共用的数据库链接,在这种状况下直接使用ConnectionHolder中的released方法进行链接数减1,而不是真正的释放链接。进入DataSourceUtils类的releaseConnection函数:
1 public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) { 2 try { 3 doReleaseConnection(con, dataSource); 4 } 5 catch (SQLException ex) { 6 logger.debug("Could not close JDBC Connection", ex); 7 } 8 catch (Throwable ex) { 9 logger.debug("Unexpected exception on closing JDBC Connection", ex); 10 } 11 }
上面函数又调用了本类中的doReleaseConnection函数:
1 public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException { 2 if (con == null) { 3 return; 4 } 5 if (dataSource != null) { 6 //当前线程存在事务的状况下说明存在共用数据库链接直接使用ConnectionHolder中的released方法进行链接数减1,而不是真正的释放链接。 7 ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); 8 if (conHolder != null && connectionEquals(conHolder, con)) { 9 // It's the transactional Connection: Don't close it. 10 conHolder.released(); 11 return; 12 } 13 } 14 doCloseConnection(con, dataSource); 15 }
(二)Update函数
1 protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss) 2 throws DataAccessException { 3 4 logger.debug("Executing prepared SQL update"); 5 6 return updateCount(execute(psc, ps -> { 7 try { 8 if (pss != null) { 9 //设置PreparedStatement所需的所有参数 10 pss.setValues(ps); 11 } 12 int rows = ps.executeUpdate(); 13 if (logger.isTraceEnabled()) { 14 logger.trace("SQL update affected " + rows + " rows"); 15 } 16 return rows; 17 } 18 finally { 19 if (pss instanceof ParameterDisposer) { 20 ((ParameterDisposer) pss).cleanupParameters(); 21 } 22 } 23 })); 24 }
其中用于真正执行SQL的int rows = ps.executeUpdate();没有太多须要讲解的,由于咱们平时在直接使用JDBC方式进行调用的时候常用此方法。可是,对于设置输入参数的函数pss.setValues(ps);,咱们有必要去深刻研究一下。在没有分析代码以前,咱们至少能够知道其功能,不妨再回顾下Spring中使用SQL的执行过程,直接使用:
1 jdbcTemplate.update("insert into user(name, age, sex) value(?, ?, ?)", 2 new Object[] {user.getName(), user.getAge(), user.getSex()}, 3 new int[] {java.sql.Types.VARCHAR, java.sql.Types,INTEGER, java.sql.Types.VARCHAR});
SQL语句对应的参数的类型清晰明了,这都归功于Spring为咱们作了封装,而真正的JDBC调用其实很是繁琐,你须要这么作:
1 PreparedStatement updateSales = con.prepareStatement("insert into user(name, age, sex) values(?, ?, ?)"); 2 updateSales.setString(1, user.getName()); 3 updateSales.setInt(2, user.getAge()); 4 updateSales.setString(3, user.getSex());
那么看看Spring是如何作到封装上面的操做呢?
首先,全部的操做都是以pss.setValues(ps)为入口的。还记得咱们以前的分析路程吗?这个pss所表明的当前类正是ArgumentTypePreparedStatementSetter。其中的setValues方法以下:
1 public void setValues(PreparedStatement ps) throws SQLException { 2 int parameterPosition = 1; 3 if (this.args != null && this.argTypes != null) { 4 //遍历每一个参数以做类型匹配和转换 5 for (int i = 0; i < this.args.length; i++) { 6 Object arg = this.args[i]; 7 //若是是集合类型则须要进入集合类内部递归解析集合内部属性 8 if (arg instanceof Collection && this.argTypes[i] != Types.ARRAY) { 9 Collection<?> entries = (Collection<?>) arg; 10 for (Object entry : entries) { 11 if (entry instanceof Object[]) { 12 Object[] valueArray = ((Object[]) entry); 13 for (Object argValue : valueArray) { 14 doSetValue(ps, parameterPosition, this.argTypes[i], argValue); 15 parameterPosition++; 16 } 17 } 18 else { 19 //解析当前属性 20 doSetValue(ps, parameterPosition, this.argTypes[i], entry); 21 parameterPosition++; 22 } 23 } 24 } 25 else { 26 doSetValue(ps, parameterPosition, this.argTypes[i], arg); 27 parameterPosition++; 28 } 29 } 30 } 31 }
对单个参数及类型的匹配处理:
1 protected void doSetValue(PreparedStatement ps, int parameterPosition, int argType, Object argValue) 2 throws SQLException { 3 4 StatementCreatorUtils.setParameterValue(ps, parameterPosition, argType, argValue); 5 }
上述函数调用了StatementCreatorUtils类的setParameterValue方法,进入:
1 public static void setParameterValue(PreparedStatement ps, int paramIndex, SqlParameter param, 2 @Nullable Object inValue) throws SQLException { 3 4 setParameterValueInternal(ps, paramIndex, param.getSqlType(), param.getTypeName(), param.getScale(), inValue); 5 }
调用了本类的setParameterValueInternal函数,继续进入:
1 private static void setParameterValueInternal(PreparedStatement ps, int paramIndex, int sqlType, 2 @Nullable String typeName, @Nullable Integer scale, @Nullable Object inValue) throws SQLException { 3 4 String typeNameToUse = typeName; 5 int sqlTypeToUse = sqlType; 6 Object inValueToUse = inValue; 7 8 // override type info? 9 if (inValue instanceof SqlParameterValue) { 10 SqlParameterValue parameterValue = (SqlParameterValue) inValue; 11 if (logger.isDebugEnabled()) { 12 logger.debug("Overriding type info with runtime info from SqlParameterValue: column index " + paramIndex + 13 ", SQL type " + parameterValue.getSqlType() + ", type name " + parameterValue.getTypeName()); 14 } 15 if (parameterValue.getSqlType() != SqlTypeValue.TYPE_UNKNOWN) { 16 sqlTypeToUse = parameterValue.getSqlType(); 17 } 18 if (parameterValue.getTypeName() != null) { 19 typeNameToUse = parameterValue.getTypeName(); 20 } 21 inValueToUse = parameterValue.getValue(); 22 } 23 24 if (logger.isTraceEnabled()) { 25 logger.trace("Setting SQL statement parameter value: column index " + paramIndex + 26 ", parameter value [" + inValueToUse + 27 "], value class [" + (inValueToUse != null ? inValueToUse.getClass().getName() : "null") + 28 "], SQL type " + (sqlTypeToUse == SqlTypeValue.TYPE_UNKNOWN ? "unknown" : Integer.toString(sqlTypeToUse))); 29 } 30 31 if (inValueToUse == null) { 32 setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse); 33 } 34 else { 35 setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse); 36 } 37 }
3、query功能的实现
在以前的章节中咱们介绍了update方法的功能实现。那么在数据库操做中查找操做也是使用很是高的函数,一样咱们也须要了解它的实现过程。使用方法以下:
1 List<User> list = jdbcTemplate.query("select * from user where age=?", 2 new Object[][20], new int[]{java.sql.Types.INTEGER}, new UserRowMapper());
跟踪jdbcTemplate的query方法:
1 @Override 2 public <T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException { 3 return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper))); 4 }
1 @Override 2 @Nullable 3 public <T> T query(String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException { 4 return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse); 5 }
上面的函数中和update方法中一样使用了 newArgTypePreparedStatementSetter。
1 public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException { 2 return query(new SimplePreparedStatementCreator(sql), pss, rse); 3 }
1 public <T> T query( 2 PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) 3 throws DataAccessException { 4 5 Assert.notNull(rse, "ResultSetExtractor must not be null"); 6 logger.debug("Executing prepared SQL query"); 7 8 return execute(psc, new PreparedStatementCallback<T>() { 9 @Override 10 @Nullable 11 public T doInPreparedStatement(PreparedStatement ps) throws SQLException { 12 ResultSet rs = null; 13 try { 14 if (pss != null) { 15 //设置PreparedStatement所需的所有参数 16 pss.setValues(ps); 17 } 18 rs = ps.executeQuery(); 19 return rse.extractData(rs); 20 } 21 finally { 22 JdbcUtils.closeResultSet(rs); 23 if (pss instanceof ParameterDisposer) { 24 ((ParameterDisposer) pss).cleanupParameters(); 25 } 26 } 27 } 28 }); 29 }
能够看到总体套路和update差很少,只不过在回调类PreparedStatementCallback的实现中使用的是ps.executeQuery()执行查询操做,并且在返回方法上也作了一些额外的处理。
rse.extractData(rs)方法负责将结果进行封装并转换到POJO,rse当前表明的类为RowMapperResultSetExtractor,而在构造RowMapperResultSetExtractor的时候咱们又将自定义的rowMapper设置了进去。调用代码以下:
1 public List<T> extractData(ResultSet rs) throws SQLException { 2 List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>()); 3 int rowNum = 0; 4 while (rs.next()) { 5 results.add(this.rowMapper.mapRow(rs, rowNum++)); 6 } 7 return results; 8 }
上面的代码并无什么负责的逻辑,只是对返回的结果遍历并以此使用rowMapper进行转换。
以前降了update方法以及query方法,使用这两个函数示例的SQL都是带参数值的,也就是带有“?”的,那么还有另外一种状况是不带有“?”的,Spring中使用的是另外一种处理方式,例如:
List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper());
跟踪进入:
1 public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { 2 Assert.notNull(sql, "SQL must not be null"); 3 Assert.notNull(rse, "ResultSetExtractor must not be null"); 4 if (logger.isDebugEnabled()) { 5 logger.debug("Executing SQL query [" + sql + "]"); 6 } 7 8 /** 9 * Callback to execute the query. 10 */ 11 class QueryStatementCallback implements StatementCallback<T>, SqlProvider { 12 @Override 13 @Nullable 14 public T doInStatement(Statement stmt) throws SQLException { 15 ResultSet rs = null; 16 try { 17 rs = stmt.executeQuery(sql); 18 return rse.extractData(rs); 19 } 20 finally { 21 JdbcUtils.closeResultSet(rs); 22 } 23 } 24 @Override 25 public String getSql() { 26 return sql; 27 } 28 } 29 30 return execute(new QueryStatementCallback()); 31 }
与以前的query方法最大的不一样是少了参数及参数类型的传递,天然也少了PreparedStatementSetter类型的封装。既然少了PreparedStatementSetter类型的传入,调用的execute方法天然也会有所改变了。
1 public <T> T execute(StatementCallback<T> action) throws DataAccessException { 2 Assert.notNull(action, "Callback object must not be null"); 3 4 Connection con = DataSourceUtils.getConnection(obtainDataSource()); 5 Statement stmt = null; 6 try { 7 stmt = con.createStatement(); 8 applyStatementSettings(stmt); 9 T result = action.doInStatement(stmt); 10 handleWarnings(stmt); 11 return result; 12 } 13 catch (SQLException ex) { 14 // Release Connection early, to avoid potential connection pool deadlock 15 // in the case when the exception translator hasn't been initialized yet. 16 String sql = getSql(action); 17 JdbcUtils.closeStatement(stmt); 18 stmt = null; 19 DataSourceUtils.releaseConnection(con, getDataSource()); 20 con = null; 21 throw translateException("StatementCallback", sql, ex); 22 } 23 finally { 24 JdbcUtils.closeStatement(stmt); 25 DataSourceUtils.releaseConnection(con, getDataSource()); 26 } 27 }
这个execute与以前的execute并没有太大的差异,都是作一些常规的处理,诸如获取链接、释放链接等,可是,有一个地方是不同的,就是statement的建立。这里直接使用connection建立,而带有参数的SQL使用的是PreparedStatementCreator类来建立的。一个是普通的Statement,另外一个是PreparedStatement,二者到底是何区别呢?
PreparedStatement接口继承Statement,并与之在两方面有所不一样。
一、PreparedStatement实例包含已经编译的SQL语句。这就是使语句“准备好”。包含于PreparedStatement对象中的SQL语句可具备一个或者多个IN参数。IN参数的值在SQL语句建立时未被指定。相反的,该语句为每一个IN参数保留一个问号(“?”)做为占位符。每一个问号的值必须在该语句执行以前,经过适当的setXXX方法来提供。
二、因为PreparedStatement对象已预编译过,因此其执行速度要快于Statement对象。所以,屡次执行的SQL语句常常建立为PreparedStatement对象,以提升效率。
做为Statement的子类,PreparedStatement继承了Statement的全部功能。另外,它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值。同时,三种方法execute、executeQuery和executeUpdate已被更改以使之再也不须要参数。这些方法的Statement形式(接收SQL语句参数的形式)不该该用于PreparedStatement对象。
4、queryForObject
Spring中不只为咱们提供了query方法,还在此基础上作了封装,提供了不一样类型的query方法。
咱们以queryForObject为例,来讨论一下Spring是如何在返回结果的基础上进行封装的。
1 public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException { 2 return queryForObject(sql, getSingleColumnRowMapper(requiredType)); 3 }
1 public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException { 2 List<T> results = query(sql, rowMapper); 3 return DataAccessUtils.nullableSingleResult(results); 4 }
其实最大的不一样仍是对于RowMapper的使用,SingleColumnRowMapper类中的mapRow:
1 public T mapRow(ResultSet rs, int rowNum) throws SQLException { 2 // Validate column count. 3 //验证返回结果 4 ResultSetMetaData rsmd = rs.getMetaData(); 5 int nrOfColumns = rsmd.getColumnCount(); 6 if (nrOfColumns != 1) { 7 throw new IncorrectResultSetColumnCountException(1, nrOfColumns); 8 } 9 10 // Extract column value from JDBC ResultSet. 11 //抽取第一个结果进行处理 12 Object result = getColumnValue(rs, 1, this.requiredType); 13 if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) { 14 // Extracted value does not match already: try to convert it. 15 //转换到对象的类型 16 try { 17 return (T) convertValueToRequiredType(result, this.requiredType); 18 } 19 catch (IllegalArgumentException ex) { 20 throw new TypeMismatchDataAccessException( 21 "Type mismatch affecting row number " + rowNum + " and column type '" + 22 rsmd.getColumnTypeName(1) + "': " + ex.getMessage()); 23 } 24 } 25 return (T) result; 26 }
对应的类型转换函数:
1 protected Object convertValueToRequiredType(Object value, Class<?> requiredType) { 2 if (String.class == requiredType) { 3 return value.toString(); 4 } 5 else if (Number.class.isAssignableFrom(requiredType)) { 6 if (value instanceof Number) { 7 // Convert original Number to target Number class. 8 //转换原始的Number类型的实体到Number类 9 return NumberUtils.convertNumberToTargetClass(((Number) value), (Class<Number>) requiredType); 10 } 11 else { 12 // Convert stringified value to target Number class. 13 //转换String类型的值到Number类 14 return NumberUtils.parseNumber(value.toString(),(Class<Number>) requiredType); 15 } 16 } 17 else if (this.conversionService != null && this.conversionService.canConvert(value.getClass(), requiredType)) { 18 return this.conversionService.convert(value, requiredType); 19 } 20 else { 21 throw new IllegalArgumentException( 22 "Value [" + value + "] is of type [" + value.getClass().getName() + 23 "] and cannot be converted to required type [" + requiredType.getName() + "]"); 24 } 25 }
本文摘自《Spring源码深度解析》数据库链接JDBC,做者:郝佳。本文代码基于的Spring版本为5.2.4.BUILD-SNAPSHOT,和原书代码部分会略有不一样。
拓展阅读:
Spring框架之beans源码彻底解析
Spring框架之AOP源码彻底解析
Spring框架之jms源码彻底解析
Spring框架之spring-web http源码彻底解析
Spring框架之spring-web web源码彻底解析
Spring框架之spring-webmvc源码彻底解析
Spring源码深度解析之Spring MVC