使用 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
參考直接實例化,在 DAO 實作中使用 JdbcTemplate
,或者您可以在 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://127.0.0.1:");
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://127.0.0.1:"
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://127.0.0.1:");
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://127.0.0.1:"
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
的位置參數陳述式可透過具有 Fluent 互動模型的統一用戶端 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 查詢/更新陳述式的彈性但簡化的 Facade。批次插入和預存程序呼叫等進階功能通常需要額外的自訂:對於 JdbcClient 中未提供的任何此類功能,請考慮 Spring 的 SimpleJdbcInsert 和 SimpleJdbcCall 類別或普通的直接 JdbcTemplate 用法。 |
使用 SQLExceptionTranslator
SQLExceptionTranslator
是一個介面,要由可以在 SQLException
和 Spring 自己的 org.springframework.dao.DataAccessException
之間轉換的類別實作,後者在資料存取策略方面是不可知的。實作可以是通用的(例如,針對 JDBC 使用 SQLState 代碼)或專有的(例如,針對 Oracle 錯誤代碼),以獲得更高的精確度。此例外狀況轉換機制在不傳播 SQLException
而是 DataAccessException
的常見 JdbcTemplate
和 JdbcTransactionManager
進入點後使用。
從 6.0 開始,預設例外狀況轉換器是 SQLExceptionSubclassTranslator ,它偵測 JDBC 4 SQLException 子類別,並進行一些額外檢查,並透過 SQLStateSQLExceptionTranslator 回退到 SQLState 內省。這通常足以應付常見的資料庫存取,並且不需要供應商特定的偵測。為了向後相容性,請考慮使用如下所述的 SQLErrorCodeSQLExceptionTranslator ,並可能使用自訂錯誤代碼對應。 |
當名為 sql-error-codes.xml
的檔案存在於類別路徑的根目錄中時,SQLErrorCodeSQLExceptionTranslator
是預設使用的 SQLExceptionTranslator
的實作。此實作使用特定的供應商代碼。它比 SQLState
或 SQLException
子類別轉換更精確。錯誤代碼轉換基於 JavaBean 類型類別 SQLErrorCodes
中保存的代碼。此類別由 SQLErrorCodesFactory
建立和填入,後者(顧名思義)是用於根據名為 sql-error-codes.xml
的組態檔的內容建立 SQLErrorCodes
的 Factory。此檔案填入了供應商代碼,並基於從 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 陳述式具有列參數的佔位符。您可以將參數值作為 varargs 或物件陣列傳入。因此,您應該將原始型別顯式包裝在原始型別包裝類別中,或者您應該使用自動裝箱。
檢索自動產生的鍵
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