此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cadn.net.cn

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

JdbcTemplate

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

当您使用JdbcTemplate对于您的代码,您只需实现 callback 接口,为它们提供明确定义的 Contract。给定一个ConnectionJdbcTemplate类中,使用PreparedStatementCreatorcallback 接口会创建一个准备好的 语句,提供 SQL 和任何必要的参数。对于CallableStatementCreator接口,用于创建可调用语句。这RowCallbackHandlerinterface 从ResultSet.spring-doc.cadn.net.cn

您可以使用JdbcTemplate在 DAO 实现中通过直接实例化 替换为DataSource引用,或者你可以在 Spring IoC 容器中配置它并将其提供给 DAO 作为 bean 引用。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);

以下查询使用 bind 变量:spring-doc.cadn.net.cn

int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
		"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");

以下查询查找String:spring-doc.cadn.net.cn

String lastName = this.jdbcTemplate.queryForObject(
		"select last_name from t_actor where id = ?",
		String.class, 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);

以下查询查找并填充域对象列表: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;
		});

如果最后两个代码片段实际存在于同一个应用程序中,那么它将使 sense 删除两者中存在的重复项RowMapperlambda 表达式和 将它们提取到一个字段中,然后 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);
}

更新 (INSERT,UPDATEDELETE) 替换为JdbcTemplate

您可以使用update(..)方法执行 INSERT、UPDATE 和 DELETE作。 参数值通常作为变量参数提供,或者作为对象数组提供。spring-doc.cadn.net.cn

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

this.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);

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

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

其他JdbcTemplate操作

您可以使用execute(..)方法来运行任意 SQL。因此, method 通常用于 DDL 语句。它严重过载了采用 回调接口、绑定变量数组等。以下示例创建一个 桌子:spring-doc.cadn.net.cn

this.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));

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

JdbcTemplate最佳实践

的实例JdbcTemplate类是线程安全的,一旦配置。这是 重要,因为这意味着您可以配置JdbcTemplate然后安全地将此共享引用注入多个 DAO(或存储库)。 这JdbcTemplate是有状态的,因为它维护了对DataSource但 此状态不是对话状态。spring-doc.cadn.net.cn

使用JdbcTemplate类(以及关联的NamedParameterJdbcTemplateclass) 为 配置DataSource在 Spring 配置文件中,然后 dependency-inject 那个分享了DataSourcebean 添加到您的 DAO 类中。这JdbcTemplate在 的 setterDataSource或在构造函数中。这导致了类似于以下内容的 DAO:spring-doc.cadn.net.cn

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

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

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

显式配置的替代方法是使用组件扫描和注释 支持依赖项注入。在这种情况下,您可以使用@Repository(这使它成为组件扫描的候选者)。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

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

}

如果您使用 Spring 的JdbcDaoSupport类和你的各种 JDBC 支持的 DAO 类 extend 的setDataSource(..)方法从JdbcDaoSupport类。您可以选择是否从此类继承。这JdbcDaoSupportclass 仅为方便起见而提供。spring-doc.cadn.net.cn

无论您选择使用上述哪种模板初始化样式(或 not),则很少需要创建JdbcTemplate每个类 运行时间。配置后,JdbcTemplate实例是线程安全的。 如果您的应用程序访问多个数据库,则可能需要多个JdbcTemplate实例,这需要多个DataSources随后,多个不同的 配置JdbcTemplate实例。spring-doc.cadn.net.cn

NamedParameterJdbcTemplate

NamedParameterJdbcTemplateclass 添加了对 JDBC 语句编程的支持 通过使用命名参数,而不是仅使用经典 placeholder ('?') 参数。这NamedParameterJdbcTemplate类将JdbcTemplate和委托给包装的JdbcTemplate来完成它的大部分工作。这 部分仅介绍了NamedParameterJdbcTemplate类 从JdbcTemplate本身 — 即使用 named 对 JDBC 语句进行编程 参数。以下示例演示如何使用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 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);
}

请注意,在分配给sql变量和插入到namedParametersvariable(类型MapSqlParameterSource).spring-doc.cadn.net.cn

或者,您可以将命名参数及其相应的值传递给NamedParameterJdbcTemplate实例Map-基于样式。剩余的 由NamedParameterJdbcOperations并由NamedParameterJdbcTemplate类遵循类似的模式,此处不作介绍。spring-doc.cadn.net.cn

以下示例显示了Map-based 样式: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);
}

一个与NamedParameterJdbcTemplate(并且存在于相同的 Java 软件包)是SqlParameterSource接口。您已经看到了一个 此接口在前面的代码片段之一中的实现(MapSqlParameterSource类)。一SqlParameterSource是命名参数的源 值设置为NamedParameterJdbcTemplate.这MapSqlParameterSourceclass 是一个 简单的实现,它是围绕java.util.Map,其中键 是参数名称,值是参数值。spring-doc.cadn.net.cn

另一个SqlParameterSourceimplementation 是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...
}

以下示例使用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);
}

请记住,NamedParameterJdbcTemplate类将经典JdbcTemplate模板。如果您需要访问已包装的JdbcTemplate要访问的实例 功能,该功能仅存在于JdbcTemplate类中,您可以使用getJdbcOperations()方法来访问包装的JdbcTemplate通过JdbcOperations接口。spring-doc.cadn.net.cn

另请参阅JdbcTemplate最佳实践有关使用NamedParameterJdbcTemplate类。spring-doc.cadn.net.cn

统一的 JDBC 查询/更新作:JdbcClient

从 6.1 开始,NamedParameterJdbcTemplate和位置 parameter 语句的常规JdbcTemplate可通过统一的客户端 API 使用 使用 Fluent 交互模型。spring-doc.cadn.net.cn

例如,使用位置参数:spring-doc.cadn.net.cn

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

例如,使用命名参数:spring-doc.cadn.net.cn

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功能也可用,具有灵活的结果分辨率:spring-doc.cadn.net.cn

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性能 作为 Record 类、自定义构造函数、Bean Properties 或 Plain Fields:spring-doc.cadn.net.cn

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

具有必需的单个对象结果:spring-doc.cadn.net.cn

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

使用java.util.Optional结果:spring-doc.cadn.net.cn

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

对于 update 语句:spring-doc.cadn.net.cn

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

或者带有命名参数的 update 语句:spring-doc.cadn.net.cn

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

除了单独的命名参数,您还可以指定一个参数源对象 – 例如,一个 Record 类、一个具有 Bean 属性的类或一个 Plain Field Holder,它 提供firstNamelastName属性,例如Actorclass 的spring-doc.cadn.net.cn

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

自动Actor参数的类映射以及上面的查询结果是 通过 Implicit 提供SimplePropertySqlParameterSourceSimplePropertyRowMapper策略,这些策略也可以直接使用。它们可以作为常见的替代品 为BeanPropertySqlParameterSourceBeanPropertyRowMapper/DataClassRowMapper, 还与JdbcTemplateNamedParameterJdbcTemplate他们自己。spring-doc.cadn.net.cn

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

SQLExceptionTranslator

SQLExceptionTranslator是由可以翻译的类实现的接口 之间SQLExceptions 和 Spring 自己的org.springframework.dao.DataAccessException, 这在数据访问策略方面是不可知的。实现可以是通用的(对于 示例,使用 JDBC 的 SQLState 代码)或专有代码(例如,使用 Oracle 错误 代码)以获得更高的精度。此异常转换机制在 常见JdbcTemplateJdbcTransactionManager不 传播SQLException而是DataAccessException.spring-doc.cadn.net.cn

从 6.0 开始,默认的异常转换器是SQLExceptionSubclassTranslator, 检测 JDBC 4SQLException子类,并带有 fallback 自SQLState内省SQLStateSQLExceptionTranslator.这通常是 足以进行常见的数据库访问,并且不需要特定于供应商的检测。 为了向后兼容,请考虑使用SQLErrorCodeSQLExceptionTranslator如 下面描述,可能带有自定义错误代码映射。

SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslator,当名为sql-error-codes.xml存在于根 的 classpath 中。此实施使用特定的供应商代码。它比SQLStateSQLException子类 translation 的错误代码翻译基于 在名为SQLErrorCodes.这个类被创建并 由SQLErrorCodesFactory,它(顾名思义)是 创建SQLErrorCodes基于名为sql-error-codes.xml.此文件填充了供应商代码,并基于DatabaseProductName摘自DatabaseMetaData.实际代码 数据库。spring-doc.cadn.net.cn

SQLErrorCodeSQLExceptionTranslator按以下顺序应用匹配规则:spring-doc.cadn.net.cn

  1. 由子类实现的任何自定义翻译。通常,提供的混凝土SQLErrorCodeSQLExceptionTranslator,因此此规则不适用。它 仅当您实际提供了 subclass 实现时适用。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是默认回退 在线翻译。如果此翻译不可用,则下一个回退翻译器为 这SQLStateSQLExceptionTranslator.spring-doc.cadn.net.cn

SQLErrorCodesFactory默认用于定义错误代码,自定义 异常翻译。它们在名为sql-error-codes.xml从 classpath 和匹配的SQLErrorCodes实例 是基于数据库定位的 name 来自正在使用的数据库的数据库元数据。

您可以扩展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;
	}
}

在前面的示例中,特定错误代码 (-12345) 被翻译,而 其他错误留给 default translator 实现进行转换。 要使用此自定义转换器,必须将其传递给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);
}

向自定义转换器传递数据源,以便在sql-error-codes.xml.spring-doc.cadn.net.cn

Running 语句

运行 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))");
	}
}

运行查询

某些查询方法返回单个值。从 一行,请使用queryForObject(..).后者将返回的 JDBCType到 作为参数传入的 Java 类。如果类型转换无效,则InvalidDataAccessApiUsageException被抛出。以下示例包含两个 query 方法,一个用于int以及查询String:spring-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);
	}
}

除了单个结果查询方法之外,还有几个方法返回一个带有 条目。最通用的方法是queryForList(..), ,它返回一个List其中每个元素都是一个Map每列包含一个条目, 使用列名作为键。如果向前面的示例添加方法以检索 列表中,它可能如下所示: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");
}

返回的列表将类似于以下内容: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);
	}
}

在前面的示例中, SQL 语句具有行参数的占位符。您可以传递参数值 in 作为 varargs 或对象数组。因此,您应该显式包装 primitives 在 primitive wrapper 类中,或者您应该使用 auto-boxing。spring-doc.cadn.net.cn

检索自动生成的密钥

update()便捷方法支持检索 数据库。此支持是 JDBC 3.0 标准的一部分。请参阅 规格。该方法采用PreparedStatementCreator作为其第一个 参数,这是指定所需 INSERT 语句的方式。另一个 argument 是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

APP信息