使用 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://localhost:");
	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://localhost:"
	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://localhost:");
		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://localhost:"
		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