Comment on page
3.3使用JDBC核心类控制基本JDBC处理和错误处理
本节介绍如何使用JDBC核心类来控制基本的JDBC处理,包括错误处理。它包括以下主题:
- 使用JdbcTemplate
- 使用NamedParameterJdbcTemplate
- 使用SQLExceptionTranslator
- 运行声明
- 运行查询
- 更新数据库
- 检索自动生成的密钥
JdbcTemplate是JDBC核心软件包中的中心类。它处理资源的创建和释放,这有助于你避免常见的错误,例如忘记关闭连接。它执行核心JDBC工作流程的基本任务(例如,语句创建和执行),而使应用程序代码提供SQL并提取结果。 JdbcTemplate类:
- 运行SQL查询
- 更新语句和存储过程调用
- 对ResultSet实例执行迭代并提取返回的参数值。
- 捕获JDBC异常并将其转换为org.springframework.dao包中定义的通用,信息量更大的异常层次结构。 (请参见一致的异常层次结构。)
当将JdbcTemplate用于代码时,只需实现回调接口,即可为它们提供明确定义的协定。给定JdbcTemplate类提供的Connection,PreparedStatementCreator回调接口创建一个准备好的语句,提供SQL和任何必要的参数。创建可调用语句的CallableStatementCreator接口也是如此。 RowCallbackHandler接口从ResultSet的每一行提取值。
你可以通过直接实例化DataSource引用在DAO实现中使用JdbcTemplate,也可以在Spring IoC容器中对其进行配置,并将其作为Bean引用提供给DAO。
应该始终在Spring IoC容器中将DataSource配置为Bean。在第一种情况下,bean被直接提供给服务。在第二种情况下,将其提供给准备好的模板。
此类发出的所有SQL都在DEBUG级别下记录,该类别对应于模板实例的完全合格的类名称(通常为JdbcTemplate ,但是如果使用JdbcTemplate类的自定义子类,则可能有所不同)。
以下各节提供了一些JdbcTemplate用法的示例。这些示例不是JdbcTemplate公开的所有功能的详尽列表。请参阅附带的javadoc。
查询(SELECT)
以下查询获取关系中的行数:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
以下查询使用绑定变量:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
以下查询查找字符串:
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
new Object[]{1212L}, String.class);
以下查询查找并填充单个域对象:
Actor actor = this.jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
new Object[]{1212L},
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
以下查询查找并填充许多域对象:
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
如果最后两个代码片段实际存在于同一个应用程序中,那么删除两个RowMapper匿名内部类中存在的重复并将它们提取到一个可以引用的单个类(通常是静态嵌套类)中是有意义的。 根据需要通过DAO方法。 例如,最好编写前面的代码片段,如下所示:
public List<Actor> findAllActors() {
return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}
private static final class ActorMapper implements RowMapper<Actor> {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}
Updating (INSERT, UPDATE, and DELETE) with JdbcTemplate
你可以使用update(..)方法执行插入,更新和删除操作。 参数值通常作为变量参数提供,或者作为对象数组提供。
以下示例插入一个新条目 :
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
以下示例更新现有条目:
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
以下示例删除现有条目:
this.jdbcTemplate.update(
"delete from actor where id = ?",
Long.valueOf(actorId));
其他JdbcTemplate操作
你可以使用execute(..)方法来运行任何任意SQL。 因此,该方法通常用于DDL语句。 带有回调接口,绑定变量数组等的变体极大地超载了它。 以下示例创建一个表:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
下面调用一个存储过程:
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
JdbcTemplate最佳实践
一旦配置,JdbcTemplate类的实例是线程安全的。 这很重要,因为这意味着你可以配置JdbcTemplate的单个实例,然后安全地将此共享引用注入到多个DAO(或存储库)中。 JdbcTemplate是有状态的,因为它维护对DataSource的引用,但是此状态不是会话状态。
使用JdbcTemplate类(和关联的NamedParameterJdbcTemplate类)的常见做法是在Spring配置文件中配置DataSource,然后将共享的DataSource bean依赖注入到DAO类中。 在数据源的设置器中创建了JdbcTemplate。 这导致类似于以下内容的DAO:
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...
}
下面是相应的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注解该类(这使其成为组件扫描的候选对象),并使用@Autowired注解DataSource setter方法。 以下示例显示了如何执行此操作:
@Repository
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new 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">
<!-- 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类,则你的子类将从JdbcDaoSupport类继承一个setDataSource(..)方法。 你可以选择是否从此类继承。 提供JdbcDaoSupport类只是为了方便。
不管你选择使用(或不使用)以上哪种模板初始化样式,都不需要在每次要运行SQL时都创建JdbcTemplate类的新实例。 配置完成后,JdbcTemplate实例是线程安全的。 如果你的应用程序访问多个数据库,则可能需要多个JdbcTemplate实例,这需要多个DataSources,以及随后的多个不同配置的JdbcTemplate实例。
与仅使用经典占位符('?')编程的JDBC语句相反,NamedParameterJdbcTemplate类增加了使用命名参数对JDBC语句进行编程的支持。 NamedParameterJdbcTemplate类包装JdbcTemplate并委托给包装的JdbcTemplate完成其许多工作。 本节仅描述NamedParameterJdbcTemplate类的与JdbcTemplate本身不同的那些区域,即使用命名参数对JDBC语句进行编程。 下面的示例演示如何使用NamedParameterJdbcTemplate:
// 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变量的值和插入namednames变量(MapSqlParameterSource类型)的相应值中使用了命名参数符号。
另外,你也可以使用基于Map的样式将命名参数及其对应的值传递给NamedParameterJdbcTemplate实例.NamedParameterJdbcOperations公开和NamedParameterJdbcTemplate类实现的其余方法遵循类似的模式,此处不再赘述。
以下示例说明了基于Map的样式的使用:
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}