使用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公开的所有功能的详尽列表。有关详细信息,请参阅相关的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)
}

Updating (INSERT, UPDATE, and DELETE) with JdbcTemplate

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

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

More sophisticated stored procedure support is covered later.

JdbcTemplate 最佳实践

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

在使用JdbcTemplate类(以及相关的NamedParameterJdbcTemplate类)时的常见做法是在Spring配置文件中配置一个DataSource,然后将这个共享的DataSource bean依赖注入到DAO类中。JdbcTemplate是在DataSource的setter中创建的。这导致DAO类如下所示:

  • Java

  • Kotlin

public class JdbcCorporateEventDao implements CorporateEventDao {

	private JdbcTemplate jdbcTemplate;

	public void setDataSource(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	// 遵循CorporateEventDao上方法的基于JDBC的实现...
}
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {

	private val jdbcTemplate = JdbcTemplate(dataSource)

	// 遵循CorporateEventDao上方法的基于JDBC的实现...
}

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<bean id="dataSource" class="org.apache.commons.dbcp.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"/>

</beans>

另一种显式配置的替代方法是使用组件扫描和注解支持进行依赖注入。在这种情况下,您可以使用@Repository对类进行注解(使其成为组件扫描的候选对象),并使用@AutowiredDataSource的setter方法进行注解。以下示例显示了如何操作:

  • Java

  • Kotlin

@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {

	private JdbcTemplate jdbcTemplate;

	@Autowired (2)
	public void setDataSource(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource); (3)
	}

	// 遵循CorporateEventDao上方法的基于JDBC的实现...
}
1 使用@Repository对类进行注解。
2 使用@AutowiredDataSource的setter方法进行注解。
3 使用DataSource创建一个新的JdbcTemplate
@Repository (1)
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { (2)

	private val jdbcTemplate = JdbcTemplate(dataSource) (3)

	// 遵循CorporateEventDao上方法的基于JDBC的实现...
}
1 使用@Repository对类进行注解。
2 使用构造函数注入DataSource
3 使用DataSource创建一个新的JdbcTemplate

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 在应用程序的基本包中扫描@Component类以配置为bean -->
	<context:component-scan base-package="org.springframework.docs.test" />

	<bean id="dataSource" class="org.apache.commons.dbcp.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"/>

</beans>

如果您使用Spring的JdbcDaoSupport类,并且您的各种基于JDBC的DAO类从中继承,那么您的子类将从JdbcDaoSupport类继承一个setDataSource(..)方法。您可以选择是否从这个类继承。JdbcDaoSupport类仅作为一种便利提供。

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

使用 NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 类支持通过使用命名参数来编程JDBC语句,而不是仅使用经典的占位符('?')参数来编程JDBC语句。 NamedParameterJdbcTemplate 类包装了一个 JdbcTemplate 并委托给包装的 JdbcTemplate 来完成大部分工作。本节仅描述了 NamedParameterJdbcTemplate 类与 JdbcTemplate 本身不同的部分,即通过使用命名参数来编程JDBC语句。以下示例展示了如何使用 NamedParameterJdbcTemplate

  • Java

  • Kotlin

// 一些基于JDBC的DAO类...
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

// 一些基于JDBC的DAO类...
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);
}
// 一些基于JDBC的DAO类...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

fun countOfActorsByFirstName(firstName: String): Int {
	val sql = "select count(*) from t_actor where first_name = :first_name"
	val namedParameters = mapOf("first_name" to firstName)
	return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}

NamedParameterJdbcTemplate 相关的一个不错的特性(并且存在于同一个Java包中)是 SqlParameterSource 接口。您已经在之前的代码片段中看到了该接口的一个实现示例(MapSqlParameterSource 类)。 SqlParameterSource 是向 NamedParameterJdbcTemplate 提供命名参数值的源。 MapSqlParameterSource 类是一个简单的实现,它是围绕一个 java.util.Map 的适配器,其中键是参数名称,值是参数值。

另一个 SqlParameterSource 的实现是 BeanPropertySqlParameterSource 类。该类包装任意JavaBean(即符合 JavaBean约定 的类的实例),并使用包装的JavaBean的属性作为命名参数值的来源。

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

  • Java

  • Kotlin

public class Actor {

	private Long id;
	private String firstName;
	private String lastName;

	public String getFirstName() {
		return this.firstName;
	}

	public String getLastName() {
		return this.lastName;
	}

	public Long getId() {
		return this.id;
	}

	// setters omitted...
}
data class Actor(val id: Long, val firstName: String, val lastName: String)

以下示例使用 NamedParameterJdbcTemplate 返回前面示例中所示类的成员数量:

  • Java

  • Kotlin

// 一些基于JDBC的DAO类...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
	this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActors(Actor exampleActor) {
	// 请注意,命名参数与上述 'Actor' 类的属性匹配
	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);
}
// 一些基于JDBC的DAO类...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

fun countOfActors(exampleActor: Actor): Int {
	// 请注意,命名参数与上述 'Actor' 类的属性匹配
	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作为记录类具有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查询/更新语句的灵活但简化的外观。高级功能,如批量插入和存储过程调用通常需要额外的定制:考虑Spring的SimpleJdbcInsertSimpleJdbcCall类或直接使用JdbcTemplate处理任何JdbcClient中不可用的功能。

使用 SQLExceptionTranslator

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

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

SQLErrorCodeSQLExceptionTranslator 是默认情况下在类路径根目录下存在名为 sql-error-codes.xml 的文件时使用的 SQLExceptionTranslator 实现。该实现使用特定的供应商代码。它比 SQLStateSQLException 子类转换更精确。错误代码转换基于一个名为 SQLErrorCodes 的 JavaBean 类中保存的代码。这个类由一个 SQLErrorCodesFactory 创建和填充,该工厂(顾名思义)是一个根据名为 sql-error-codes.xml 的配置文件的内容创建 SQLErrorCodes 的工厂。该文件填充了供应商代码,并基于从 DatabaseMetaData 获取的 DatabaseProductName。使用的是实际数据库的代码。

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

  1. 任何子类实现的自定义翻译。通常使用提供的具体 SQLErrorCodeSQLExceptionTranslator,因此此规则不适用。只有在实际提供了子类实现时才适用。

  2. 提供作为 SQLErrorCodes 类的 customSqlExceptionTranslator 属性的 SQLExceptionTranslator 接口的自定义实现。

  3. 搜索 SQLErrorCodes 类的 customTranslations 属性提供的 CustomSQLErrorCodesTranslation 类的实例列表以进行匹配。

  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) {
	// 创建一个 JdbcTemplate 并设置数据源
	this.jdbcTemplate = new JdbcTemplate();
	this.jdbcTemplate.setDataSource(dataSource);

	// 创建一个自定义翻译器并设置数据源以进行默认翻译查找
	CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
	tr.setDataSource(dataSource);
	this.jdbcTemplate.setExceptionTranslator(tr);
}

public void updateShippingCharge(long orderId, long pct) {
	// 使用准备好的 JdbcTemplate 进行此更新
	this.jdbcTemplate.update("update orders" +
		" set shipping_charge = shipping_charge * ? / 100" +
		" where id = ?", pct, orderId);
}
// 创建一个 JdbcTemplate 并设置数据源
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
	// 创建一个自定义翻译器并设置数据源以进行默认翻译查找
	exceptionTranslator = CustomSQLErrorCodesTranslator().apply {
		this.dataSource = dataSource
	}
}

fun updateShippingCharge(orderId: Long, pct: Long) {
	// 使用准备好的 JdbcTemplate 进行此更新
	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