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

使用 JdbcTemplate

JdbcTemplate 是 JDBC 核心包中的中心類。它負責資源的建立和釋放,這有助於您避免常見的錯誤,例如忘記關閉連線。它執行核心 JDBC 工作流的基本任務(例如語句建立和執行),將 SQL 提供和結果提取留給應用程式程式碼。JdbcTemplate

  • 執行 SQL 查詢

  • 更新語句和儲存過程呼叫

  • ResultSet 例項進行迭代並提取返回的引數值。

  • 捕獲 JDBC 異常並將其轉換為 org.springframework.dao 包中定義的通用、更具資訊性的異常層次結構。(參閱 一致的異常層次結構。)

當您在程式碼中使用 JdbcTemplate 時,只需實現回撥介面,它們具有明確定義的契約。給定由 JdbcTemplate 類提供的 ConnectionPreparedStatementCreator 回撥介面建立一個預處理語句,提供 SQL 和任何必要的引數。對於 CallableStatementCreator 介面也一樣,它建立可呼叫語句。RowCallbackHandler 介面從 ResultSet 的每一行中提取值。

您可以在 DAO 實現中使用 JdbcTemplate,既可以透過直接例項化並引用 DataSource,也可以在 Spring IoC 容器中配置它,然後將它作為 bean 引用提供給 DAO。

DataSource 應該始終配置為 Spring IoC 容器中的一個 bean。第一種情況是將 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, UPDATEDELETE)

您可以使用 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(或 Repository)中。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://:");
	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 類)。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 的位置引數語句都可以透過具有流暢互動模型的統一客戶端 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 類作為 record class、具有自定義建構函式、bean 屬性或普通欄位,並且擁有 firstNamelastName 屬性

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();

除了單獨的命名引數,您還可以指定一個引數源物件——例如,record class、具有 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 查詢/更新語句的一個靈活但簡化的門面。批處理插入和儲存過程呼叫等高階功能通常需要額外的自定義:對於 JdbcClient 中不可用的任何此類功能,請考慮使用 Spring 的 SimpleJdbcInsertSimpleJdbcCall 類或直接使用普通的 JdbcTemplate

使用 SQLExceptionTranslator

SQLExceptionTranslator 是一個介面,由能夠翻譯 SQLException 和 Spring 自有的 org.springframework.dao.DataAccessException 之間的類實現,後者與資料訪問策略無關。實現可以是通用的(例如,對 JDBC 使用 SQLState 程式碼),也可以是專有的(例如,使用 Oracle 錯誤程式碼)以獲得更高的精度。這種異常轉換機制用於常見的 JdbcTemplateJdbcTransactionManager 入口點後面,這些入口點不傳播 SQLException,而是傳播 DataAccessException

從 6.0 版本開始,預設的異常轉換器是 SQLExceptionSubclassTranslator,它透過一些額外的檢查來檢測 JDBC 4 的 SQLException 子類,並回退到透過 SQLStateSQLExceptionTranslator 進行 SQLState 自省。這通常足以滿足常見的資料庫訪問需求,並且不需要特定於供應商的檢測。為了向後相容,可以考慮使用如下所述的 SQLErrorCodeSQLExceptionTranslator,可能需要自定義錯誤程式碼對映。

SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslator 的預設實現,當類路徑根目錄下存在名為 sql-error-codes.xml 的檔案時使用此實現。此實現使用特定的供應商程式碼。它比 SQLStateSQLException 子類轉換更精確。錯誤程式碼轉換基於名為 SQLErrorCodes 的 JavaBean 型別類中包含的程式碼。此類由 SQLErrorCodesFactory 建立和填充,後者(顧名思義)是一個工廠,用於根據名為 sql-error-codes.xml 的配置檔案內容建立 SQLErrorCodes。此檔案填充了供應商程式碼,並基於從 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 語句所需的程式碼非常少。您需要一個 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