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