对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

使用 JDBC 核心类来控制基本的 JDBC 处理和错误处理

使用JdbcTemplate

JdbcTemplate 是 JDBC 核心包中的中心类。它负责资源的创建和释放,帮助你避免常见错误,例如忘记关闭连接。它执行 JDBC 核心工作流程的基本任务(例如语句的创建与执行),而将提供 SQL 和提取结果的工作留给应用程序代码。JdbcTemplate 类:spring-doc.cadn.net.cn

当你在代码中使用 JdbcTemplate 时,只需实现回调接口,这些接口具有明确定义的契约。给定由 Connection 类提供的 JdbcTemplatePreparedStatementCreator 回调接口会创建一个预编译语句(prepared statement),并提供 SQL 语句及所有必要的参数。CallableStatementCreator 接口也是如此,它用于创建可调用语句(callable statements)。RowCallbackHandler 接口则从 ResultSet 的每一行中提取值。spring-doc.cadn.net.cn

您可以通过直接使用JdbcTemplateDataSource引用进行实例化,在DAO实现中使用它,或者将其配置在一个Spring IoC容器中,并将它作为bean引用提供给DAO。spring-doc.cadn.net.cn

DataSource 应始终在 Spring IoC 容器中配置为一个 Bean。在第一种情况下,该 Bean 直接提供给服务;在第二种情况下,它被提供给预定义的模板。

此类发出的所有 SQL 都会以 DEBUG 级别记录在与模板实例的完全限定类名对应的类别下(通常为 JdbcTemplate,但如果您使用的是 JdbcTemplate 的自定义子类,则可能不同)。spring-doc.cadn.net.cn

以下各节提供了一些 JdbcTemplate 的使用示例。这些示例并未涵盖 JdbcTemplate 所提供的全部功能。 有关完整功能,请参阅相应的javadocspring-doc.cadn.net.cn

查询(SELECT)

以下查询获取关系中的行数:spring-doc.cadn.net.cn

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

以下查询使用了一个绑定变量:spring-doc.cadn.net.cn

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

该查询查找一个 Stringspring-doc.cadn.net.cn

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

以下查询用于查找并填充单一领域对象:spring-doc.cadn.net.cn

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

以下查询查找并填充一个领域对象列表:spring-doc.cadn.net.cn

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 方法按需引用,这样会更加合理。 例如,最好将前面的代码片段改写如下:spring-doc.cadn.net.cn

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

正在更新(INSERT, UPDATE,和DELETE)与JdbcTemplate

您可以使用update(..)方法来执行插入、更新和删除操作。 参数值通常作为可变参数提供,或者作为对象数组的替代方式。spring-doc.cadn.net.cn

以下示例插入一个新条目:spring-doc.cadn.net.cn

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

以下示例更新现有条目:spring-doc.cadn.net.cn

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)

以下示例删除一个条目:spring-doc.cadn.net.cn

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 语句。它提供了大量重载的变体,支持传入回调接口、绑定变量数组等参数。以下示例创建了一张表:spring-doc.cadn.net.cn

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

以下示例调用了一个存储过程:spring-doc.cadn.net.cn

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

更复杂的存储过程支持将在后面介绍。spring-doc.cadn.net.cn

JdbcTemplate最佳实践

JdbcTemplate 类的实例在配置完成后是线程安全的。这一点非常重要,因为它意味着你可以配置一个 JdbcTemplate 的单例实例,然后安全地将这个共享引用注入到多个 DAO(或仓库)中。 JdbcTemplate 是有状态的,因为它持有一个对 DataSource 的引用,但这种状态并非会话状态(conversational state)。spring-doc.cadn.net.cn

使用 JdbcTemplate 类(以及相关的 NamedParameterJdbcTemplate 类)时的常见做法是, 在 Spring 配置文件中配置一个 DataSource,然后将该共享的 DataSource Bean 依赖注入到您的 DAO 类中。JdbcTemplate 是在 DataSource 的 setter 方法中创建的。这使得 DAO 类似于以下内容:spring-doc.cadn.net.cn

public class JdbcCorporateEventDao implements CorporateEventDao {

	private JdbcTemplate jdbcTemplate;

	public void setDataSource(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...
}

以下示例显示了相应的XML配置:spring-doc.cadn.net.cn

<?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>

显式配置的一种替代方式是使用组件扫描(component-scanning)和注解支持进行依赖注入。在这种情况下,您可以使用 @Repository 注解该类(使其成为组件扫描的候选对象),并使用 DataSource 注解 @Autowired 的 setter 方法。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {

	private JdbcTemplate jdbcTemplate;

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

	// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 使用 @Repository 注解该类。
2 使用 DataSource 注解标注 @Autowired 的 setter 方法。
3 使用 JdbcTemplate 创建一个新的 DataSource
@Repository (1)
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { (2)

	private val jdbcTemplate = JdbcTemplate(dataSource) (3)

	// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 使用 @Repository 注解该类。
2 DataSource 的构造函数注入。
3 使用 JdbcTemplate 创建一个新的 DataSource

以下示例显示了相应的XML配置:spring-doc.cadn.net.cn

<?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">

	<!-- Scans within the base package of the application for @Component classes to configure as beans -->
	<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 类都继承自该类,那么你的子类将从 setDataSource(..) 类继承一个 JdbcDaoSupport 方法。你可以自行选择是否继承此类。JdbcDaoSupport 类仅作为便利工具提供。spring-doc.cadn.net.cn

无论您选择使用上述哪种模板初始化方式(或不使用),通常都无需在每次执行 SQL 时都创建一个新的 JdbcTemplate 类实例。一旦配置完成,JdbcTemplate 实例就是线程安全的。如果您的应用程序需要访问多个数据库,则可能需要多个 JdbcTemplate 实例,这就要求配置多个 DataSources,进而需要多个配置不同的 JdbcTemplate 实例。spring-doc.cadn.net.cn

使用NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 类增加了对使用命名参数编写 JDBC 语句的支持,而不是仅使用传统的占位符('?')参数来编写 JDBC 语句。NamedParameterJdbcTemplate 类包装了一个 JdbcTemplate,并将大部分工作委托给被包装的 JdbcTemplate 来完成。本节仅介绍 NamedParameterJdbcTemplate 类与 JdbcTemplate 本身不同的部分——即使用命名参数编写 JDBC 语句。以下示例展示了如何使用 NamedParameterJdbcTemplatespring-doc.cadn.net.cn

// 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)中的值。spring-doc.cadn.net.cn

或者,你可以使用基于 NamedParameterJdbcTemplate 的方式,将命名参数及其对应的值传递给 Map 实例。NamedParameterJdbcOperations 所公开的其余方法由 NamedParameterJdbcTemplate 类实现,遵循类似的模式,此处不再赘述。spring-doc.cadn.net.cn

以下示例展示了基于Map的用法:spring-doc.cadn.net.cn

// 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,其中键是参数名称,值是参数值。spring-doc.cadn.net.cn

另一个 SqlParameterSource 的实现是 BeanPropertySqlParameterSource 类。该类包装一个任意的 JavaBean(即符合JavaBean 规范的类的实例),并使用被包装的 JavaBean 的属性作为命名参数值的来源。spring-doc.cadn.net.cn

以下示例展示了典型的JavaBean:spring-doc.cadn.net.cn

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来返回前面示例中所示类的成员数量:spring-doc.cadn.net.cn

// 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() 方法,经由 JdbcTemplate 接口来访问被包装的 JdbcOperationsspring-doc.cadn.net.cn

另请参阅 JdbcTemplate 最佳实践, 以获取在应用程序上下文中使用 NamedParameterJdbcTemplate 类的指南。spring-doc.cadn.net.cn

使用SQLExceptionTranslator

SQLExceptionTranslator 是一个接口,由能够将 SQLException 转换为 Spring 自身的 org.springframework.dao.DataAccessException 的类实现。后者对数据访问策略是无关的(agnostic)。实现可以是通用的(例如,使用 JDBC 的 SQLState 代码),也可以是专有的(例如,使用 Oracle 错误代码),以获得更高的精确度。这种异常转换机制被用于常见的 JdbcTemplateJdbcTransactionManager 入口点背后,这些入口点不会传播 SQLException,而是传播 DataAccessExceptionspring-doc.cadn.net.cn

从 6.0 版本开始,默认的异常翻译器是 SQLExceptionSubclassTranslator, 它通过少量额外检查来识别 JDBC 4 的 SQLException 子类,并在必要时回退到通过 SQLState 进行 SQLStateSQLExceptionTranslator 内省。这通常足以满足常见的数据库访问需求,且无需依赖特定数据库厂商的检测。 为了向后兼容,可考虑使用如下所述的 SQLErrorCodeSQLExceptionTranslator, 并可能配合自定义的错误代码映射。

SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslator 的实现,当类路径根目录下存在名为 sql-error-codes.xml 的文件时,默认使用该实现。此实现使用特定的厂商代码,比 SQLStateSQLException 的子类转换更为精确。错误代码的翻译基于一个名为 SQLErrorCodes 的 JavaBean 类型类中持有的代码。该类由一个 SQLErrorCodesFactory 创建并填充,顾名思义,它是一个工厂,用于根据名为 sql-error-codes.xml 的配置文件内容创建 SQLErrorCodes。该文件填充了厂商代码,并基于从 DatabaseMetaData 获取的 DatabaseProductName。实际使用的数据库对应的代码将被采用。spring-doc.cadn.net.cn

The SQLErrorCodeSQLExceptionTranslator 应用匹配规则的顺序如下:spring-doc.cadn.net.cn

  1. 由子类实现的任何自定义转换逻辑。通常使用所提供的具体实现 SQLErrorCodeSQLExceptionTranslator,因此此规则不适用。仅当您实际提供了子类实现时,此规则才适用。spring-doc.cadn.net.cn

  2. 任何自定义实现SQLExceptionTranslator接口的类,作为customSqlExceptionTranslator类的SQLErrorCodes属性提供。spring-doc.cadn.net.cn

  3. CustomSQLErrorCodesTranslation 类的实例列表(作为 customTranslations 属性提供给 SQLErrorCodes 类)会被搜索以找到匹配项。spring-doc.cadn.net.cn

  4. 错误代码匹配已应用。spring-doc.cadn.net.cn

  5. 使用备用翻译器。SQLExceptionSubclassTranslator 是默认的备用翻译器。如果此翻译不可用,则下一个备用翻译器是 SQLStateSQLExceptionTranslatorspring-doc.cadn.net.cn

SQLErrorCodesFactory 默认用于定义错误代码和自定义异常转换。它会从类路径下名为 sql-error-codes.xml 的文件中查找这些定义,并根据当前所用数据库的元数据中的数据库名称,定位匹配的 SQLErrorCodes 实例。

您可以扩展SQLErrorCodeSQLExceptionTranslator,如下例所示:spring-doc.cadn.net.cn

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)会被翻译,而其他错误则交由默认的翻译器实现进行翻译。 要使用此自定义翻译器,您必须通过 JdbcTemplate 方法将其传递给 setExceptionTranslator, 并且在所有需要此翻译器的数据访问处理中都必须使用该 JdbcTemplate。 以下示例展示了如何使用此自定义翻译器:spring-doc.cadn.net.cn

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 中查找错误代码。spring-doc.cadn.net.cn

运行语句

执行一条 SQL 语句所需的代码非常少。你只需要一个 DataSource 和一个 JdbcTemplate,包括 JdbcTemplate 提供的便捷方法。以下示例展示了你需要包含哪些内容,才能创建一个最小但功能完整的类来新建一张表:spring-doc.cadn.net.cn

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,另一个用于查询 Stringspring-doc.cadn.net.cn

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,该 3 包含对应行中每一列的条目,并以列名作为键。如果你在前面的示例中添加一个方法来获取所有行的列表,该方法可能如下所示:spring-doc.cadn.net.cn

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

返回的列表将类似于以下内容:spring-doc.cadn.net.cn

[{name=Bob, id=1}, {name=Mary, id=2}]

更新数据库

以下示例更新某个主键对应的列:spring-doc.cadn.net.cn

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAnUpdate {

	private JdbcTemplate jdbcTemplate;

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

	public void setName(int id, String name) {
		this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
	}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate

class ExecuteAnUpdate(dataSource: DataSource) {

	private val jdbcTemplate = JdbcTemplate(dataSource)

	fun setName(id: Int, name: String) {
		jdbcTemplate.update("update mytable set name = ? where id = ?", name, id)
	}
}

在前面的示例中, SQL 语句包含用于行参数的占位符。您可以将参数值 以可变参数(varargs)的形式传入,或者 以对象数组的形式传入。因此,您应当显式地将基本类型 包装在其对应的包装类中,或者使用自动装箱。spring-doc.cadn.net.cn

自动生成键的检索

一个便捷的 update() 方法支持获取由数据库生成的主键。此功能属于 JDBC 3.0 标准的一部分,具体细节请参见规范第 13.6 章。该方法以一个 PreparedStatementCreator 作为第一个参数,通过这种方式指定所需的插入语句;另一个参数是 KeyHolder,在更新操作成功返回后,其中将包含生成的主键。由于没有一种标准的通用方式来创建合适的 PreparedStatement(这也解释了该方法签名如此设计的原因),以下示例适用于 Oracle,但在其他平台上可能无法正常工作:spring-doc.cadn.net.cn

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