主頁 > 後端開發 > Spring JDBC

Spring JDBC

2021-10-31 06:29:43 後端開發

用過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 學習筆記(五)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more