|
对于最新稳定版本,请使用 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 回调接口会创建一个预编译语句(prepared statement),并提供 SQL 语句及所有必要的参数。CallableStatementCreator 接口也是如此,它用于创建可调用语句(callable statements)。RowCallbackHandler 接口则从 ResultSet 的每一行中提取值。
您可以通过直接使用JdbcTemplate和DataSource引用进行实例化,在DAO实现中使用它,或者将其配置在一个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 的引用,但这种状态并非会话状态(conversational state)。
使用 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>
显式配置的一种替代方式是使用组件扫描(component-scanning)和注解支持进行依赖注入。在这种情况下,您可以使用 @Repository 注解该类(使其成为组件扫描的候选对象),并使用 DataSource 注解 @Autowired 的 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)
}
// 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配置:
<?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)中的值。
或者,你可以使用基于 NamedParameterJdbcTemplate 的方式,将命名参数及其对应的值传递给 Map 实例。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() 方法,经由 JdbcTemplate 接口来访问被包装的 JdbcOperations。
另请参阅 JdbcTemplate 最佳实践,
以获取在应用程序上下文中使用 NamedParameterJdbcTemplate 类的指南。
使用SQLExceptionTranslator
SQLExceptionTranslator 是一个接口,由能够将 SQLException 转换为 Spring 自身的 org.springframework.dao.DataAccessException 的类实现。后者对数据访问策略是无关的(agnostic)。实现可以是通用的(例如,使用 JDBC 的 SQLState 代码),也可以是专有的(例如,使用 Oracle 错误代码),以获得更高的精确度。这种异常转换机制被用于常见的 JdbcTemplate 和 JdbcTransactionManager 入口点背后,这些入口点不会传播 SQLException,而是传播 DataAccessException。
从 6.0 版本开始,默认的异常翻译器是 SQLExceptionSubclassTranslator,
它通过少量额外检查来识别 JDBC 4 的 SQLException 子类,并在必要时回退到通过 SQLState
进行 SQLStateSQLExceptionTranslator 内省。这通常足以满足常见的数据库访问需求,且无需依赖特定数据库厂商的检测。
为了向后兼容,可考虑使用如下所述的 SQLErrorCodeSQLExceptionTranslator,
并可能配合自定义的错误代码映射。 |
SQLErrorCodeSQLExceptionTranslator 是 SQLExceptionTranslator 的实现,当类路径根目录下存在名为 sql-error-codes.xml 的文件时,默认使用该实现。此实现使用特定的厂商代码,比 SQLState 或 SQLException 的子类转换更为精确。错误代码的翻译基于一个名为 SQLErrorCodes 的 JavaBean 类型类中持有的代码。该类由一个 SQLErrorCodesFactory 创建并填充,顾名思义,它是一个工厂,用于根据名为 sql-error-codes.xml 的配置文件内容创建 SQLErrorCodes。该文件填充了厂商代码,并基于从 DatabaseMetaData 获取的 DatabaseProductName。实际使用的数据库对应的代码将被采用。
The SQLErrorCodeSQLExceptionTranslator 应用匹配规则的顺序如下:
-
由子类实现的任何自定义转换逻辑。通常使用所提供的具体实现
SQLErrorCodeSQLExceptionTranslator,因此此规则不适用。仅当您实际提供了子类实现时,此规则才适用。 -
任何自定义实现
SQLExceptionTranslator接口的类,作为customSqlExceptionTranslator类的SQLErrorCodes属性提供。 -
的
CustomSQLErrorCodesTranslation类的实例列表(作为customTranslations属性提供给SQLErrorCodes类)会被搜索以找到匹配项。 -
错误代码匹配已应用。
-
使用备用翻译器。
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)会被翻译,而其他错误则交由默认的翻译器实现进行翻译。
要使用此自定义翻译器,您必须通过 JdbcTemplate 方法将其传递给 setExceptionTranslator,
并且在所有需要此翻译器的数据访问处理中都必须使用该 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,该 3 包含对应行中每一列的条目,并以列名作为键。如果你在前面的示例中添加一个方法来获取所有行的列表,该方法可能如下所示:
-
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