Spring 6 JdbcClient API 指南
1. 概述
在本教程中,我們將了解JdbcClient接口,它是 Spring Framework 6.1 的最新成員。它為JdbcTemplate和NamedParameterJdbcTemplate提供了一個具有統一外觀的流暢介面。這意味著現在它支援連結類型的操作。我們現在可以以流暢的 API 風格定義查詢、設定參數並執行資料庫操作。
此功能簡化了 JDBC 操作,使它們更具可讀性且更易於理解。但是,我們必須使用舊的JdbcTemplate和NamedParameterJdbcTemplate類別來進行 JDBC 批次作業和預存程序呼叫。
在本文中,我們將使用 H2 資料庫來展示JdbcClient的功能。
2. 必要的資料庫設置
讓我們先來看看student表,我們在探索JdbcClient時將引用該表:
CREATE TABLE student (
student_id INT AUTO_INCREMENT PRIMARY KEY,
student_name VARCHAR(255) NOT NULL,
age INT,
grade INT NOT NULL,
gender VARCHAR(10) NOT NULL,
state VARCHAR(100) NOT NULL
);
-- Student 1
INSERT INTO student (student_name, age, grade, gender, state) VALUES ('John Smith', 18, 3, 'Male', 'California');
-- Student 2
INSERT INTO student (student_name, age, grade, gender, state) VALUES ('Emily Johnson', 17, 2, 'Female', 'New York');
--More insert statements...
上面的 SQL 腳本會建立一個student表並在其中插入記錄。
3.創建JdbcClient
Spring Boot 框架會自動發現application.properties中的資料庫連線屬性,並在應用程式啟動期間建立JdbcClient bean。此後, JdbcClient bean 可以在任何類別中自動組裝。
以下是一個範例,我們在StudentDao類別中註入JdbcClient bean:
@Repository
class StudentDao {
@Autowired
private JdbcClient jdbcClient;
}
我們將在本文中使用StudentDao來定義理解JdbcClient介面的方法。
但是,介面中也有一些靜態方法,例如create(DataSource dataSource) 、 create(JdbcOperations jdbcTemplate)和create(NamedParameterJdbcOperations jdbcTemplate) ,可以建立JdbcClient實例。
4. 使用JdbcClient執行資料庫查詢
如前所述, JdbcClient是JdbcTemplate和NamedParameterJdbcTemplate的統一外觀。因此,我們將看看它如何支持它們。
4.1.隱式支援位置參數
在本節中,我們將討論將位置 SQL 語句參數與佔位符?綁定的支援。 。基本上,我們將了解它如何支援JdbcTemplate的功能。
讓我們來看看StudentDao類別中的以下方法:
List<Student> getStudentsOfGradeStateAndGenderWithPositionalParams(int grade, String state, String gender) {
String sql = "select student_id, student_name, age, grade, gender, state from student"
+ " where grade = ? and state = ? and gender = ?";
return jdbcClient.sql(sql)
.param(grade)
.param(state)
.param(gender)
.query(new StudentRowMapper()).list();
}
在上面的方法中,參數grade 、 state和gender按照它們分配給方法param()的順序隱式註冊。最後,當呼叫query()方法時,將執行該語句,並像JdbcTemplate中一樣在RowMapper的幫助下檢索結果。
query()方法也支援ResultSetExtractor和RowCallbackHandler參數。我們將在接下來的部分中看到相關範例。
有趣的是,在呼叫方法list()之前,不會檢索到任何結果。也支援其他終端操作,例如optional() 、 set() 、 [single() ,](https://docs.spring.io/spring-framework/docs/6.1.0-SNAPSHOT/javadoc-api/org/springframework/jdbc/core/simple/JdbcClient.MappedQuerySpec.html#single())和stream()
現在,我們來看看它是如何運作的:
@Test
void givenJdbcClient_whenQueryWithPositionalParams_thenSuccess() {
List<Student> students = studentDao.getStudentsOfGradeStateAndGenderWithPositionalParams(1, "New York", "Male");
assertEquals(6, students.size());
}
讓我們看看如何在 Varargs 的幫助下完成相同的事情:
Student getStudentsOfGradeStateAndGenderWithParamsInVarargs(int grade, String state, String gender) {
String sql = "select student_id, student_name, age, grade, gender, state from student"
+ " where grade = ? and state = ? and gender = ? limit 1";
return jdbcClient.sql(sql)
.params(grade, state, gender)
.query(new StudentRowMapper()).single();
}
正如我們在上面看到的,我們將param()方法替換為採用 Varargs 參數的params()方法。此外,我們使用single()方法僅檢索一筆記錄。
讓我們看看它是如何運作的:
@Test
void givenJdbcClient_whenQueryWithParamsInVarargs_thenSuccess() {
Student student = studentDao.getStudentsOfGradeStateAndGenderWithParamsInVarargs(1, "New York", "Male");
assertNotNull(student);
}
此外,方法params()也有一個重載版本,它採用參數List 。讓我們來看一個例子:
Optional<Student> getStudentsOfGradeStateAndGenderWithParamsInList(List params) {
String sql = "select student_id, student_name, age, grade, gender, state from student"
+ " where grade = ? and state = ? and gender = ? limit 1";
return jdbcClient.sql(sql)
.params(params)
.query(new StudentRowMapper()).optional();
}
除了params(List<?> values)之外,我們還看到了optional()方法,它傳回Optional<Student>物件。這是上面的方法的實際應用:
@Test
void givenJdbcClient_whenQueryWithParamsInList_thenSuccess() {
List params = List.of(1, "New York", "Male");
Optional<Student> optional = studentDao.getStudentsOfGradeStateAndGenderWithParamsInList(params);
if(optional.isPresent()) {
assertNotNull(optional.get());
} else {
assertThrows(NoSuchElementException.class, () -> optional.get());
}
}
4.2.透過索引明確支援位置參數
如果我們需要設定SQL語句參數的位置呢?為此,我們將使用方法**param(int jdbcIndex, Object value)** :
List<Student> getStudentsOfGradeStateAndGenderWithParamIndex(int grade, String state, String gender) {
String sql = "select student_id, student_name, age, grade, gender, state from student"
+ " where grade = ? and state = ? and gender = ?";
return jdbcClient.sql(sql)
.param(1, grade)
.param(2, state)
.param(3, gender)
.query(new StudentResultExtractor());
}
在該方法中,明確指定了參數的位置索引。此外,我們也使用了方法query(ResultSetExtractor rse) 。
讓我們看看實際效果:
@Test
void givenJdbcClient_whenQueryWithParamsIndex_thenSuccess() {
List<Student> students = studentDao.getStudentsOfGradeStateAndGenderWithParamIndex(
1, "New York", "Male");
assertEquals(6, students.size());
}
4.3.支援帶有名稱-值對的命名參數
JdbcClient也支援使用佔位符: ×綁定命名 SQL 語句參數,這是NamedParameterJdbcTemplate的功能。
param()方法也可以將參數當作鍵值對:
int getCountOfStudentsOfGradeStateAndGenderWithNamedParam(int grade, String state, String gender) {
String sql = "select student_id, student_name, age, grade, gender, state from student"
+ " where grade = :grade and state = :state and gender = :gender";
RowCountCallbackHandler countCallbackHandler = new RowCountCallbackHandler();
jdbcClient.sql(sql)
.param("grade", grade)
.param("state", state)
.param("gender", gender)
.query(countCallbackHandler);
return countCallbackHandler.getRowCount();
}
在上面的方法中,我們使用了命名參數。此外,我們也使用了query(RowCallbackHandler rch) 。讓我們看看它的實際效果:
@Test
void givenJdbcClient_whenQueryWithNamedParam_thenSuccess() {
Integer count = studentDao.getCountOfStudentsOfGradeStateAndGenderWithNamedParam(1, "New York", "Male");
assertEquals(6, count);
}
4.4.支援帶有Map的命名參數
有趣的是,我們也可以在params(Map<String,?> paramMap)方法中在映射中傳遞參數名稱-值對:
List<Student> getStudentsOfGradeStateAndGenderWithParamMap(Map<String, ?> paramMap) {
String sql = "select student_id, student_name, age, grade, gender, state from student"
+ " where grade = :grade and state = :state and gender = :gender";
return jdbcClient.sql(sql)
.params(paramMap)
.query(new StudentRowMapper()).list();
}
繼續,讓我們看看它是如何運作的:
@Test
void givenJdbcClient_whenQueryWithParamMap_thenSuccess() {
Map<String, ?> paramMap = Map.of(
"grade", 1,
"gender", "Male",
"state", "New York"
);
List<Student> students = studentDao.getStudentsOfGradeStateAndGenderWithParamMap(paramMap);
assertEquals(6, students.size());
}
5. 使用JdbcClient執行資料庫操作
就像查詢一樣, JdbcClient也支援資料庫操作,例如建立、更新和刪除記錄。與前面的部分類似,我們也可以透過param()和params()方法的各種重載版本來綁定參數。因此,我們不會重複它們。
但是,為了執行 SQL 語句而不是呼叫query()方法,我們將呼叫update()方法。
以下是向student表中插入記錄的範例:
Integer insertWithSetParamWithNamedParamAndSqlType(Student student) {
String sql = "INSERT INTO student (student_name, age, grade, gender, state)"
+ "VALUES (:name, :age, :grade, :gender, :state)";
Integer noOfrowsAffected = this.jdbcClient.sql(sql)
.param("name", student.getStudentName(), Types.VARCHAR)
.param("age", student.getAge(), Types.INTEGER)
.param("grade", student.getGrade(), Types.INTEGER)
.param("gender", student.getStudentGender(), Types.VARCHAR)
.param("state", student.getState(), Types.VARCHAR)
.update();
return noOfrowsAffected;
}
上面的方法使用param(String name, Object value, int sqlType)來綁定參數。它有一個附加的sqlType參數來指定參數的資料類型。此外, update()方法也會傳回受影響的行數。
讓我們看看該方法的實際效果:
@Test
void givenJdbcClient_whenInsertWithNamedParamAndSqlType_thenSuccess() {
Student student = getSampleStudent("Johny Dep", 8, 4, "Male", "New York");
assertEquals(1, studentDao.insertWithSetParamWithNamedParamAndSqlType(student));
}
在上面的方法中, getSampleStudent()傳回一個student物件。然後將student物件傳遞給方法insertWithSetParamWithNamedParamAndSqlType()以在student表中建立新記錄。
與JdbcTemplate類似, JdbcClient具有update(KeyHolder generatedKeyHolder)方法來擷取執行insert語句時所建立的自動產生的鍵。
六,結論
在本文中,我們了解了 Spring Framework 6.1 中引入的新介面JdbcClient 。我們看到了這個介面如何執行先前由JdbcTemplate和NamedParameterJdbcTemplate執行的所有操作。此外,由於流暢的 API 風格,程式碼也變得更容易閱讀和理解。
與往常一樣,本文中使用的程式碼可以在 GitHub 上找到。