用過JDBC(Java DataBase Connectivity,Java資料庫連接)的人都知道,JDBC非常臃腫,一點也不可愛,以致于我們每次使用JDBC操作資料庫時,總會忍不住吐槽,為了讓大家少些吐槽,多些舒心;致力于簡化Java開發的Spring果斷出手,簡化了JDBC,把它封裝成為Spring旗下的一個重要模塊,這個模塊就是著名的Spring JDBC,至于簡化了多少,且讓我們先用傳統的JDBC實作一個小專案,再用Spring JDBC對其進行改進,進而比較直觀地了解Spring JDBC對JDBC做的簡化,學習Spring JDBC的基礎知識,
這個例子非常簡單,就往資料庫里插入人的資訊,之后查詢出來進行顯示,因此,我們需要創建一個資料庫,一張資料庫表,用于保存人的資訊,而這,可以通過打開先前安裝過的MYSQL Workbench,執行以下SQL腳本進行創建:
1 CREATE DATABASE sj_person_jdbc DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; 2 3 USE sj_person_jdbc; 4 5 CREATE TABLE person ( 6 person_id INT NOT NULL AUTO_INCREMENT, # 資料庫表ID 7 person_name VARCHAR(50) NOT NULL, # 名字 8 person_gender VARCHAR(50) NOT NULL, # 性別 9 PRIMARY KEY (person_id) # 添加主鍵 10 ) ENGINE = INNODB;
建好資料庫之后,還需新建一個Java專案,用于實作前文提到的功能,這個專案名叫person,同先前一樣打開IntelliJ IDEA進行創建即可,不同的是,這個專案需與資料庫打交道,因此,還需添加資料庫驅動程式JAR包,資料庫驅動程式JAR包無需額外下載,我們安裝MYSQL的時候已經裝上了,只需打開MYSQL的安裝目錄(默認裝在C:\Program Files (x86)\MySQL\Connector J 8.0\),把檔案mysql-connector-java-8.0.23.jar復制到專案的libs目錄即可,
注意:復制資料庫驅動程式JAR包到libs目錄之后,可能需要重啟一下IntelliJ IDEA,不然,IntelliJ IDEA可能不會加載新增的JAR包,導致編譯錯誤,
我們知道使用JDBC操作資料庫之前,首先應該獲取資料庫連接,獲取資料庫連接的方式通常有兩種:一種是通過DriverManager,一種是通過資料源,
通過DriverManager獲取資料庫連接并不是一個好的方式,為什么呢?因為DriverManager并不支持連接池(Connection Pool),這意味著每次通過DriverManager獲取資料庫連接時都得建立新的連接,這個程序涉及網路連接的建立,協議的交換,身份的驗證,等等,既耗資源,又費時間,一點也不劃算,因此,進行軟體開發的時候,通常不會選用這種方式,
于是,第二種方式出現了,這種方式涉及JDBC提供的一個介面:javax.sql.DataSource,這個介面就是通常所說的資料源,目前,有很多廠商對該介面進行了實作,使之作為一個組件具有把資料庫連接快取到連接池,以及對連接池里的連接進行管理的功能,這樣,每次通過資料源獲取資料庫連接時,只需從連接池里獲取,無需花費大量的資源和時間建立新的連接;用完之后把連接交還連接池就行,由此可見,通過資料源獲取資料庫連接可以大幅提高性能,正因如此,進行軟體開發的時候,通常選用這種方式,我們的小專案也不例外,
需要了解的是,雖然在這世上實作和提供資料源的廠商遠遠不止一家,但是,常用的資料源總是那樣幾種,比如DBCP,C3P0,Druid,等等,我們的小專案選用的是DBCP,而這,需要我們下載DBCP相關的JAR包并添加到我們的小專案中,如下:
1.Apache Commons DBCP,下載檔案commons-dbcp2-2.8.0-bin.zip:
http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
2.Apache Commons Pool,下載檔案commons-pool2-2.9.0-bin.zip:
http://commons.apache.org/proper/commons-pool/download_pool.cgi
3.Apache Commons Logging,下載檔案commons-logging-1.2-bin.zip:
http://commons.apache.org/proper/commons-logging/download_logging.cgi
下載完成之后解壓.zip檔案,把下面這些JAR包復制到libs目錄即可:
1.commons-dbcp2-2.8.0.jar
2.commons-pool2-2.9.0.jar
3.commons-logging-1.2.jar
現在,萬事俱備,可以開始寫代碼了,我們的目標是使用JDBC實作兩個功能,即往資料庫里插入人的資訊,以及從資料庫里查出人的資訊進行顯示,因此,我們需要一個資料模型類,用于保存人的資訊,如下所示:
1 package com.dream; 2 3 public class Person { 4 private int id = 0; 5 private String name = null; 6 private String gender = null; 7 8 public int getId() { 9 return this.id; 10 } 11 12 public void setId(int id) { 13 this.id = id; 14 } 15 16 public String getName() { 17 return this.name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 24 public String getGender() { 25 return this.gender; 26 } 27 28 public void setGender(String gender) { 29 this.gender = gender; 30 } 31 }
資料模型Person類定義了三個屬性:id(資料庫表ID),name(名字),gender(性別),這三個屬性是和資料庫表person的三個欄位一一對應的,剛好能夠作為資料模型,在與資料庫打交道時保存人的資訊,當然,除了資料模型,我們還需考慮如何定義一個資料存取類,用于插入和查詢人的資訊,如下所示:
1 package com.dream; 2 3 import java.sql.*; 4 import java.util.*; 5 import javax.sql.*; 6 import org.apache.commons.dbcp2.*; 7 8 public class DaoPerson { 9 public int addPerson(Person person) { 10 int updatedCount = 0; 11 Connection connection = null; 12 PreparedStatement statement = null; 13 SQLException sqlException = null; 14 try { 15 var sql = " INSERT INTO person" 16 + " (person_name, person_gender)" 17 + " VALUES" 18 + " (?, ?)"; 19 var dataSource = this.getDataSource(); 20 connection = dataSource.getConnection(); 21 statement = connection.prepareStatement(sql); 22 statement.setString(1, person.getName()); 23 statement.setString(2, person.getGender()); 24 updatedCount = statement.executeUpdate(); 25 } catch (SQLException e) { 26 sqlException = e; 27 } finally { 28 if (statement != null) { 29 try { 30 statement.close(); 31 } catch (SQLException e) { 32 if (sqlException == null) { 33 sqlException = e; 34 } 35 } 36 } 37 if (connection != null) { 38 try { 39 connection.close(); 40 } catch (SQLException e) { 41 if (sqlException == null) { 42 sqlException = e; 43 } 44 } 45 } 46 if (sqlException != null) { 47 throw new RuntimeException(sqlException); 48 } 49 } 50 return updatedCount; 51 } 52 53 public List<Person> queryPersonByName(String personName) { 54 List<Person> personList = new ArrayList<>(); 55 Connection connection = null; 56 PreparedStatement statement = null; 57 SQLException sqlException = null; 58 try { 59 var sql = " SELECT" 60 + " person_id, person_name, person_gender" 61 + " FROM" 62 + " person" 63 + " WHERE" 64 + " person_name = ?"; 65 var dataSource = this.getDataSource(); 66 connection = dataSource.getConnection(); 67 statement = connection.prepareStatement(sql); 68 statement.setString(1, personName); 69 var result = statement.executeQuery(); 70 while (result.next()) { 71 var person = new Person(); 72 person.setId(result.getInt(1)); 73 person.setName(result.getString(2)); 74 person.setGender(result.getString(3)); 75 personList.add(person); 76 } 77 } catch (SQLException e) { 78 sqlException = e; 79 } finally { 80 if (statement != null) { 81 try { 82 statement.close(); 83 } catch (SQLException e) { 84 if (sqlException == null) { 85 sqlException = e; 86 } 87 } 88 } 89 if (connection != null) { 90 try { 91 connection.close(); 92 } catch (SQLException e) { 93 if (sqlException == null) { 94 sqlException = e; 95 } 96 } 97 } 98 if (sqlException != null) { 99 throw new RuntimeException(sqlException); 100 } 101 } 102 return personList; 103 } 104 105 private DataSource getDataSource() { 106 var dataSource = new BasicDataSource(); 107 dataSource.setUsername("root"); 108 dataSource.setPassword("123456"); 109 dataSource.setUrl("jdbc:mysql://localhost:3306/sj_person_jdbc"); 110 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 111 return dataSource; 112 } 113 }
經過一段時間的努力,我們終于把資料存取類DaoPerson(DAO,Data Access Object,資料存取物件)寫完了,不禁感嘆,該類的代碼真的太長了!卻主要定義了三個方法:getDataSource,addPerson,queryPersonByName,
getDataSource方法只做一件事:創建資料源物件,也就是創建BasicDataSource類的實體,BasicDataSource類是DBCP提供的一種資料源實作,實作了javax.sql.DataSource介面,使之具有快取資料庫連接到連接池以及對連接池里的連接進行管理的功能,之后,再把資料庫用戶名,密碼,URL以及驅動類這些資訊賦給剛剛創建的資料源物件;最后回傳資料源物件,這樣,其它代碼就能呼叫這個方法創建資料源物件,再經資料源物件獲得資料庫連接了,
addPerson方法實作了資料的插入,也就是把人的資訊插入資料庫,這個程序涉及這些步驟:
01.呼叫getDataSource方法創建資料源物件,
02.通過資料源物件獲得資料庫連接物件,
03.通過資料庫連接物件獲得PreparedStatement物件,
04.呼叫PreparedStatement物件的方法設定名字,性別兩個SQL引數,
05.執行SQL陳述句插入人的資訊,
06.處理SQL陳述句執行例外,
07.關閉PreparedStatement
08.處理PreparedStatement關閉例外,
09.關閉資料庫連接,
10.處理資料庫連接關閉例外,
queryPersonByName方法實作了資料的查詢,也就是根據人的名字查出人的資訊,這個程序涉及這些步驟:
01.呼叫getDataSource方法創建資料源物件,
02.通過資料源物件獲得資料庫連接物件,
03.通過資料庫連接物件獲得PreparedStatement物件,
04.呼叫PreparedStatement物件的方法設定名字這個SQL引數,
05.執行SQL陳述句查出人的資訊,
06.處理SQL陳述句執行例外,
07.關閉PreparedStatement
08.處理PreparedStatement關閉例外,
09.關閉資料庫連接,
10.處理資料庫連接關閉例外,
看完這些步驟之后,想必“我和我的小伙伴們都驚呆了”,addPerson和queryPersonByName方法相比,除了第四步,第五步之外,其它步驟竟然都是一樣的!這意味著我們正在不知疲倦地重復敲寫同樣的代碼(俗稱樣板代碼)實作不同的資料庫操作,
這,不是糟透了嗎?
于是我們不禁追問:“難道就沒有什么辦法能夠消除這些糟糕的樣板代碼,讓事情變得簡單一些,優雅一些嗎?”
當然有的,比如,我們可以寫些資料庫存取類,把JDBC那些樣板代碼封裝起來,爾后使用這些封裝好的類進行資料庫存取,這樣不就可以消除樣板代碼,讓事情簡單起來了嗎?關于這點,從來都是英雄所見略同,Spring也是這樣想的,于是Spring大刀闊斧,把JDBC那些樣板代碼封裝起來,消除了樣板代碼,產出Spring JDBC這個模塊,至于Spring JDBC能夠簡化多少代碼,且讓我們使用Spring JDBC重新實作一下addPerson方法,看看實際的效果,如下所示:
1 public int addPerson(Person person) { 2 var sql = " INSERT INTO person" 3 + " (person_name, person_gender)" 4 + " VALUES" 5 + " (?, ?)"; 6 var dataSource = this.getDataSource(); 7 var jdbcTemplate = new JdbcTemplate(dataSource); 8 var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() { 9 @Override 10 public void setValues(PreparedStatement preparedStatement) throws SQLException { 11 preparedStatement.setString(1, person.getName()); 12 preparedStatement.setString(2, person.getGender()); 13 } 14 }); 15 return updatedCount; 16 }
哇,代碼一下就減少了,而且減少不止一點!以前,我們進行資料插入時,總要經過以下十步:
01.呼叫getDataSource方法創建資料源物件,
02.通過資料源物件獲得資料庫連接物件,
03.通過資料庫連接物件獲得PreparedStatement物件,
04.呼叫PreparedStatement物件的方法設定名字,性別兩個SQL引數,
05.執行SQL陳述句插入人的資訊,
06.處理SQL陳述句執行例外,
07.關閉PreparedStatement
08.處理PreparedStatement關閉例外,
09.關閉資料庫連接,
10.處理資料庫連接關閉例外,
如今不用了,只需三步即可搞定:
1.呼叫getDataSource方法創建資料源物件,
2.以資料源物件作為引數創建JdbcTemplate物件,
3.呼叫JdbcTemplate物件的update方法執行SQL陳述句,插入人的資訊,
超級簡單,對不對?于是問題來了:“通過兩種實作方式的強烈對比,我們深刻體會到了Spring JDBC對JDBC的簡化,可是,JdbcTemplate是什么呀?Spring JDBC封裝JDBC之后產生的一個類嗎?我們應該怎樣用它?”帶著這些問題,懷揣著美好的求知欲,我們踏上征途,上下求索,正如大家想的那樣,JdbcTemplate就是Spring JDBC封裝JDBC之后產生的一個類,而且是一個非常重要的核心類,該類定義了一些方法,以供我們呼叫之后執行任何我們想要執行的SQL陳述句,完成任何我們想做的資料庫操作,比如創建資料庫表,添加資料庫索引,增刪改查資料,等等,前文實作的addPerson方法正是通過呼叫JdbcTempdate的update方法實作人的資訊的插入的,update方法的簽名如下:
public int update(String sql, PreparedStatementSetter pss)
update方法接受兩個引數,第一個引數是String型別的,用于指定即將執行的SQL陳述句;第二個引數是PreparedStatementSetter型別的,用于指定PreparedStatementSetter型別的物件,設定SQL引數,addPerson方法呼叫update方法時,指定了這條SQL陳述句:
1 var sql = " INSERT INTO person" 2 + " (person_name, person_gender)" 3 + " VALUES" 4 + " (?, ?)";
這條SQL陳述句具有兩個引數占位符,分別對應即將插入的person_name,person_gender,這意味著執行這條SQL陳述句之前,需先設好這些SQL引數,問題在于,這些SQL引數是怎樣設定的呢?
回答這個問題之前,最好先來瞧瞧update方法執行SQL陳述句的程序,大概如下:
1.通過資料源物件獲得資料庫連接物件,
2.通過資料庫連接物件獲得PreparedStatement物件,
3.以PreparedStatement物件作為引數,呼叫PreparedStatementSetter介面的setValues方法設定SQL引數,
4.執行SQL陳述句插入資料,
5.關閉PreparedStatement,關閉資料庫連接,處理例外,
看到了吧?問題的關鍵在于第三步,而這,與PreparedStatementSetter介面息息相關,PreparedStatementSetter介面定義如下:
1 @FunctionalInterface 2 public interface PreparedStatementSetter { 3 void setValues(PreparedStatement ps) throws SQLException; 4 }
這是一個函式式介面,專門用來設定SQL引數,因此,addPerson方法呼叫update方法時,以匿名類實作了PreparedStatementSetter介面,并作為第二個引數傳了進去,如下代碼片段所示:
1 var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() { 2 @Override 3 public void setValues(PreparedStatement preparedStatement) throws SQLException { 4 preparedStatement.setString(1, person.getName()); 5 preparedStatement.setString(2, person.getGender()); 6 } 7 });
看到了吧?在實作了PreparedStatementSetter介面的匿名類里,setValues方法設定了兩個SQL引數,這樣,update方法呼叫這個匿名類的setValues方法之后,就能正確設定SQL引數,從而順利執行SQL陳述句了,SQL陳述句執行完成之后,update方法將回傳受影響的行數,也就是成功插入資料庫的數目,
還有,PreparedStatementSetter是一個函式式介面,自然也能使用lambda運算式進行實作,而且,如果使用lambda運算式進行實作的話,addPerson方法將會更加簡潔,大家不妨動手試試,另外,為了方便大家執行無需任何引數的SQL陳述句,Spring還提供了以下這種多載形式的update方法:
public int update(String sql)
這個方法非常簡單,傳入一條SQL陳述句即可執行,大家一看就知道怎么用了,這里不作介紹,卻該好好聊聊update方法能做的另外兩件重要的事情,興許大家已經猜到,除了插入之外,update方法還特別善長資料的洗掉和修改,呼叫update方法進行資料的洗掉和修改與呼叫update方法進行資料的插入并無分別,只是執行的SQL陳述句不同而已,這里不作太多介紹,只向大家示例兩個方法,大家看了之后自然知道怎么使用,這兩個方法一個名為deletePersonByName,一個名為updatePersonByName,deletePersonByName方法用于洗掉名字等于某值的人的資訊;updatePersonByName方法用于把人的名字改成新的名字,代碼如下:
public int deletePersonByName(String personName) { var sql = "DELETE FROM person WHERE person_name = ?"; var dataSource = this.getDataSource(); var jdbcTemplate = new JdbcTemplate(dataSource); var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement preparedStatement) throws SQLException { preparedStatement.setString(1, personName); } }); return updatedCount; } public int updatePersonByName(String personName, String newPersonName) { var sql = "UPDATE person SET person_name = ? WHERE person_name = ?"; var dataSource = this.getDataSource(); var jdbcTemplate = new JdbcTemplate(dataSource); var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement preparedStatement) throws SQLException { preparedStatement.setString(1, newPersonName); preparedStatement.setString(2, personName); } }); return updatedCount; }
增刪改既已談過,也就是時候聊聊查詢那些事了,不知大家可還記得,前文曾經寫過queryPersonByName方法,當時,這個方法是用JDBC實作的,能夠根據人的名字查出人的資訊,現在,讓我們看看這個方法能用JdbcTemplate怎么實作,如下所示:
1 public List<Person> queryPersonByName(String personName) { 2 var sql = " SELECT" 3 + " person_id, person_name, person_gender" 4 + " FROM" 5 + " person" 6 + " WHERE" 7 + " person_name = ?"; 8 var dataSource = this.getDataSource(); 9 var jdbcTemplate = new JdbcTemplate(dataSource); 10 var personList = jdbcTemplate.query(sql, new PreparedStatementSetter() { 11 @Override 12 public void setValues(PreparedStatement preparedStatement) throws SQLException { 13 preparedStatement.setString(1, personName); 14 } 15 }, new RowMapper<Person>() { 16 @Override 17 public Person mapRow(ResultSet resultSet, int i) throws SQLException { 18 var person = new Person(); 19 person.setId(resultSet.getInt(1)); 20 person.setName(resultSet.getString(2)); 21 person.setGender(resultSet.getString(3)); 22 return person; 23 } 24 }); 25 return personList; 26 }
可以看到,這里呼叫了JdbcTemplate的query方法,方法簽名如下:
public <T> List<T> query(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper)
這個方法接受三個引數,前兩個引數和update方法一樣,用于傳入即將執行的SQL陳述句和通過實作PreparedStatementSetter介面設定SQL引數,第三個引數則不同,是支持泛型的RowMapper<T>介面,重點在于,這個介面是干嘛用的?
回答這個問題之前,最好先來瞧瞧query方法執行SQL陳述句的程序,大概如下:
01.通過資料源物件獲得資料庫連接物件,
02.通過資料庫連接物件獲得PreparedStatement物件,
03.以PreparedStatement物件作為引數,呼叫PreparedStatementSetter介面的setValues方法設定SQL引數,
04.執行SQL陳述句查詢資料,得到ResultSet物件,
05.創建一個空的結果串列,
06.回圈呼叫ResultSet物件的next()方法,遍歷查詢結果,每遍歷一條查詢結果,就以遍歷到的ResultSet物件和當前物件的索引(也就是遍歷到的第幾條資料)作為引數,呼叫一次RowMapper<T>介面的mapRow方法,
07.mapRow方法處理查詢結果之后,回傳一個物件,這個物件的型別,就是我們實作RowMapper<T>介面時指定的型別,
08.把呼叫mapRow方法回傳的物件存進結果串列,
09.如此回圈遍歷完成之后,關閉PreparedStatement,關閉資料庫連接,處理例外,
10.回傳結果串列,
看到了吧?問題的關鍵在于第六步和第七步,而這,與RowMapper<T>介面息息相關,RowMapper<T>介面定義如下:
@FunctionalInterface public interface RowMapper<T> { @Nullable T mapRow(ResultSet rs, int rowNum) throws SQLException; }
不出大家所料,RowMapper<T>也是一個函式式介面,而且是一個支持泛型的函式式介面,專門用來處理query方法每次遍歷到的查詢結果,因此,queryPersonByName方法呼叫query方法時,以匿名類實作了RowMapper<T>介面,并作為第三個引數傳了進去,如下代碼片段所示:
1 var personList = jdbcTemplate.query(sql, new PreparedStatementSetter() { 2 @Override 3 public void setValues(PreparedStatement preparedStatement) throws SQLException { 4 preparedStatement.setString(1, personName); 5 } 6 }, new RowMapper<Person>() { 7 @Override 8 public Person mapRow(ResultSet resultSet, int i) throws SQLException { 9 var person = new Person(); 10 person.setId(resultSet.getInt(1)); 11 person.setName(resultSet.getString(2)); 12 person.setGender(resultSet.getString(3)); 13 return person; 14 } 15 });
看到了吧?在實作了RowMapper<T>介面的匿名類里,mapRow方法創建了Person物件,并從ResultSet物件里拿到資料之后進行填充,這樣,query方法每遍歷到一個ResultSet查詢結果,就呼叫一次匿名類的mapRow方法,mapRow方法處理ResultSet查詢結果之后,回傳一個Person型別的物件,這個物件被query方法存進Person型別的結果串列里,待到所有結果遍歷完成之后,query方法就將Person型別的結果串列回傳,于是,呼叫query方法之后,我們能夠順利拿到Person型別的結果串列,另外,為了方便大家執行無需任何引數的SQL陳述句,Spring還提供了這種多載的query方法:
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
這種多載除了缺少PreparedStatementSetter型別的引數之外,其它都是一樣的,大家一看就知道怎么用了,這里不作介紹,卻該好好聊聊另外一種常見的查詢,很多時候,我們想查的資料并非串列形式的;而是一個整數,一個字串,一個物件,等等,比如,這個方法執行之后,就只回傳一個Person物件:
1 public Person queryPersonById(int id) { 2 var sql = " SELECT" 3 + " person_id, person_name, person_gender" 4 + " FROM" 5 + " person" 6 + " WHERE" 7 + " person_id = ?"; 8 var dataSource = this.getDataSource(); 9 var jdbcTemplate = new JdbcTemplate(dataSource); 10 var person = jdbcTemplate.query(sql, new PreparedStatementSetter() { 11 @Override 12 public void setValues(PreparedStatement preparedStatement) throws SQLException { 13 preparedStatement.setInt(1, id); 14 } 15 }, new ResultSetExtractor<Person>() { 16 @Override 17 public Person extractData(ResultSet resultSet) throws SQLException, DataAccessException { 18 if (resultSet.next()) { 19 var person = new Person(); 20 person.setId(resultSet.getInt(1)); 21 person.setName(resultSet.getString(2)); 22 person.setGender(resultSet.getString(3)); 23 return person; 24 } else { 25 return null; 26 } 27 } 28 }); 29 return person; 30 }
這里呼叫了query方法的另外一種多載,方法簽名如下:
public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse)
這種多載的query方法同樣接受三個引數,前兩個引數是一樣的,第三個引數則不同,是支持泛型的ResultSetExtractor<T>介面,重點同樣在于,這個介面是干嘛用的?
回答這個問題之前,最好先來瞧瞧query方法執行SQL陳述句的程序,大概如下:
1.通過資料源物件獲得資料庫連接物件,
2.通過資料庫連接物件獲得PreparedStatement物件,
3.以PreparedStatement物件作為引數,呼叫PreparedStatementSetter介面的setValues方法設定SQL引數,
4.執行SQL陳述句查詢資料,得到ResultSet物件,
5.以ResultSet物件作為引數,直接呼叫ResultSetExtractor<T>介面的extractData方法進行查詢結果的處理,
6.關閉PreparedStatement,關閉資料庫連接,處理例外,
7.回傳extractData方法處理之后的結果,
看到了吧?問題的關鍵在于第五步,而這,與ResultSetExtractor<T>介面息息相關,ResultSetExtractor<T>介面定義如下:
1 @FunctionalInterface 2 public interface ResultSetExtractor<T> { 3 @Nullable 4 T extractData(ResultSet rs) throws SQLException, DataAccessException; 5 }
和大家想的一樣,ResultSetExtractor<T>同樣也是一個函式式介面,而且是一個支持泛型的函式式介面,專門用來處理查詢結果,因此,queryPersonById方法呼叫query方法時,以匿名類實作了ResultSetExtractor<T>介面,并作為第三個引數傳了進去,如下代碼片段所示:
1 var person = jdbcTemplate.query(sql, new PreparedStatementSetter() { 2 @Override 3 public void setValues(PreparedStatement preparedStatement) throws SQLException { 4 preparedStatement.setInt(1, id); 5 } 6 }, new ResultSetExtractor<Person>() { 7 @Override 8 public Person extractData(ResultSet resultSet) throws SQLException, DataAccessException { 9 if (resultSet.next()) { 10 var person = new Person(); 11 person.setId(resultSet.getInt(1)); 12 person.setName(resultSet.getString(2)); 13 person.setGender(resultSet.getString(3)); 14 return person; 15 } else { 16 return null; 17 } 18 } 19 });
看到了吧?在實作了ResultSetExtractor<T>介面的匿名類里,extractData方法呼叫ResultSet物件的next()方法獲取查詢結果,之后創建Person物件,把ResultSet的資料填充進去,回傳Person物件,同樣的,為了方便大家執行無需任何引數的SQL陳述句,Spring還提供了這種多載的query方法:
public <T> T query(String sql, ResultSetExtractor<T> rse)
現在,我們已經知道怎樣使用Spring JDBC進行增刪改查了,增刪改查只是Spring JDBC提供的基本功能,還有很多東西沒有討論,比如如何進行例外處理,如何執行存盤程序,如何執行事務,等等,我們將在“細說Spring JDBC”的時候進行介紹,現在,我們只需知道怎樣使用Spring JDBC進行增刪改查就行,卻從下章開始先來瞧瞧Spring關于簡化Web開發的那些趣事,歡迎大家繼續閱讀,謝謝大家!
回傳目錄 下載代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/342102.html
標籤:其他
上一篇:深入理解和運用Pandas的GroupBy機制——理解篇
下一篇:Python 學習筆記(五)
