Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling

本部分涵盖了如何使用 JDBC 核心类控制基本的 JDBC 处理,包括错误处理。它涵盖以下主题:

This section covers how to use the JDBC core classes to control basic JDBC processing, including error handling. It includes the following topics:

Using JdbcTemplate

JdbcTemplate 是 JDBC 核心包中的中心类。它处理资源的创建和释放,这有助于你避免常见错误,例如忘记关闭连接。它执行核心 JDBC 工作流的基本任务(例如语句创建和执行),让应用程序代码提供 SQL 并提取结果。JdbcTemplate 类:

JdbcTemplate is the central class in the JDBC core package. It handles the creation and release of resources, which helps you avoid common errors, such as forgetting to close the connection. It performs the basic tasks of the core JDBC workflow (such as statement creation and execution), leaving application code to provide SQL and extract results. The JdbcTemplate class:

  • Runs SQL queries

  • Updates statements and stored procedure calls

  • Performs iteration over ResultSet instances and extraction of returned parameter values.

  • Catches JDBC exceptions and translates them to the generic, more informative, exception hierarchy defined in the org.springframework.dao package. (See Consistent Exception Hierarchy.)

当你为代码使用 JdbcTemplate 时,你只需要实现回调接口,为它们提供清晰定义的契约。给定由 JdbcTemplate 类提供的 ConnectionPreparedStatementCreator 回调接口创建了已准备好的语句,提供了 SQL 和任何必需的参数。CallableStatementCreator 接口也是如此,它创建可调用语句。RowCallbackHandler 接口从 ResultSet 的每一行中提取值。

When you use the JdbcTemplate for your code, you need only to implement callback interfaces, giving them a clearly defined contract. Given a Connection provided by the JdbcTemplate class, the PreparedStatementCreator callback interface creates a prepared statement, providing SQL and any necessary parameters. The same is true for the CallableStatementCreator interface, which creates callable statements. The RowCallbackHandler interface extracts values from each row of a ResultSet.

你可以通过使用具有 DataSource 引用进行直接实例化,在 DAO 实现中使用 JdbcTemplate,或者你可以在 Spring IoC 容器中配置它,并将其作为 bean 引用提供给 DAO。

You can use JdbcTemplate within a DAO implementation through direct instantiation with a DataSource reference, or you can configure it in a Spring IoC container and give it to DAOs as a bean reference.

DataSource 应始终配置为 Spring IoC 容器中的 bean。在第一种情况下,bean 直接提供给服务;在第二种情况下,bean 提供给准备好的模板。

The DataSource should always be configured as a bean in the Spring IoC container. In the first case the bean is given to the service directly; in the second case it is given to the prepared template.

此类发出的所有 SQL 都记录在模板实例的完全限定类名的相应类别下的 DEBUG 级别(通常为 JdbcTemplate,但如果你使用 JdbcTemplate 类的自定义子类,则可能不同)。

All SQL issued by this class is logged at the DEBUG level under the category corresponding to the fully qualified class name of the template instance (typically JdbcTemplate, but it may be different if you use a custom subclass of the JdbcTemplate class).

以下部分提供了一些 JdbcTemplate 用法的示例。这些示例并不是 JdbcTemplate 公开的所有功能的详尽列表。请参阅随附的 javadoc

The following sections provide some examples of JdbcTemplate usage. These examples are not an exhaustive list of all of the functionality exposed by the JdbcTemplate. See the attendant javadoc for that.

Querying (SELECT)

以下查询获取关系中的行号:

The following query gets the number of rows in a relation:

  • Java

  • Kotlin

int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!

以下查询使用绑定变量:

The following query uses a bind variable:

  • 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

The following query looks for a 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))!!

以下查询查找并填充单个域对象:

The following query finds and populates a single domain object:

  • 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"))
	}

以下查询查找并填充域对象列表:

The following query finds and populates a list of domain objects:

  • 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 方法引用。例如,最好按如下所示编写前面的代码片段:

If the last two snippets of code actually existed in the same application, it would make sense to remove the duplication present in the two RowMapper lambda expressions and extract them out into a single field that could then be referenced by DAO methods as needed. For example, it may be better to write the preceding code snippet as follows:

  • 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)
}

Updating (INSERT, UPDATE, and DELETE) with JdbcTemplate

你可以使用 update(..) 方法执行插入、更新和删除操作。通常将参数值作为可变参数或作为对象数组提供。

You can use the update(..) method to perform insert, update, and delete operations. Parameter values are usually provided as variable arguments or, alternatively, as an object array.

以下示例插入新条目:

The following example inserts a new entry:

  • 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")

以下示例更新现有条目:

The following example updates an existing entry:

  • 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)

以下示例删除条目:

The following example deletes an entry:

  • Java

  • Kotlin

this.jdbcTemplate.update(
		"delete from t_actor where id = ?",
		Long.valueOf(actorId));
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())

Other JdbcTemplate Operations

你可以使用 execute(..) 方法运行任何任意的 SQL。因此,该方法通常用于 DDL 语句。它大量重载了带有回调接口、绑定变量数组等内容的变量。以下示例创建一个表:

You can use the execute(..) method to run any arbitrary SQL. Consequently, the method is often used for DDL statements. It is heavily overloaded with variants that take callback interfaces, binding variable arrays, and so on. The following example creates a table:

  • Java

  • Kotlin

this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")

下面的示例调用存储过程:

The following example invokes a stored procedure:

  • Java

  • Kotlin

this.jdbcTemplate.update(
		"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
		Long.valueOf(unionId));
jdbcTemplate.update(
		"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
		unionId.toLong())

更复杂存储过程支持是 covered later

More sophisticated stored procedure support is covered later.

JdbcTemplate Best Practices

配置后,JdbcTemplate 类的实例将是线程安全的。这一点很重要,因为这意味着你可以配置 JdbcTemplate 的单个实例,然后将此共享引用安全地注入到多个 DAO(或存储库)中。JdbcTemplate 是有状态的,因为它维护对 DataSource 的引用,但是此状态不是会话状态。

Instances of the JdbcTemplate class are thread-safe, once configured. This is important because it means that you can configure a single instance of a JdbcTemplate and then safely inject this shared reference into multiple DAOs (or repositories). The JdbcTemplate is stateful, in that it maintains a reference to a DataSource, but this state is not conversational state.

在使用`JdbcTemplate`类(以及关联的xref:data-access/jdbc/core.adoc#jdbc-NamedParameterJdbcTemplate[NamedParameterJdbcTemplate`类)时,一个常见做法是在你的Spring配置文件中配置一个`DataSource,然后将该共享`DataSource`bean依赖注入到你的DAO类中。`JdbcTemplate`是在`DataSource`的setter方法中或是在构造函数中创建的。这会导致类似于下面所示内容的DAO:

A common practice when using the JdbcTemplate class (and the associated NamedParameterJdbcTemplate class) is to configure a DataSource in your Spring configuration file and then dependency-inject that shared DataSource bean into your DAO classes. The JdbcTemplate is created in the setter for the DataSource or in the constructor. This leads to DAOs that resemble the following:

  • 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...
}

以下示例显示了相应的配置:

The following example shows the corresponding configuration:

  • 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 注释此类(使其成为组件扫描的候选对象)。以下示例显示了如何执行此操作:

An alternative to explicit configuration is to use component-scanning and annotation support for dependency injection. In this case, you can annotate the class with @Repository (which makes it a candidate for component-scanning). The following example shows how to do so:

@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...
}

以下示例显示了相应的配置:

The following example shows the corresponding configuration:

  • 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 类只是为了方便。

If you use Spring’s JdbcDaoSupport class and your various JDBC-backed DAO classes extend from it, your sub-class inherits a setDataSource(..) method from the JdbcDaoSupport class. You can choose whether to inherit from this class. The JdbcDaoSupport class is provided as a convenience only.

无论选择使用上述哪种模板初始化样式(或不使用),在每次需要运行 SQL 时都创建一个新的 JdbcTemplate 类的实例都是没有必要的。配置后,JdbcTemplate 实例是线程安全的。如果应用程序访问多个数据库,则可能需要多个 JdbcTemplate 实例,这需要多个 DataSources 以及随后需要多个不同配置的 JdbcTemplate 实例。

Regardless of which of the above template initialization styles you choose to use (or not), it is seldom necessary to create a new instance of a JdbcTemplate class each time you want to run SQL. Once configured, a JdbcTemplate instance is thread-safe. If your application accesses multiple databases, you may want multiple JdbcTemplate instances, which requires multiple DataSources and, subsequently, multiple differently configured JdbcTemplate instances.

Using NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 类通过使用命名参数(而不是仅使用经典占位符 ( ’?'`) 参数)对 JDBC 语句进行编程来添加支持。NamedParameterJdbcTemplate 类包装 JdbcTemplate,并委托给包装的 JdbcTemplate 来执行大量工作。本节仅描述 NamedParameterJdbcTemplate 类与 JdbcTemplate 自身不同的方面——即通过使用命名参数对 JDBC 语句进行编程。以下示例显示如何使用 NamedParameterJdbcTemplate

The NamedParameterJdbcTemplate class adds support for programming JDBC statements by using named parameters, as opposed to programming JDBC statements using only classic placeholder ( ’?'`) arguments. The NamedParameterJdbcTemplate class wraps a JdbcTemplate and delegates to the wrapped JdbcTemplate to do much of its work. This section describes only those areas of the NamedParameterJdbcTemplate class that differ from the JdbcTemplate itself — namely, programming JDBC statements by using named parameters. The following example shows how to use 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 类型)的相应值中使用了命名参数符号。

Notice the use of the named parameter notation in the value assigned to the sql variable and the corresponding value that is plugged into the namedParameters variable (of type MapSqlParameterSource).

或者,你可以通过使用基于 Map 的样式将命名参数及其相应值传递到 NamedParameterJdbcTemplate 实例。由 NamedParameterJdbcOperations 公开并由 NamedParameterJdbcTemplate 类实现的其余方法遵循类似的模式,这里不进行介绍。

Alternatively, you can pass along named parameters and their corresponding values to a NamedParameterJdbcTemplate instance by using the Map-based style. The remaining methods exposed by the NamedParameterJdbcOperations and implemented by the NamedParameterJdbcTemplate class follow a similar pattern and are not covered here.

以下示例显示基于 Map 的样式的使用:

The following example shows the use of the Map-based style:

  • 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 的一个适配器,其中键为参数名称,值为参数值。

One nice feature related to the NamedParameterJdbcTemplate (and existing in the same Java package) is the SqlParameterSource interface. You have already seen an example of an implementation of this interface in one of the previous code snippets (the MapSqlParameterSource class). An SqlParameterSource is a source of named parameter values to a NamedParameterJdbcTemplate. The MapSqlParameterSource class is a simple implementation that is an adapter around a java.util.Map, where the keys are the parameter names and the values are the parameter values.

另一个 SqlParameterSource 实现是 BeanPropertySqlParameterSource 类。此类包装一个任意的 JavaBean(即,一个遵守 the JavaBean conventions 的类的实例),并使用包装的 JavaBean 的属性作为命名参数值来源。

Another SqlParameterSource implementation is the BeanPropertySqlParameterSource class. This class wraps an arbitrary JavaBean (that is, an instance of a class that adheres to the JavaBean conventions) and uses the properties of the wrapped JavaBean as the source of named parameter values.

以下示例展示了一个典型的 JavaBean:

The following example shows a typical 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 返回前一个示例中所示类的成员数:

The following example uses a NamedParameterJdbcTemplate to return the count of the members of the class shown in the preceding example:

  • 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

Remember that the NamedParameterJdbcTemplate class wraps a classic JdbcTemplate template. If you need access to the wrapped JdbcTemplate instance to access functionality that is present only in the JdbcTemplate class, you can use the getJdbcOperations() method to access the wrapped JdbcTemplate through the JdbcOperations interface.

有关在应用程序上下文中使用 NamedParameterJdbcTemplate 类的准则,请参见 JdbcTemplate Best Practices

See also JdbcTemplate Best Practices for guidelines on using the NamedParameterJdbcTemplate class in the context of an application.

Unified JDBC Query/Update Operations: JdbcClient

从 6.1 开始,NamedParameterJdbcTemplate 的命名参数语句和常规 JdbcTemplate 的位置参数语句可以通过具有流畅交互模型的统一客户端 API 获得。

As of 6.1, the named parameter statements of NamedParameterJdbcTemplate and the positional parameter statements of a regular JdbcTemplate are available through a unified client API with a fluent interaction model.

例如,使用位置参数:

For example, with positional parameters:

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

例如,使用命名参数:

For example, with named parameters:

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 功能也可用,具有灵活的结果解析:

RowMapper capabilities are available as well, with flexible result resolution:

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 属性或普通字段:

Instead of a custom RowMapper, you may also specify a class to map to. For example, assuming that Actor has firstName and lastName properties as a record class, a custom constructor, bean properties, or plain fields:

List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
		.query(Actor.class)
		.list();

使用一个必需的单一对象结果:

With a required single object result:

Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
		.param(1212L)
		.query(Actor.class)
		.single();

使用 java.util.Optional 结果:

With a java.util.Optional result:

Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
		.param(1212L)
		.query(Actor.class)
		.optional();

以及用于更新语句:

And for an update statement:

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
		.param("Leonor").param("Watling")
		.update();

或带命名参数的更新语句:

Or an update statement with named parameters:

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

除了单独的命名参数,还可以指定参数源对象 - 例如记录类、具有 bean 属性的类或提供 firstNamelastName 属性的普通字段持有者,例如上面的 Actor 类:

Instead of individual named parameters, you may also specify a parameter source object – for example, a record class, a class with bean properties, or a plain field holder which provides firstName and lastName properties, such as the Actor class from above:

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 本身一起使用。

The automatic Actor class mapping for parameters as well as the query results above is provided through implicit SimplePropertySqlParameterSource and SimplePropertyRowMapper strategies which are also available for direct use. They can serve as a common replacement for BeanPropertySqlParameterSource and BeanPropertyRowMapper/DataClassRowMapper, also with JdbcTemplate and NamedParameterJdbcTemplate themselves.

JdbcClient 是 JDBC 查询/更新语句的一个灵活但简化的外观。高级功能(如批量插入和存储过程调用)通常需要额外的自定义:考虑 Spring 的 SimpleJdbcInsertSimpleJdbcCall 类或普通的 JdbcTemplate 直接使用来获得 JdbcClient 中的任何此类可用功能。

JdbcClient is a flexible but simplified facade for JDBC query/update statements. Advanced capabilities such as batch inserts and stored procedure calls typically require extra customization: consider Spring’s SimpleJdbcInsert and SimpleJdbcCall classes or plain direct JdbcTemplate usage for any such capabilities not available in JdbcClient.

Using SQLExceptionTranslator

SQLExceptionTranslator 是一个由类实现的接口,该类可以在 SQLException 和 Spring 自己的 org.springframework.dao.DataAccessException 之间进行转换,它与数据访问策略无关。实现可以是通用的(例如,对 JDBC 使用 SQLState 代码)或专有的(例如,对 Oracle 错误代码使用)以获得更高的精度。这种异常转换机制用于公共 JdbcTemplateJdbcTransactionManager 入口点,这些入口点不会传播 SQLException,而是传播 DataAccessException

SQLExceptionTranslator is an interface to be implemented by classes that can translate between SQLException`s and Spring’s own `org.springframework.dao.DataAccessException, which is agnostic in regard to data access strategy. Implementations can be generic (for example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error codes) for greater precision. This exception translation mechanism is used behind the common JdbcTemplate and JdbcTransactionManager entry points which do not propagate SQLException but rather DataAccessException.

从 6.0 开始,默认异常转换器是 SQLExceptionSubclassTranslator,它通过一些额外的检查检测 JDBC 4 SQLException 子类,并通过 SQLStateSQLExceptionTranslator 回退到 SQLState 自省。这通常足以用于常见的数据库访问,并且不需要特定供应商的检测。为了向后兼容,请考虑使用 SQLErrorCodeSQLExceptionTranslator,如下所述,可能使用自定义错误代码映射。

As of 6.0, the default exception translator is SQLExceptionSubclassTranslator, detecting JDBC 4 SQLException subclasses with a few extra checks, and with a fallback to SQLState introspection through SQLStateSQLExceptionTranslator. This is usually sufficient for common database access and does not require vendor-specific detection. For backwards compatibility, consider using SQLErrorCodeSQLExceptionTranslator as described below, potentially with custom error code mappings.

SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslator 的实现,当类路径的根中存在名为 sql-error-codes.xml 的文件时,默认使用该实现。此实现使用特定的供应商代码。它比 SQLStateSQLException 子类翻译更精确。错误代码转换基于保存在名为 SQLErrorCodes 的 JavaBean 类型类中的代码。此类是由 SQLErrorCodesFactory 创建和填充的,而 SQLErrorCodesFactory(顾名思义)是基于名为 sql-error-codes.xml 的配置文件的内容创建 SQLErrorCodes 的工厂。此文件使用供应商代码填充,并基于从 DatabaseMetaData 获取的 DatabaseProductName。将使用你正在使用的实际数据库的代码。

SQLErrorCodeSQLExceptionTranslator is the implementation of SQLExceptionTranslator that is used by default when a file named sql-error-codes.xml is present in the root of the classpath. This implementation uses specific vendor codes. It is more precise than SQLState or SQLException subclass translation. The error code translations are based on codes held in a JavaBean type class called SQLErrorCodes. This class is created and populated by an SQLErrorCodesFactory, which (as the name suggests) is a factory for creating SQLErrorCodes based on the contents of a configuration file named sql-error-codes.xml. This file is populated with vendor codes and based on the DatabaseProductName taken from DatabaseMetaData. The codes for the actual database you are using are used.

SQLErrorCodeSQLExceptionTranslator 按以下顺序应用匹配规则:

The SQLErrorCodeSQLExceptionTranslator applies matching rules in the following sequence:

  1. Any custom translation implemented by a subclass. Normally, the provided concrete SQLErrorCodeSQLExceptionTranslator is used, so this rule does not apply. It applies only if you have actually provided a subclass implementation.

  2. Any custom implementation of the SQLExceptionTranslator interface that is provided as the customSqlExceptionTranslator property of the SQLErrorCodes class.

  3. The list of instances of the CustomSQLErrorCodesTranslation class (provided for the customTranslations property of the SQLErrorCodes class) are searched for a match.

  4. Error code matching is applied.

  5. Use the fallback translator. SQLExceptionSubclassTranslator is the default fallback translator. If this translation is not available, the next fallback translator is the SQLStateSQLExceptionTranslator.

SQLErrorCodesFactory 默认用于定义错误代码和自定义异常转换。它们在类路径中的 sql-error-codes.xml 文件中查找,并且匹配的 SQLErrorCodes 实例基于正在使用的数据库的数据库元数据中的数据库名称进行查找。

The SQLErrorCodesFactory is used by default to define error codes and custom exception translations. They are looked up in a file named sql-error-codes.xml from the classpath, and the matching SQLErrorCodes instance is located based on the database name from the database metadata of the database in use.

你可以扩展 SQLErrorCodeSQLExceptionTranslator,如下例所示:

You can extend SQLErrorCodeSQLExceptionTranslator, as the following example shows:

  • 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。以下示例展示了如何使用此自定义转换器:

In the preceding example, the specific error code (-12345) is translated while other errors are left to be translated by the default translator implementation. To use this custom translator, you must pass it to the JdbcTemplate through the method setExceptionTranslator, and you must use this JdbcTemplate for all of the data access processing where this translator is needed. The following example shows how you can use this custom translator:

  • 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`中查看错误代码,需要将自定义转换器传递给一个数据源。

The custom translator is passed a data source in order to look up the error codes in sql-error-codes.xml.

Running Statements

运行 SQL 语句只需要少量的代码。您需要一个`DataSource`和一个`JdbcTemplate`,包括`JdbcTemplate`随附的那些便利方式。以下示例显示了创建新表所需的最小但功能齐全的类的内容:

Running an SQL statement requires very little code. You need a DataSource and a JdbcTemplate, including the convenience methods that are provided with the JdbcTemplate. The following example shows what you need to include for a minimal but fully functional class that creates a new table:

  • 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))")
	}
}

Running Queries

某些查询方法会返回单个值。要从一行中检索计数或特定值,请使用`queryForObject(..)。后者会将返回的 JDBC `Type`转换为作为参数传入的 Java 类。如果类型转换无效,则会抛出`InvalidDataAccessApiUsageException。以下示例包含两个查询方法,一个用于`int`,另一个用于查询`String`:

Some query methods return a single value. To retrieve a count or a specific value from one row, use queryForObject(..). The latter converts the returned JDBC Type to the Java class that is passed in as an argument. If the type conversion is invalid, an InvalidDataAccessApiUsageException is thrown. The following example contains two query methods, one for an int and one that queries for a 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`,该 Map 包含每一列的一个条目。如果您在前一个示例中添加一个方法以检索所有行的一个列表,则它可能如下所示:

In addition to the single result query methods, several methods return a list with an entry for each row that the query returned. The most generic method is queryForList(..), which returns a List where each element is a Map containing one entry for each column, using the column name as the key. If you add a method to the preceding example to retrieve a list of all the rows, it might be as follows:

  • 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")
}

返回的列表将类似于以下内容:

The returned list would resemble the following:

Updating the Database

以下示例更新特定主键列:

The following example updates a column for a certain primary key:

  • 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 语句具有行参数的占位符。您可以将参数值传递为可变参数,也可以作为对象数组传递。因此,您应该显式地将基本类型封装在基本类型包装器类中,或者您应该使用自动装箱。

In the preceding example, an SQL statement has placeholders for row parameters. You can pass the parameter values in as varargs or, alternatively, as an array of objects. Thus, you should explicitly wrap primitives in the primitive wrapper classes, or you should use auto-boxing.

Retrieving Auto-generated Keys

update()`便捷方法支持检索数据库生成的的主键。此支持是 JDBC 3.0 标准的一部分。有关详细信息,请参阅规范的第 13.6 章。该方法将其`PreparedStatementCreator`作为其第一个参数,这就是指定所需的插入语句的方式。另一个参数是`KeyHolder,其中包含在成功从更新返回时生成的主键。没有创建适当的`PreparedStatement`的标准单一方法(这解释了为什么方法签名是这样的)。以下示例适用于 Oracle,但可能不适用于其他平台:

An update() convenience method supports the retrieval of primary keys generated by the database. This support is part of the JDBC 3.0 standard. See Chapter 13.6 of the specification for details. The method takes a PreparedStatementCreator as its first argument, and this is the way the required insert statement is specified. The other argument is a KeyHolder, which contains the generated key on successful return from the update. There is no standard single way to create an appropriate PreparedStatement (which explains why the method signature is the way it is). The following example works on Oracle but may not work on other platforms:

  • 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