|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
使用JDBC核心类控制基本JDBC处理与错误处理
本节介绍如何使用JDBC核心类来控制基本的JDBC处理,包括错误处理。它包含以下主题:
使用 JdbcTemplate
JdbcTemplate 是 JDBC 核心包中的核心类。它负责资源的创建和释放,这有助于您避免常见的错误,例如忘记关闭连接。它执行核心 JDBC 工作流的基本任务(如语句创建和执行),让应用程序代码提供 SQL 并提取结果。JdbcTemplate 类:
-
运行 SQL 查询
-
更新语句和存储过程调用
-
对
ResultSet个实例进行迭代并提取返回的参数值。 -
捕获JDBC异常,并将其转换为在
org.springframework.dao包中定义的通用且更具信息量的异常层次结构。(参见一致的异常层次结构。)
当您在代码中使用 JdbcTemplate 时,只需实现回调接口,并为其提供明确的契约。给定由 Connection 类提供的 JdbcTemplate,PreparedStatementCreator 回调接口会创建一个预编译语句,提供 SQL 和任何必要的参数。对于 CallableStatementCreator 接口也是如此,它会创建可调用语句。 RowCallbackHandler 接口从每个 ResultSet 的行中提取值。
您可以通过直接实例化一个 JdbcTemplate 引用,在 DAO 实现中使用 JdbcTemplate,或者将其配置在 Spring IoC 容器中,并作为 bean 引用提供给 DAO。
DataSource 应始终在 Spring IoC 容器中配置为一个 Bean。在第一种情况下,该 Bean 直接提供给服务;在第二种情况下,它提供给预定义的模板。 |
此类发出的所有SQL在对应于模板实例的完全限定类名的类别下以DEBUG级别记录。
通常为JdbcTemplate,但如果您使用的是JdbcTemplate类的自定义子类,则可能不同。
以下部分提供了一些 JdbcTemplate 用法的示例。这些示例并不是 JdbcTemplate 所有功能的完整列表。
有关详细信息,请参阅相应的 javadoc。
查询(SELECT)
以下查询获取关系中的行数:
-
Java
-
Kotlin
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!
以下查询使用了绑定变量:
-
Java
-
Kotlin
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
"select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!
以下查询用于查找 String:
-
Java
-
Kotlin
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);
val lastName = this.jdbcTemplate.queryForObject<String>(
"select last_name from t_actor where id = ?",
arrayOf(1212L))!!
以下查询会查找并填充一个领域对象:
-
Java
-
Kotlin
Actor actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
(resultSet, rowNum) -> {
Actor newActor = new Actor();
newActor.setFirstName(resultSet.getString("first_name"));
newActor.setLastName(resultSet.getString("last_name"));
return newActor;
},
1212L);
val actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
arrayOf(1212L)) { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
}
以下查询会查找并填充一个领域对象列表:
-
Java
-
Kotlin
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
(resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
});
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
如果前面两个代码片段确实存在于同一个应用程序中,那么删除两个 RowMapper lambda 表达式中的重复内容,并将它们提取到一个单独的字段中,然后根据需要由 DAO 方法引用,这将是合理的。
例如,可能更好的写法是将前面的代码片段改写如下:
-
Java
-
Kotlin
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
};
public List<Actor> findAllActors() {
return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}
val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
}
fun findAllActors(): List<Actor> {
return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
}
更新(INSERT、UPDATE 和 DELETE)与 JdbcTemplate
您可以使用 update(..) 方法执行插入、更新和删除操作。
参数值通常作为可变参数提供,或者作为对象数组提供。
以下示例插入一条新条目:
-
Java
-
Kotlin
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling")
以下示例将更新现有条目:
-
Java
-
Kotlin
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L)
以下示例删除一条记录:
-
Java
-
Kotlin
this.jdbcTemplate.update(
"delete from t_actor where id = ?",
Long.valueOf(actorId));
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
其他 JdbcTemplate 操作
您可以使用 execute(..) 方法来运行任何任意的 SQL。因此,该方法通常用于 DDL 语句。它有大量重载版本,可以接受回调接口、绑定变量数组等。以下示例用于创建表:
-
Java
-
Kotlin
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
以下示例调用一个存储过程:
-
Java
-
Kotlin
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
unionId.toLong())
更复杂的存储过程支持将在后面 介绍。
JdbcTemplate 最佳实践
JdbcTemplate 类的实例在配置后是线程安全的。这一点很重要,因为这意味着您可以配置一个 JdbcTemplate 的实例,然后安全地将此共享引用注入到多个 DAO(或仓库)中。
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);
}
// 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 配置:
<?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>
显式配置的替代方法是使用组件扫描和注解支持的依赖注入。在这种情况下,您可以将类标注为<code>0</code>> (这使其成为组件扫描的候选),并将<code>1</code>设置方法标注为<code>2</code>。下面的示例展示了如何操作:
-
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)
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
| 1 | 使用 @Repository 注解该类。 |
| 2 | 使用 @Autowired 注解 DataSource 的设置方法。 |
| 3 | 使用 DataSource 创建一个新的 JdbcTemplate。 |
@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 | 使用 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">
<!-- 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 类仅作为便利提供。
无论您选择以上哪种模板初始化样式(或不使用),通常不需要每次运行SQL时都创建一个新的 JdbcTemplate 类的实例。
配置完成后,JdbcTemplate 实例是线程安全的。
如果您的应用程序访问多个数据库,可能需要多个 JdbcTemplate 实例,这需要多个 DataSources,并且随后需要多个配置不同的 JdbcTemplate 实例。
使用 NamedParameterJdbcTemplate
NamedParameterJdbcTemplate 类通过使用命名参数来支持编程 JDBC 语句,而不是仅使用经典的占位符('?')参数来编程 JDBC 语句。 NamedParameterJdbcTemplate 类包装了一个 JdbcTemplate,并委托包装的 JdbcTemplate 来完成大部分工作。 本节仅描述 NamedParameterJdbcTemplate 类与 JdbcTemplate 本身不同的方面——即通过使用命名参数来编程 JDBC 语句。 下面的示例展示了如何使用 NamedParameterJdbcTemplate:
-
Java
-
Kotlin
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from t_actor where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = MapSqlParameterSource("first_name", firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
注意在分配给 sql 变量的值中使用了命名参数符号,以及对应插入到 namedParameters 变量(类型为 MapSqlParameterSource)中的值。
或者,您可以通过使用基于Map的样式,将命名参数及其对应的值传递给NamedParameterJdbcTemplate实例。NamedParameterJdbcOperations所暴露的其余方法以及由NamedParameterJdbcTemplate类实现的方法遵循类似的模式,并在此不作详述。
以下示例显示了 Map 为基础的样式用法:
-
Java
-
Kotlin
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from t_actor where first_name = :first_name";
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = mapOf("first_name" to firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
与NamedParameterJdbcTemplate(存在于同一Java包中)相关的一个不错的特点是SqlParameterSource接口。您已经在前面的代码片段中的一个示例中看到了该接口的实现(MapSqlParameterSource类)。一个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
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActors(exampleActor: Actor): Int {
// notice how the named parameters match the properties of the above 'Actor' class
val sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName"
val namedParameters = BeanPropertySqlParameterSource(exampleActor)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
请注意,NamedParameterJdbcTemplate 类包装了一个经典的 JdbcTemplate 模板。如果您需要访问被包装的 JdbcTemplate 实例以使用仅在 JdbcTemplate 类中存在的功能,可以使用 getJdbcOperations() 方法通过 JdbcOperations 接口访问被包装的 JdbcTemplate。
另请参阅 JdbcTemplate 最佳实践,了解在应用程序上下文中使用 NamedParameterJdbcTemplate 类的指南。
使用 SQLExceptionTranslator
SQLExceptionTranslator 是一个接口,由可以在 SQLException 和 Spring 自己的 org.springframework.dao.DataAccessException 之间进行转换的类实现,
后者在数据访问策略方面是无差别的。实现可以是通用的(例如,使用 JDBC 的 SQLState 代码)或专有的(例如,使用 Oracle 错误代码),
以获得更高的精度。此异常转换机制用于常见的 JdbcTemplate 和 JdbcTransactionManager 入口点背后,
这些入口点不传播 SQLException,而是传播 DataAccessException。
自6.0版本起,默认的异常转换器为SQLExceptionSubclassTranslator,它通过额外检查检测JDBC 4的SQLException子类,并回退到通过SQLStateSQLExceptionTranslator进行的SQLState内省机制。这通常能满足常见数据库访问需求,无需特定厂商的检测。为保持向后兼容性,建议使用下文所述的SQLErrorCodeSQLExceptionTranslator,可配合自定义错误码映射使用。 |
SQLErrorCodeSQLExceptionTranslator 是 SQLExceptionTranslator 的默认实现,
当类路径根目录中存在名为 sql-error-codes.xml 的文件时,将使用此实现。该实现使用特定的提供商代码。
它比 SQLState 或 SQLException 子类转换更精确。错误代码的转换基于一个名为 SQLErrorCodes 的 JavaBean 类型类中定义的代码。
该类由一个 SQLErrorCodesFactory 创建并填充,顾名思义,它是一个根据名为 sql-error-codes.xml 的配置文件内容
来创建 SQLErrorCodes 的工厂。该文件中填充了提供商代码,并基于从 DatabaseMetaData 获取的 DatabaseProductName 生成。
实际使用的数据库的代码将被应用于此。
SQLErrorCodeSQLExceptionTranslator 按照以下顺序应用匹配规则:
-
子类实现的任何自定义翻译。通常,使用提供的具体
SQLErrorCodeSQLExceptionTranslator,因此此规则不适用。只有在您实际提供了子类实现时才适用。 -
任何对
SQLExceptionTranslator接口的自定义实现,作为customSqlExceptionTranslator属性提供给SQLErrorCodes类。 -
查找
CustomSQLErrorCodesTranslation类的实例列表(为SQLErrorCodes类的customTranslations属性提供)中的匹配项。 -
错误代码匹配已应用。
-
使用备用翻译器。
SQLExceptionSubclassTranslator是默认的备用翻译器。如果此翻译不可用,则下一个备用翻译器是SQLStateSQLExceptionTranslator。
默认使用 SQLErrorCodesFactory 来定义错误代码和自定义异常的翻译。它们会在名为 sql-error-codes.xml 的文件中查找,该文件位于类路径中,并根据正在使用的数据库的数据库元数据中的数据库名称找到匹配的 SQLErrorCodes 实例。 |
您可以扩展 SQLErrorCodeSQLExceptionTranslator,如下面的示例所示:
-
Java
-
Kotlin
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {
if (sqlEx.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, sqlEx);
}
return null;
}
}
class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() {
override fun customTranslate(task: String, sql: String?, sqlEx: SQLException): DataAccessException? {
if (sqlEx.errorCode == -12345) {
return DeadlockLoserDataAccessException(task, sqlEx)
}
return null
}
}
在前面的示例中,特定的错误代码(-12345)被翻译,而其他错误则由默认的翻译器实现来处理。
要使用此自定义翻译器,必须通过方法 setExceptionTranslator 将其传递给 JdbcTemplate,并且必须使用此 JdbcTemplate 来处理所有需要此翻译器的数据访问操作。下面的示例显示了如何使用此自定义翻译器:
-
Java
-
Kotlin
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
// create a JdbcTemplate and set data source
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);
// create a custom translator and set the DataSource for the default translation lookup
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
tr.setDataSource(dataSource);
this.jdbcTemplate.setExceptionTranslator(tr);
}
public void updateShippingCharge(long orderId, long pct) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId);
}
// create a JdbcTemplate and set data source
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
// create a custom translator and set the DataSource for the default translation lookup
exceptionTranslator = CustomSQLErrorCodesTranslator().apply {
this.dataSource = dataSource
}
}
fun updateShippingCharge(orderId: Long, pct: Long) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate!!.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId)
}
自定义翻译器会传入数据源,以便在sql-error-codes.xml中查找错误代码。
运行语句
执行SQL语句所需的代码非常少。你需要一个DataSource和一个JdbcTemplate,包括随JdbcTemplate提供的便捷方法。下面的示例显示了创建新表所需包含的内容:
-
Java
-
Kotlin
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAStatement {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void doExecute() {
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class ExecuteAStatement(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun doExecute() {
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
}
}
执行查询
一些查询方法返回单个值。要从一行中检索计数或特定值,请使用queryForObject(..)。后者将返回的JDBC Type转换为作为参数传入的Java类。如果类型转换无效,会抛出InvalidDataAccessApiUsageException。下面的例子包含两个查询方法,一个用于int,另一个用于查询String:
-
Java
-
Kotlin
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class RunAQuery {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int getCount() {
return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
}
public String getName() {
return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class RunAQuery(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
val count: Int
get() = jdbcTemplate.queryForObject("select count(*) from mytable")!!
val name: String?
get() = jdbcTemplate.queryForObject("select name from mytable")
}
除了单结果查询方法外,还有几种方法会返回一个列表,其中每个条目对应查询返回的一行。最通用的方法是queryForList(..),它返回一个List,其中每个元素是一个Map,每个列对应一个条目,使用列名作为键。如果你在前面的示例中添加一个方法以检索所有行的列表,它可能如下所示:
-
Java
-
Kotlin
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Map<String, Object>> getList() {
return this.jdbcTemplate.queryForList("select * from mytable");
}
private val jdbcTemplate = JdbcTemplate(dataSource)
fun getList(): List<Map<String, Any>> {
return jdbcTemplate.queryForList("select * from mytable")
}
返回的列表将如下所示:
[{name=Bob, id=1}, {name=Mary, id=2}]
更新数据库
以下示例更新某个主键的列:
-
Java
-
Kotlin
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAnUpdate {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setName(int id, String name) {
this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class ExecuteAnUpdate(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun setName(id: Int, name: String) {
jdbcTemplate.update("update mytable set name = ? where id = ?", name, id)
}
}
在前面的例子中, SQL 语句有行参数的占位符。你可以以 varargs 的形式或,作为对象数组传入参数值。因此,你应该显式地将基本类型包装在基本类型包装类中,或者使用自动装箱。
检索自动生成的键
一个 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