使用 JDBC 核心類控制基本 JDBC 處理和錯誤處理
本節介紹如何使用 JDBC 核心類來控制基本的 JDBC 處理,包括錯誤處理。它包括以下主題
使用 JdbcTemplate
JdbcTemplate 是 JDBC 核心包中的中心類。它處理資源的建立和釋放,這有助於避免常見錯誤,例如忘記關閉連線。它執行核心 JDBC 工作流的基本任務(例如語句建立和執行),而應用程式程式碼則提供 SQL 並提取結果。JdbcTemplate 類
-
執行 SQL 查詢
-
更新語句和儲存過程呼叫
-
對
ResultSet例項進行迭代並提取返回的引數值。 -
捕獲 JDBC 異常並將其轉換為
org.springframework.dao包中定義的通用且資訊更豐富的異常層次結構。(請參閱一致的異常層次結構。)
當您在程式碼中使用 JdbcTemplate 時,您只需要實現回撥介面,併為它們提供一個明確定義的契約。給定一個由 JdbcTemplate 類提供的 Connection,PreparedStatementCreator 回撥介面建立一個預處理語句,提供 SQL 和任何必要的引數。CallableStatementCreator 介面也是如此,它建立可呼叫語句。RowCallbackHandler 介面從 ResultSet 的每一行中提取值。
您可以透過使用 DataSource 引用直接例項化 JdbcTemplate,在 DAO 實現中使用它,或者您可以在 Spring IoC 容器中配置它,並將其作為 bean 引用提供給 DAO。
DataSource 應該始終在 Spring IoC 容器中配置為 bean。在第一種情況下,bean 直接提供給服務;在第二種情況下,它提供給準備好的模板。 |
此類的所有 SQL 都以 DEBUG 級別記錄,類別對應於模板例項的完全限定類名(通常是 JdbcTemplate,但如果您使用 JdbcTemplate 類的自定義子類,則可能不同)。
以下部分提供了一些 JdbcTemplate 用法的示例。這些示例並非 JdbcTemplate 公開的所有功能的窮盡列表。請參閱附帶的 Javadoc 以獲取更多資訊。
查詢 (SELECT)
以下查詢獲取關係中的行數
-
Java
-
Kotlin
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!
以下查詢使用繫結變數
-
Java
-
Kotlin
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
"select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!
以下查詢查詢 String
-
Java
-
Kotlin
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);
val lastName = this.jdbcTemplate.queryForObject<String>(
"select last_name from t_actor where id = ?",
arrayOf(1212L))!!
以下查詢查詢並填充單個領域物件
-
Java
-
Kotlin
Actor actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
(resultSet, rowNum) -> {
Actor newActor = new Actor();
newActor.setFirstName(resultSet.getString("first_name"));
newActor.setLastName(resultSet.getString("last_name"));
return newActor;
},
1212L);
val actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
arrayOf(1212L)) { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
}
以下查詢查詢並填充領域物件列表
-
Java
-
Kotlin
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
(resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
});
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
如果最後兩個程式碼片段實際上存在於同一個應用程式中,那麼刪除兩個 RowMapper lambda 表示式中存在的重複並將其提取到一個單一欄位中是有意義的,然後 DAO 方法可以根據需要引用該欄位。例如,最好將前面的程式碼片段寫成如下所示
-
Java
-
Kotlin
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
};
public List<Actor> findAllActors() {
return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}
val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
}
fun findAllActors(): List<Actor> {
return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
}
使用 JdbcTemplate 更新 (INSERT, UPDATE 和 DELETE)
您可以使用 update(..) 方法執行插入、更新和刪除操作。引數值通常以可變引數或物件陣列的形式提供。
以下示例插入一個新條目
-
Java
-
Kotlin
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling")
以下示例更新現有條目
-
Java
-
Kotlin
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L)
以下示例刪除一個條目
-
Java
-
Kotlin
this.jdbcTemplate.update(
"delete from t_actor where id = ?",
Long.valueOf(actorId));
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
其他 JdbcTemplate 操作
您可以使用 execute(..) 方法執行任何任意 SQL。因此,該方法常用於 DDL 語句。它被大量過載,包含接受回撥介面、繫結變數陣列等的變體。以下示例建立一個表
-
Java
-
Kotlin
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
以下示例呼叫儲存過程
-
Java
-
Kotlin
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
unionId.toLong())
更復雜的儲存過程支援稍後介紹。
JdbcTemplate 最佳實踐
JdbcTemplate 類的例項一旦配置,就是執行緒安全的。這很重要,因為它意味著您可以配置 JdbcTemplate 的單個例項,然後安全地將此共享引用注入到多個 DAO(或儲存庫)中。JdbcTemplate 是有狀態的,因為它維護對 DataSource 的引用,但此狀態不是會話狀態。
使用 JdbcTemplate 類(以及關聯的 NamedParameterJdbcTemplate 類)時,常見的做法是在 Spring 配置檔案中配置一個 DataSource,然後將該共享的 DataSource bean 依賴注入到您的 DAO 類中。JdbcTemplate 在 DataSource 的 setter 或建構函式中建立。這導致 DAO 類似於以下內容
-
Java
-
Kotlin
public class JdbcCorporateEventDao implements CorporateEventDao {
private final JdbcTemplate jdbcTemplate;
public JdbcCorporateEventDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
class JdbcCorporateEventDao(dataSource: DataSource): CorporateEventDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
以下示例顯示了相應的配置
-
Java
-
Kotlin
-
Xml
@Bean
JdbcCorporateEventDao corporateEventDao(DataSource dataSource) {
return new JdbcCorporateEventDao(dataSource);
}
@Bean(destroyMethod = "close")
BasicDataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://:");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
@Bean
fun corporateEventDao(dataSource: DataSource) = JdbcCorporateEventDao(dataSource)
@Bean(destroyMethod = "close")
fun dataSource() = BasicDataSource().apply {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:hsql://:"
username = "sa"
password = ""
}
<bean id="corporateEventDao" class="org.example.jdbc.JdbcCorporateEventDao">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
顯式配置的替代方法是使用元件掃描和依賴注入的註解支援。在這種情況下,您可以使用 @Repository 註解類(使其成為元件掃描的候選者)。以下示例顯示瞭如何執行此操作
@Repository
public class JdbcCorporateEventRepository implements CorporateEventRepository {
private JdbcTemplate jdbcTemplate;
// Implicitly autowire the DataSource constructor parameter
public JdbcCorporateEventRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// JDBC-backed implementations of the methods on the CorporateEventRepository follow...
}
以下示例顯示了相應的配置
-
Java
-
Kotlin
-
Xml
@Configuration
@ComponentScan("org.example.jdbc")
public class JdbcCorporateEventRepositoryConfiguration {
@Bean(destroyMethod = "close")
BasicDataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://:");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}
@Configuration
@ComponentScan("org.example.jdbc")
class JdbcCorporateEventRepositoryConfiguration {
@Bean(destroyMethod = "close")
fun dataSource() = BasicDataSource().apply {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:hsql://:"
username = "sa"
password = ""
}
}
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
<context:component-scan base-package="org.example.jdbc" />
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
如果您使用 Spring 的 JdbcDaoSupport 類,並且您的各種 JDBC 支援的 DAO 類都繼承自它,那麼您的子類將從 JdbcDaoSupport 類繼承一個 setDataSource(..) 方法。您可以選擇是否繼承此類的功能。JdbcDaoSupport 類僅作為便利提供。
無論您選擇使用(或不使用)上述哪種模板初始化方式,每次執行 SQL 時都很少需要建立 JdbcTemplate 類的新例項。一旦配置好,JdbcTemplate 例項就是執行緒安全的。如果您的應用程式訪問多個數據庫,您可能需要多個 JdbcTemplate 例項,這需要多個 DataSource,然後需要多個配置不同的 JdbcTemplate 例項。
使用 NamedParameterJdbcTemplate
NamedParameterJdbcTemplate 類增加了對使用命名引數程式設計 JDBC 語句的支援,而不是僅使用經典佔位符 ('?') 引數程式設計 JDBC 語句。NamedParameterJdbcTemplate 類包裝了一個 JdbcTemplate 並將大部分工作委託給被包裝的 JdbcTemplate。本節僅描述 NamedParameterJdbcTemplate 類與 JdbcTemplate 本身不同的地方——即使用命名引數程式設計 JDBC 語句。以下示例顯示瞭如何使用 NamedParameterJdbcTemplate
-
Java
-
Kotlin
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from t_actor where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = MapSqlParameterSource("first_name", firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
請注意在賦給 sql 變數的值中使用了命名引數表示法,以及插入到 namedParameters 變數(型別為 MapSqlParameterSource)中的相應值。
或者,您可以使用基於 Map 的樣式將命名引數及其相應值傳遞給 NamedParameterJdbcTemplate 例項。NamedParameterJdbcOperations 公開並由 NamedParameterJdbcTemplate 類實現的其餘方法遵循類似的模式,此處不再贅述。
以下示例顯示了基於 Map 的樣式的使用
-
Java
-
Kotlin
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from t_actor where first_name = :first_name";
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = mapOf("first_name" to firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
與 NamedParameterJdbcTemplate 相關(並存在於同一個 Java 包中)的一個不錯的功能是 SqlParameterSource 介面。您已經在前面的一些程式碼片段中看到了此介面的一個實現示例(MapSqlParameterSource 類)。SqlParameterSource 是 NamedParameterJdbcTemplate 的命名引數值的來源。MapSqlParameterSource 類是一個簡單的實現,它是 java.util.Map 的介面卡,其中鍵是引數名稱,值是引數值。
另一個 SqlParameterSource 實現是 BeanPropertySqlParameterSource 類。此類包裝任意 JavaBean(即,遵守JavaBean 約定的類例項),並使用包裝的 JavaBean 的屬性作為命名引數值的來源。
以下示例顯示了一個典型的 JavaBean
-
Java
-
Kotlin
public class Actor {
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public Long getId() {
return this.id;
}
// setters omitted...
}
data class Actor(val id: Long, val firstName: String, val lastName: String)
以下示例使用 NamedParameterJdbcTemplate 返回前一個示例中所示的類的成員數量
-
Java
-
Kotlin
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActors(exampleActor: Actor): Int {
// notice how the named parameters match the properties of the above 'Actor' class
val sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName"
val namedParameters = BeanPropertySqlParameterSource(exampleActor)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
請記住,NamedParameterJdbcTemplate 類包裝了一個經典的 JdbcTemplate 模板。如果您需要訪問包裝的 JdbcTemplate 例項以訪問僅存在於 JdbcTemplate 類中的功能,您可以使用 getJdbcOperations() 方法透過 JdbcOperations 介面訪問包裝的 JdbcTemplate。
另請參閱 JdbcTemplate 最佳實踐,瞭解在應用程式上下文中使用 NamedParameterJdbcTemplate 類的指南。
統一的 JDBC 查詢/更新操作:JdbcClient
從 6.1 版本開始,NamedParameterJdbcTemplate 的命名引數語句和常規 JdbcTemplate 的位置引數語句都可以透過具有流暢互動模型的統一客戶端 API 使用。
例如,使用位置引數
private JdbcClient jdbcClient = JdbcClient.create(dataSource);
public int countOfActorsByFirstName(String firstName) {
return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
.param(firstName)
.query(Integer.class).single();
}
例如,使用命名引數
private JdbcClient jdbcClient = JdbcClient.create(dataSource);
public int countOfActorsByFirstName(String firstName) {
return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
.param("firstName", firstName)
.query(Integer.class).single();
}
RowMapper 功能也可用,具有靈活的結果解析
List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
.query((rs, rowNum) -> new Actor(rs.getString("first_name"), rs.getString("last_name")))
.list();
除了自定義的 RowMapper,你還可以指定一個類來對映。例如,假設 Actor 類是一個記錄類,具有 firstName 和 lastName 屬性,一個自定義建構函式、bean 屬性或普通欄位。
List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
.query(Actor.class)
.list();
需要單個物件結果時
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
.param(1212L)
.query(Actor.class)
.single();
使用 java.util.Optional 結果
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
.param(1212L)
.query(Actor.class)
.optional();
以及更新語句
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
.param("Leonor").param("Watling")
.update();
或帶命名引數的更新語句
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
.param("firstName", "Leonor").param("lastName", "Watling")
.update();
除了單個命名引數,您還可以指定一個引數源物件——例如,一個記錄類、一個帶有 bean 屬性的類,或者一個提供 firstName 和 lastName 屬性的普通欄位持有者,例如上面的 Actor 類
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
.paramSource(new Actor("Leonor", "Watling"))
.update();
上述引數的自動 Actor 類對映以及查詢結果是透過隱式 SimplePropertySqlParameterSource 和 SimplePropertyRowMapper 策略提供的,這些策略也可以直接使用。它們可以作為 BeanPropertySqlParameterSource 和 BeanPropertyRowMapper/DataClassRowMapper 的常見替代品,也可以直接用於 JdbcTemplate 和 NamedParameterJdbcTemplate。
JdbcClient 是 JDBC 查詢/更新語句的靈活但簡化的門面。高階功能,如批次插入和儲存過程呼叫,通常需要額外的定製:對於 JdbcClient 中不可用的任何此類功能,請考慮 Spring 的 SimpleJdbcInsert 和 SimpleJdbcCall 類或直接使用純 JdbcTemplate。 |
使用 SQLExceptionTranslator
SQLExceptionTranslator 是一個介面,由能夠將 SQLException 轉換為 Spring 自己的 org.springframework.dao.DataAccessException 的類實現,後者與資料訪問策略無關。實現可以是通用的(例如,使用 SQLState 程式碼進行 JDBC)或專有的(例如,使用 Oracle 錯誤程式碼)以提高精度。這種異常轉換機制用於不傳播 SQLException 而傳播 DataAccessException 的常見 JdbcTemplate 和 JdbcTransactionManager 入口點。
從 6.0 版本開始,預設的異常轉換器是 SQLExceptionSubclassTranslator,它透過一些額外的檢查檢測 JDBC 4 SQLException 子類,並回退到透過 SQLStateSQLExceptionTranslator 進行 SQLState 內省。這通常足以滿足常見的資料庫訪問需求,並且不需要特定於供應商的檢測。為了向後相容,可以考慮使用下面描述的 SQLErrorCodeSQLExceptionTranslator,可能帶有自定義錯誤程式碼對映。 |
SQLErrorCodeSQLExceptionTranslator 是 SQLExceptionTranslator 的實現,當類路徑根目錄中存在名為 sql-error-codes.xml 的檔案時,它會被預設使用。此實現使用特定的供應商程式碼。它比 SQLState 或 SQLException 子類轉換更精確。錯誤程式碼轉換基於名為 SQLErrorCodes 的 JavaBean 型別類中儲存的程式碼。此類由 SQLErrorCodesFactory 建立和填充,SQLErrorCodesFactory(顧名思義)是一個根據名為 sql-error-codes.xml 的配置檔案內容建立 SQLErrorCodes 的工廠。此檔案填充了供應商程式碼,並基於從 DatabaseMetaData 獲取的 DatabaseProductName。將使用您實際使用的資料庫的程式碼。
SQLErrorCodeSQLExceptionTranslator 按以下順序應用匹配規則
-
子類實現的任何自定義翻譯。通常,使用提供的具體
SQLErrorCodeSQLExceptionTranslator,因此此規則不適用。它僅在您實際提供了子類實現時才適用。 -
作為
SQLErrorCodes類的customSqlExceptionTranslator屬性提供的SQLExceptionTranslator介面的任何自定義實現。 -
搜尋
CustomSQLErrorCodesTranslation類的例項列表(為SQLErrorCodes類的customTranslations屬性提供)以查詢匹配項。 -
應用錯誤程式碼匹配。
-
使用回退翻譯器。
SQLExceptionSubclassTranslator是預設的回退翻譯器。如果此翻譯不可用,則下一個回退翻譯器是SQLStateSQLExceptionTranslator。
SQLErrorCodesFactory 預設用於定義錯誤程式碼和自定義異常轉換。它們在類路徑中名為 sql-error-codes.xml 的檔案中查詢,並根據正在使用的資料庫的資料庫元資料中的資料庫名稱定位匹配的 SQLErrorCodes 例項。 |
您可以擴充套件 SQLErrorCodeSQLExceptionTranslator,如下例所示
-
Java
-
Kotlin
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {
if (sqlEx.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, sqlEx);
}
return null;
}
}
class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() {
override fun customTranslate(task: String, sql: String?, sqlEx: SQLException): DataAccessException? {
if (sqlEx.errorCode == -12345) {
return DeadlockLoserDataAccessException(task, sqlEx)
}
return null
}
}
在前面的示例中,特定的錯誤程式碼 (-12345) 被轉換,而其他錯誤則留給預設轉換器實現進行轉換。要使用此自定義轉換器,您必須透過 setExceptionTranslator 方法將其傳遞給 JdbcTemplate,並且您必須將此 JdbcTemplate 用於所有需要此轉換器的資料訪問處理。以下示例顯示瞭如何使用此自定義轉換器
-
Java
-
Kotlin
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
// create a JdbcTemplate and set data source
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);
// create a custom translator and set the DataSource for the default translation lookup
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
tr.setDataSource(dataSource);
this.jdbcTemplate.setExceptionTranslator(tr);
}
public void updateShippingCharge(long orderId, long pct) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId);
}
// create a JdbcTemplate and set data source
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
// create a custom translator and set the DataSource for the default translation lookup
exceptionTranslator = CustomSQLErrorCodesTranslator().apply {
this.dataSource = dataSource
}
}
fun updateShippingCharge(orderId: Long, pct: Long) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate!!.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId)
}
自定義轉換器被傳遞一個數據源,以便在 sql-error-codes.xml 中查詢錯誤程式碼。
執行語句
執行 SQL 語句只需要很少的程式碼。您需要一個 DataSource 和一個 JdbcTemplate,包括 JdbcTemplate 提供的便捷方法。以下示例顯示了一個最小但功能齊全的類,用於建立新表所需的全部內容
-
Java
-
Kotlin
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAStatement {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void doExecute() {
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class ExecuteAStatement(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun doExecute() {
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
}
}
執行查詢
一些查詢方法返回單個值。要從一行中檢索計數或特定值,請使用 queryForObject(..)。後者將返回的 JDBC Type 轉換為作為引數傳入的 Java 類。如果型別轉換無效,則會丟擲 InvalidDataAccessApiUsageException。以下示例包含兩個查詢方法,一個用於 int,一個用於查詢 String
-
Java
-
Kotlin
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class RunAQuery {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int getCount() {
return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
}
public String getName() {
return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class RunAQuery(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
val count: Int
get() = jdbcTemplate.queryForObject("select count(*) from mytable")!!
val name: String?
get() = jdbcTemplate.queryForObject("select name from mytable")
}
除了單結果查詢方法之外,還有幾種方法返回一個列表,其中每個查詢返回的行都有一個條目。最通用的方法是 queryForList(..),它返回一個 List,其中每個元素都是一個 Map,其中包含每個列的一個條目,使用列名作為鍵。如果您在前面的示例中新增一個方法來檢索所有行的列表,它可能如下所示
-
Java
-
Kotlin
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Map<String, Object>> getList() {
return this.jdbcTemplate.queryForList("select * from mytable");
}
private val jdbcTemplate = JdbcTemplate(dataSource)
fun getList(): List<Map<String, Any>> {
return jdbcTemplate.queryForList("select * from mytable")
}
返回的列表將類似於以下內容
[{name=Bob, id=1}, {name=Mary, id=2}]
更新資料庫
以下示例更新某個主鍵的列
-
Java
-
Kotlin
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAnUpdate {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setName(int id, String name) {
this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class ExecuteAnUpdate(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun setName(id: Int, name: String) {
jdbcTemplate.update("update mytable set name = ? where id = ?", name, id)
}
}
在前面的例子中,SQL 語句為行引數留有佔位符。您可以將引數值作為變長引數傳遞,或者作為物件陣列傳遞。因此,您應該將原始型別顯式包裝在原始包裝類中,或者使用自動裝箱。
檢索自動生成的主鍵
update() 便捷方法支援檢索資料庫生成的主鍵。此支援是 JDBC 3.0 標準的一部分。有關詳細資訊,請參閱規範的第 13.6 章。該方法將 PreparedStatementCreator 作為其第一個引數,這是指定所需插入語句的方式。另一個引數是 KeyHolder,它在更新成功返回時包含生成的主鍵。沒有標準單一方法可以建立合適的 PreparedStatement(這解釋了為什麼方法簽名是這樣的)。以下示例適用於 Oracle,但可能不適用於其他平臺
-
Java
-
Kotlin
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
ps.setString(1, name);
return ps;
}, keyHolder);
// keyHolder.getKey() now contains the generated key
val INSERT_SQL = "insert into my_test (name) values(?)"
val name = "Rob"
val keyHolder = GeneratedKeyHolder()
jdbcTemplate.update({
it.prepareStatement (INSERT_SQL, arrayOf("id")).apply { setString(1, name) }
}, keyHolder)
// keyHolder.getKey() now contains the generated key