使用 JDBC 核心類別控制基本 JDBC 處理和錯誤處理

使用 JdbcTemplate

JdbcTemplate 是 JDBC 核心套件中的核心類別。它處理資源的建立和釋放,這有助於您避免常見錯誤,例如忘記關閉連線。它執行核心 JDBC 工作流程的基本任務(例如陳述式建立和執行),讓應用程式程式碼提供 SQL 並擷取結果。JdbcTemplate 類別

  • 執行 SQL 查詢

  • 更新陳述式和預存程序呼叫

  • 執行 ResultSet 實例的迭代,並擷取傳回的參數值。

  • 捕獲 JDBC 例外狀況,並將其轉換為 org.springframework.dao 套件中定義的通用、更具資訊性的例外狀況階層。(請參閱 一致的例外狀況階層。)

當您為程式碼使用 JdbcTemplate 時,您只需要實作回呼介面,為它們提供明確定義的契約。給定 JdbcTemplate 類別提供的 ConnectionPreparedStatementCreator 回呼介面會建立預備陳述式,提供 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 更新 (INSERTUPDATEDELETE)

您可以使用 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 類別中。JdbcTemplateDataSource 的 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 類別)。SqlParameterSourceNamedParameterJdbcTemplate 的具名參數值的來源。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 具有 firstNamelastName 屬性作為記錄類別、自訂建構子、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 屬性的類別或提供 firstNamelastName 屬性的純欄位持有者,例如上述的 Actor 類別

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
		.paramSource(new Actor("Leonor", "Watling")
		.update();

上述參數以及查詢結果的自動 Actor 類別對應是透過隱含的 SimplePropertySqlParameterSourceSimplePropertyRowMapper 策略提供的,這些策略也可直接使用。它們可以作為 BeanPropertySqlParameterSourceBeanPropertyRowMapper/DataClassRowMapper 的常見替代方案,也適用於 JdbcTemplateNamedParameterJdbcTemplate 本身。

JdbcClient 是 JDBC 查詢/更新陳述式的彈性但簡化的 Facade。批次插入和預存程序呼叫等進階功能通常需要額外的自訂:對於 JdbcClient 中未提供的任何此類功能,請考慮 Spring 的 SimpleJdbcInsertSimpleJdbcCall 類別或普通的直接 JdbcTemplate 用法。

使用 SQLExceptionTranslator

SQLExceptionTranslator 是一個介面,要由可以在 SQLException 和 Spring 自己的 org.springframework.dao.DataAccessException 之間轉換的類別實作,後者在資料存取策略方面是不可知的。實作可以是通用的(例如,針對 JDBC 使用 SQLState 代碼)或專有的(例如,針對 Oracle 錯誤代碼),以獲得更高的精確度。此例外狀況轉換機制在不傳播 SQLException 而是 DataAccessException 的常見 JdbcTemplateJdbcTransactionManager 進入點後使用。

從 6.0 開始,預設例外狀況轉換器是 SQLExceptionSubclassTranslator,它偵測 JDBC 4 SQLException 子類別,並進行一些額外檢查,並透過 SQLStateSQLExceptionTranslator 回退到 SQLState 內省。這通常足以應付常見的資料庫存取,並且不需要供應商特定的偵測。為了向後相容性,請考慮使用如下所述的 SQLErrorCodeSQLExceptionTranslator,並可能使用自訂錯誤代碼對應。

當名為 sql-error-codes.xml 的檔案存在於類別路徑的根目錄中時,SQLErrorCodeSQLExceptionTranslator 是預設使用的 SQLExceptionTranslator 的實作。此實作使用特定的供應商代碼。它比 SQLStateSQLException 子類別轉換更精確。錯誤代碼轉換基於 JavaBean 類型類別 SQLErrorCodes 中保存的代碼。此類別由 SQLErrorCodesFactory 建立和填入,後者(顧名思義)是用於根據名為 sql-error-codes.xml 的組態檔的內容建立 SQLErrorCodes 的 Factory。此檔案填入了供應商代碼,並基於從 DatabaseMetaData 取得的 DatabaseProductName。使用您正在使用的實際資料庫的代碼。

SQLErrorCodeSQLExceptionTranslator 依照下列順序套用比對規則

  1. 子類別實作的任何自訂轉譯。通常,會使用提供的具體 SQLErrorCodeSQLExceptionTranslator,因此此規則不適用。它僅在您實際提供了子類別實作時適用。

  2. SQLErrorCodes 類別的 customSqlExceptionTranslator 屬性提供的 SQLExceptionTranslator 介面的任何自訂實作。

  3. 搜尋 CustomSQLErrorCodesTranslation 類別的實例清單(為 SQLErrorCodes 類別的 customTranslations 屬性提供)以尋找符合項。

  4. 套用錯誤代碼比對。

  5. 使用後備轉譯器。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 陳述式只需要極少的程式碼。您需要 DataSourceJdbcTemplate,包括 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