Spring数据访问和数据访问层与业务或服务层之间的交互(二)

时间:2022-11-17 14:20:03

Spring数据访问和数据访问层与业务或服务层之间的交互(二)

2. DAO 支持

Spring 中的数据访问对象 (DAO) 支持旨在使其易于使用 以一致的方式访问数据访问技术(如 JDBC、Hibernate 或 JPA)。这 让您相当轻松地在上述持久性技术之间切换, 它还允许您编码,而不必担心捕获异常 特定于每种技术。

2.1. 一致的异常层次结构

Spring 提供了从特定于技术的异常的便捷转换,例如到它自己的异常类层次结构,它有 根异常。这些异常包装原始异常,因此永远不会 您可能会丢失有关可能出错的任何信息的任何风险。​​SQLException​​​​DataAccessException​

除了 JDBC 异常之外,Spring 还可以包装特定于 JPA 和 Hibernate 的异常, 将它们转换为一组集中的运行时异常。这使您可以处理大多数 仅在适当的层中出现不可恢复的持久性异常,而没有 DAO 中烦人的样板捕获和抛出块和异常声明。 (不过,您仍然可以在需要的任何位置捕获和处理异常。如上所述, JDBC 异常(包括特定于数据库的方言)也会转换为相同的 层次结构,这意味着您可以在一致的 JDBC 中执行一些操作 编程模型。

前面的讨论适用于 Spring 支持中的各种模板类 适用于各种 ORM 框架。如果使用基于侦听器的类,则应用程序必须 关心处理和本身,最好由 分别委托给理论方法。这些方法转换异常 与异常层次结构中的异常兼容的异常。未经检查,它们也可能被扔掉 (不过,在例外方面牺牲了通用的DAO抽象)。​​HibernateExceptions​​​​PersistenceExceptions​​​​convertHibernateAccessException(..)​​​​convertJpaAccessException(..)​​​​SessionFactoryUtils​​​​org.springframework.dao​​​​PersistenceExceptions​

下图显示了 Spring 提供的异常层次结构。 (请注意,图中详述的类层次结构仅显示整个层次结构的子集。​​DataAccessException​

Spring数据访问和数据访问层与业务或服务层之间的交互(二)

2.2. 用于配置 DAO 或存储库类的注释

保证数据访问对象 (DAO) 或存储库提供的最佳方法 例外翻译是使用注释。此注释还 让组件扫描支持查找和配置您的 DAO 和存储库 而不必为它们提供 XML 配置条目。以下示例显示 如何使用注释:​​@Repository​​​​@Repository​

@Repository 
public class SomeMovieFinder implements MovieFinder {
// ...
}

注释。​​@Repository​

任何 DAO 或存储库实现都需要访问持久性资源, 取决于所使用的持久性技术。例如,基于 JDBC 的存储库 需要访问 JDBC,基于 JPA 的存储库需要访问 。实现此目的的最简单方法是具有此资源依赖关系 通过使用其中一个,,oran注释注入。以下示例适用于 JPA 存储库:​​DataSource​​​​EntityManager​​​​@Autowired​​​​@Inject​​​​@Resource​​​​@PersistenceContext​

@Repository
public class JpaMovieFinder implements MovieFinder {

@PersistenceContext
private EntityManager entityManager;

// ...
}

如果您使用经典的 Hibernate API,则可以注入,如下所示 示例显示:​​SessionFactory​

@Repository
public class HibernateMovieFinder implements MovieFinder {

private SessionFactory sessionFactory;

@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}

// ...
}

我们在这里展示的最后一个示例是典型的 JDBC 支持。您可以注入到初始化方法或构造函数中,您将在其中使用 这。以下示例自动连线 a:​​DataSource​​​​JdbcTemplate​​​​SimpleJdbcCall​​​​DataSource​​​​DataSource​

@Repository
public class JdbcMovieFinder implements MovieFinder {

private JdbcTemplate jdbcTemplate;

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

// ...
}

3. 使用 JDBC 访问数据

Spring Framework JDBC 抽象提供的价值也许最好地表现为 下表中概述的操作顺序。该表显示了哪些操作 Spring 照顾和哪些行动是你的责任。

表 4.春季JDBC - 谁做什么?

行动

春天


定义连接参数。

X

打开连接。

X

指定 SQL 语句。

X

声明参数并提供参数值

X

准备并运行语句。

X

设置循环以循环访问结果(如果有)。

X

完成每次迭代的工作。

X

处理任何异常。

X

处理事务。

X

关闭连接、语句和结果集。

X

Spring 框架负责所有可以使 JDBC 成为 繁琐的 API。

3.1. 选择 JDBC 数据库访问方法

您可以在多种方法中进行选择,以构成 JDBC 数据库访问的基础。 除了三种风格之外,newand方法还优化了数据库元数据,而RDBMS对象样式采用了 更面向对象的方法类似于JDO查询设计。开始使用后 其中一种方法,您仍然可以混合和匹配以包含来自 不同的方法。所有方法都需要符合 JDBC 2.0 的驱动程序,并且某些 高级功能需要 JDBC 3.0 驱动程序。​​JdbcTemplate​​​​SimpleJdbcInsert​​​​SimpleJdbcCall​

  • ​JdbcTemplate​​是经典和最流行的春季JDBC方法。这 “最低级别”方法和所有其他方法都在幕后使用 Jdbc 模板。
  • ​NamedParameterJdbcTemplate​​包装 ATO 提供命名参数 而不是传统的 JDBC 占位符。这种方法提供更好的 文档和 SQL 语句有多个参数时的易用性。JdbcTemplate?
  • ​SimpleJdbcInsert​​并优化数据库元数据以限制数量 必要的配置。此方法简化了编码,因此您需要 仅提供表或过程的名称,并提供参数匹配的映射 列名称。仅当数据库提供足够的元数据时,此操作才有效。如果 数据库不提供此元数据,您必须提供显式 参数的配置。SimpleJdbcCall
  • RDBMS 对象 — 包括、和— 要求您在初始化期间创建可重用和线程安全的对象 数据访问层。此方法以 JDO 查询为模型,其中您可以定义您的 查询字符串,声明参数,然后编译查询。一旦你这样做了,,,方法就可以被称为多个 具有各种参数值的时间。MappingSqlQuerySqlUpdateStoredProcedureexecute(…​)update(…​)findObject(…​)

3.2. 软件包层次结构

Spring 框架的 JDBC 抽象框架由四个不同的包组成:

  • ​core​​:包包含类及其 各种回调接口,以及各种相关类。名为 theandclasses 的子包包含 theandclass。另一个名为 的子包包含类和相关的支持类。请参阅使用 JDBC 核心类控制基本的 JDBC 处理和错误处理、JDBC 批处理操作和使用SimpleJdbc类简化 JDBC 操作。org.springframework.jdbc.coreJdbcTemplateorg.springframework.jdbc.core.simpleSimpleJdbcInsertSimpleJdbcCallorg.springframework.jdbc.core.namedparamNamedParameterJdbcTemplate
  • ​datasource​​:该软件包包含一个易于访问的实用程序类和各种可用于的简单实现 在 Jakarta EE 容器之外测试和运行未修改的 JDBC 代码。一个子包 named提供对创建的支持 使用 Java 数据库引擎(如 HSQL、H2 和 Derby)嵌入数据库。请参阅控制数据库连接和嵌入式数据库支持。org.springframework.jdbc.datasourceDataSourceDataSourceorg.springfamework.jdbc.datasource.embedded
  • ​object​​:包包含表示 RDBMS 的类 查询、更新和存储过程作为线程安全、可重用的对象。请参阅将 JDBC 操作建模为 Java 对象。此方法由 JDO 建模,尽管对象由查询返回 自然与数据库断开连接。这种更高级别的 JDBC 抽象 取决于包中的较低级别抽象。org.springframework.jdbc.objectorg.springframework.jdbc.core
  • ​support​​:包提供翻译 功能和一些实用程序类。JDBC 处理期间引发的异常是 转换为包中定义的异常。这意味着 使用 Spring JDBC 抽象层的代码不需要实现 JDBC 或 特定于 RDBMS 的错误处理。所有翻译的异常均未选中,这为您提供了 捕获异常的选项,您可以从中恢复,同时让其他 异常将传播到调用方。请参见使用SQLExceptionTranslator。org.springframework.jdbc.supportSQLExceptionorg.springframework.dao

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

本节介绍如何使用 JDBC 核心类来控制基本的 JDBC 处理。 包括错误处理。它包括以下主题:

  • 使用Jdbc 模板
  • 使用NamedParameterJdbcTemplate
  • 使用SQLExceptionTranslator
  • 运行语句
  • 运行查询
  • 更新数据库
  • 检索自动生成的密钥

3.3.1. 使用​​JdbcTemplate​

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

  • 运行 SQL 查询
  • 更新语句和存储过程调用
  • 执行迭代覆盖实例和返回参数值的提取。ResultSet
  • 捕获 JDBC 异常并将其转换为通用的、信息量更大的异常 包中定义的层次结构。(请参阅一致的异常层次结构。org.springframework.dao

当你使用for你的代码时,你只需要实现回调 接口,为它们提供明确定义的协定。给定由类提供,回调接口创建一个准备好的 语句,提供 SQL 和任何必要的参数。接口也是如此,它创建可调用的语句。接口从 a 的每一行中提取值。​​JdbcTemplate​​​​Connection​​​​JdbcTemplate​​​​PreparedStatementCreator​​​​CallableStatementCreator​​​​RowCallbackHandler​​​​ResultSet​

您可以通过直接实例化在 DAO 实现中使用 或者您可以在Spring IoC容器中配置它并将其提供给 DAO 作为豆子参考。​​JdbcTemplate​​​​DataSource​

此类发出的所有 SQL 都记录在类别下的级别 对应于模板实例的完全限定类名(通常,但如果使用 TheClass 的自定义子类,则可能会有所不同)。​​DEBUG​​​​JdbcTemplate​​​​JdbcTemplate​

以下各节提供了一些用法示例。这些例子 不是公开的所有功能的详尽列表。 请参阅随之而来的javadoc了解这一点。​​JdbcTemplate​​​​JdbcTemplate​

查询 (​​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​

String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);

以下查询查找并填充单个域对象:

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

以下查询查找并填充域对象列表:

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

如果最后两个代码片段实际上存在于同一个应用程序中,它将使 删除 twolambda 表达式中存在的重复项,以及 将它们提取到单个字段中,然后可以根据需要由 DAO 方法引用。 例如,最好按如下方式编写前面的代码片段:​​RowMapper​

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​​​​UPDATE​​​​DELETE​​​​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 t_actor where id = ?",
Long.valueOf(actorId));
其他操作​​JdbcTemplate​

您可以使用该方法运行任意 SQL。因此, 方法通常用于 DDL 语句。它被大量超载的变体 回调接口、绑定变量数组等。以下示例创建一个 桌子:​​execute(..)​

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

下面的示例调用存储过程:

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

稍后将介绍更复杂的存储过程支持。

​JdbcTemplate​​最佳实践

一旦配置,类的实例是线程安全的。这是 很重要,因为这意味着您可以配置 aand 的单个实例,然后将此共享引用安全地注入多个 DAO(或存储库)。 Theis 有状态的,因为它保持对 a 的引用,但是 此状态不是会话状态。​​JdbcTemplate​​​​JdbcTemplate​​​​JdbcTemplate​​​​DataSource​

使用类(以及关联的NamedParameterJdbcTemplate类)时的常见做法是 在 Spring 配置文件中配置,然后进行依赖注入 将豆子共享到您的 DAO 类中。泰斯创建于 二传手。这会导致类似于以下内容的 DAO:​​JdbcTemplate​​​​DataSource​​​​DataSource​​​​JdbcTemplate​​​​DataSource​

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

用注释类。​​@Repository​

注释方法。​​DataSource​​​​@Autowired​

创建一个新的。​​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">

<bean class="com.example.JdbcCorporateEventDao">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean 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​​​​DataSource​​​​@Autowired​

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

用注释类。​​@Repository​

构造函数注入的。​​DataSource​

创建一个新的。​​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 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 的类和你的各种 JDBC 支持的 DAO 类 从它扩展,你的子类从类继承amethod。您可以选择是否从此类继承。该课程仅为方便起见而提供。​​JdbcDaoSupport​​​​setDataSource(..)​​​​JdbcDaoSupport​​​​JdbcDaoSupport​

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

3.3.2. 使用​​NamedParameterJdbcTemplate​

该类增加了对 JDBC 语句编程的支持 通过使用命名参数,而不是仅使用经典参数对 JDBC 语句进行编程 占位符 () 参数。该类包装和委托给包装以完成其大部分工作。这 部分仅介绍类中那些不同的区域 从自身 — 即通过使用命名对 JDBC 语句进行编程 参数。以下示例演示如何使用:​​NamedParameterJdbcTemplate​​​​'?'​​​​NamedParameterJdbcTemplate​​​​JdbcTemplate​​​​JdbcTemplate​​​​NamedParameterJdbcTemplate​​​​JdbcTemplate​​​​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​​​​namedParameters​​​​MapSqlParameterSource​

或者,您可以使用基于样式将命名参数及其相应的值传递给实例。其余的 由 TheAnd Class实现的方法遵循类似的模式,此处不涉及。​​NamedParameterJdbcTemplate​​​​Map​​​​NamedParameterJdbcOperations​​​​NamedParameterJdbcTemplate​

以下示例显示了基于样式的用法:​​Map​

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

一个与(并且存在于相同的 Java包)是接口。你已经看过一个例子 此接口在前面的代码段之一 (Theclass) 中的实现。Anis 命名参数的来源 值为 a。该类是一个 简单的实现,即围绕 a 的适配器,其中键 是参数名称,值是参数值。​​NamedParameterJdbcTemplate​​​​SqlParameterSource​​​​MapSqlParameterSource​​​​SqlParameterSource​​​​NamedParameterJdbcTemplate​​​​MapSqlParameterSource​​​​java.util.Map​

另一个实现是类。此类包装一个任意 JavaBean(即,一个类的实例 坚持 JavaBean 约定),并使用包装的 JavaBean 的属性作为源代码 的命名参数值。​​SqlParameterSource​​​​BeanPropertySqlParameterSource​

以下示例显示了一个典型的 JavaBean:

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

}

以下示例使用 ato 返回 前面示例中所示的类的成员:​​NamedParameterJdbcTemplate​

// 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​

另请参阅JdbcTemplate最佳实践,了解有关在应用程序上下文中使用类的指南。​​NamedParameterJdbcTemplate​

3.3.3. 使用​​SQLExceptionTranslator​

​SQLExceptionTranslator​​是由可以翻译的类实现的接口 在斯普林斯之间, 这在数据访问策略方面是不可知的。实现可以是通用的(对于 例如,对 JDBC 使用 SQLState 代码)或专有代码(例如,使用 Oracle 错误) 代码),以提高精度。​​SQLException​​​​org.springframework.dao.DataAccessException​

​SQLErrorCodeSQLExceptionTranslator​​是默认使用的实现。此实现使用特定的供应商代码。它更多 比实施精确。错误代码翻译基于 代码保存在调用的 JavaBean 类型类中。此类已创建并 由 an 填充,它(顾名思义)是 创建基于命名的配置文件的内容。此文件填充有供应商代码,并基于取自。实际代码 使用您正在使用的数据库。​​SQLExceptionTranslator​​​​SQLState​​​​SQLErrorCodes​​​​SQLErrorCodesFactory​​​​SQLErrorCodes​​​​sql-error-codes.xml​​​​DatabaseProductName​​​​DatabaseMetaData​

按以下顺序应用匹配规则:​​SQLErrorCodeSQLExceptionTranslator​

  1. 由子类实现的任何自定义翻译。通常,使用所提供的混凝土,因此此规则不适用。它 仅当您实际提供了子类实现时才适用。SQLErrorCodeSQLExceptionTranslator
  2. 提供的接口的任何自定义实现 作为类的属性。SQLExceptionTranslatorcustomSqlExceptionTranslatorSQLErrorCodes
  3. 搜索类的实例列表(为类的属性提供)以查找匹配项。CustomSQLErrorCodesTranslationcustomTranslationsSQLErrorCodes
  4. 应用错误代码匹配。
  5. 使用回退 translator.is 默认回退 在线翻译。如果此翻译不可用,则下一个回退转换器是 这。SQLExceptionSubclassTranslatorSQLStateSQLExceptionTranslator

您可以扩展,如以下示例所示:​​SQLErrorCodeSQLExceptionTranslator​

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​​​​JdbcTemplate​​​​setExceptionTranslator​​​​JdbcTemplate​

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​

3.3.4. 运行语句

运行 SQL 语句只需要很少的代码。您需要a和a,包括随之提供的便利方法。以下示例显示了您需要包含的最小但 创建新表的全功能类:​​DataSource​​​​JdbcTemplate​​​​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))");
}
}

3.3.5. 运行查询

某些查询方法返回单个值。从中检索计数或特定值 一行,使用。后者将返回的 JDBC 转换为 作为参数传入的 Java 类。如果类型转换无效,则引发 anis 。以下示例包含两个 查询方法,一个用于 an,另一个用于查询 a:​​queryForObject(..)​​​​Type​​​​InvalidDataAccessApiUsageException​​​​int​​​​String​

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

除了单个结果查询方法之外,还有几种方法返回一个列表,其中包含 查询返回的每一行的条目。最通用的方法是, 它返回 awhere 每个元素包含每列一个条目, 使用列名作为键。如果将方法添加到前面的示例以检索 所有行的列表,可能如下所示:​​queryForList(..)​​​​List​​​​Map​

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

返回的列表将类似于以下内容:

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

3.3.6. 更新数据库

下面的示例更新某个主键的列:

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 语句具有行参数的占位符。您可以传递参数值 作为变量,或者作为对象数组。因此,您应该显式包装基元 在基元包装类中,或者您应该使用自动装箱。

3.3.7. 检索自动生成的密钥

一种方便的方法支持检索由 数据库。此支持是 JDBC 3.0 标准的一部分。请参阅第 13.6 章 详细规范。该方法首先采用 aas 参数,这是指定所需插入语句的方式。另一个 参数为 a,它包含从 更新。没有标准的单一方法来创建一个适当的(这解释了为什么方法签名是这样的)。以下示例有效 在甲骨文上,但可能无法在其他平台上运行:​​update()​​​​PreparedStatementCreator​​​​KeyHolder​​​​PreparedStatement​

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

3.4. 控制数据库连接

本节涵盖:

  • 使用数据源
  • 使用数据源实用程序
  • 实现智能数据源
  • 扩展抽象数据源
  • 使用单一连接数据源
  • 使用驱动程序管理器数据源
  • 使用TransactionAwareDataSourceProxy
  • 使用数据源事务管理器

3.4.1. 使用​​DataSource​

Spring 通过 a 获取与数据库的连接。艾斯 JDBC 规范的一部分,是一个通用的连接工厂。它让一个 容器或框架隐藏连接池和事务管理问题 从应用程序代码。作为开发人员,您无需知道如何详细了解 连接到数据库。这是设置的管理员的责任 数据源。在开发和测试代码时,您很可能会同时担任这两个角色,但是您 不一定必须知道生产数据源的配置方式。​​DataSource​​​​DataSource​

当你使用Spring的JDBC层时,你可以从JNDI获取数据源,或者你可以 使用第三方提供的连接池实现配置您自己的连接池实现。 传统的选择是带有bean样式类的Apache Commons DBCP和C3P0; 对于现代 JDBC 连接池,请考虑使用 HikariCP 及其构建器样式的 API。​​DataSource​

以下部分使用 Spring 的实现。 后面将介绍其他几种变体。​​DriverManagerDataSource​​​​DataSource​

要配置:​​DriverManagerDataSource​

  1. 获取连接,因为您通常获得 JDBC 连接。DriverManagerDataSource
  2. 指定 JDBC 驱动程序的完全限定类名,以便可以装入驱动程序类。DriverManager
  3. 提供因 JDBC 驱动程序而异的 URL。(请参阅驱动程序的文档 获得正确的值。
  4. 提供用户名和密码以连接到数据库。

以下示例显示了如何配置 ain Java:​​DriverManagerDataSource​

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");

以下示例显示了相应的 XML 配置:

<bean  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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"/>

接下来的两个示例显示了 DBCP 和 C3P0 的基本连接和配置。 要了解有助于控制池化的更多选项,请参阅产品 有关相应连接池实现的文档。

以下示例显示了 DBCP 配置:

<bean  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"/>

以下示例显示了 C3P0 配置:

<bean  class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

3.4.2. 使用​​DataSourceUtils​

Theclass 是一个方便而强大的帮助程序类,它提供了从 JNDI 获取连接并在必要时关闭连接的方法。它 例如,支持线程绑定连接。​​DataSourceUtils​​​​static​​​​DataSourceTransactionManager​

3.4.3. 实现​​SmartDataSource​

该接口应由可以提供 与关系数据库的连接。它扩展了接口,让 使用它的类查询在给定后是否应关闭连接 操作。当您知道需要重用连接时,此用法非常有效。​​SmartDataSource​​​​DataSource​

3.4.4. 扩展​​AbstractDataSource​

​AbstractDataSource​​是 Spring 实现的基础类。它实现了所有实现通用的代码。 如果你编写自己的实现,你应该扩展类。​​abstract​​​​DataSource​​​​DataSource​​​​AbstractDataSource​​​​DataSource​

3.4.5. 使用​​SingleConnectionDataSource​

Theclass 是接口的实现,它包装一个单曲每次使用后都不会关闭。 这不是多线程功能。​​SingleConnectionDataSource​​​​SmartDataSource​​​​Connection​

如果任何客户端代码调用池连接的假设(如使用 持久性工具),应将属性设置为。此设置 返回包装物理连接的关闭抑制代理。请注意,您可以 不再将其强制转换为本机 Oracle 或类似的对象。​​close​​​​suppressClose​​​​true​​​​Connection​

​SingleConnectionDataSource​​主要是一个测试类。它通常可以轻松测试 应用程序服务器外部的代码,结合简单的 JNDI 环境。 相反,它一直重复使用相同的连接, 避免过度创建物理连接。​​DriverManagerDataSource​

3.4.6. 使用​​DriverManagerDataSource​

Theclass 是标准接口的实现,它通过 bean 属性配置一个普通的 JDBC 驱动程序,并每次都返回一个 new。​​DriverManagerDataSource​​​​DataSource​​​​Connection​

此实现对于 Jakarta EE 之外的测试和独立环境非常有用 容器,作为 Spring IoC 容器中的 abean 或组合使用 使用简单的 JNDI 环境。池假设调用 关闭连接,以便任何感知的持久性代码都应该有效。然而 使用JavaBean风格的连接池(例如)非常简单,即使在测试中也是如此 环境,几乎总是最好使用这样的连接池。​​DataSource​​​​Connection.close()​​​​DataSource​​​​commons-dbcp​​​​DriverManagerDataSource​

3.4.7. 使用​​TransactionAwareDataSourceProxy​

​TransactionAwareDataSourceProxy​​是目标的代理。代理包装了该 目标以增加对 Spring 管理的交易的认知度。在这方面,它 类似于 Jakarta EE 服务器提供的事务性 JNDI。​​DataSource​​​​DataSource​​​​DataSource​

有关更多详细信息,请参阅TransactionAwareDataSourceProxyjavadoc。

3.4.8. 使用​​DataSourceTransactionManager​

该类是单个 JDBC 数据源的实现。它绑定来自 当前正在执行的线程的指定数据源,可能允许一个 每个数据源的线程连接。​​DataSourceTransactionManager​​​​PlatformTransactionManager​

需要应用程序代码才能检索 JDBC 连接,而不是 Jakarta EE 的标准。它抛出未选中的异常 而不是检查。所有框架类(例如)都使用此 隐含的策略。如果未与此事务管理器一起使用,则查找策略 行为与普通人完全相同。因此,它可以在任何情况下使用。​​DataSourceUtils.getConnection(DataSource)​​​​DataSource.getConnection​​​​org.springframework.dao​​​​SQLExceptions​​​​JdbcTemplate​

该类支持自定义隔离级别和超时 作为适当的 JDBC 语句查询超时应用。为了支持后者, 应用程序代码必须为每个创建的语句使用或调用该方法。​​DataSourceTransactionManager​​​​JdbcTemplate​​​​DataSourceUtils.applyTransactionTimeout(..)​

您可以使用此实现而不是在单一资源中 的情况下,因为它不需要容器支持 JTA。在 两者都只是配置问题,只要您坚持所需的连接查找 模式。JTA 不支持自定义隔离级别。​​JtaTransactionManager​

3.5. JDBC 批处理操作

如果对同一调用进行批处理,则大多数 JDBC 驱动程序都会提供改进的性能 准备好的声明。通过将更新分组到批处理中,可以限制往返次数 到数据库。

3.5.1. 基本批处理操作​​JdbcTemplate​

通过实现两种特殊方法完成批处理 接口,并将该实现作为第二个参数传入 在您的方法调用中。您可以使用该方法提供大小 当前批处理。您可以使用该方法设置参数的值 准备好的声明。此方法称为您 在调用中指定。以下示例更新表 基于列表中的条目,并将整个列表用作批处理:​​JdbcTemplate​​​​BatchPreparedStatementSetter​​​​batchUpdate​​​​getBatchSize​​​​setValues​​​​getBatchSize​​​​t_actor​

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

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

public int[] batchUpdate(final List<Actor> actors) {
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
Actor actor = actors.get(i);
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
}

// ... additional methods
}

如果处理更新流或从文件读取,则可能具有 首选批大小,但最后一个批可能没有该数量的条目。在此 在这种情况下,您可以使用接口,它让 输入源耗尽后,您将中断批处理。方法之法 允许您发出批处理结束的信号。​​InterruptibleBatchPreparedStatementSetter​​​​isBatchExhausted​

3.5.2. 使用对象列表进行批处理操作

两者和提供了另一种方式 提供批量更新。而不是实现特殊的批处理接口,你 以列表形式提供调用中的所有参数值。框架循环这些 值并使用内部预准备语句资源库。API 因 是否使用命名参数。对于命名参数,请为批处理的每个成员提供一个条目数组。您可以使用方便的方法创建此数组,通过 在 Bean 样式的对象数组(具有对应于参数的 getter 方法)、键实例(包含相应的参数作为值)或两者的混合中。​​JdbcTemplate​​​​NamedParameterJdbcTemplate​​​​SqlParameterSource​​​​SqlParameterSourceUtils.createBatch​​​​String​​​​Map​

以下示例显示了使用命名参数的批量更新:

public class JdbcActorDao implements ActorDao {

private NamedParameterTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int[] batchUpdate(List<Actor> actors) {
return this.namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
SqlParameterSourceUtils.createBatch(actors));
}

// ... additional methods
}

对于使用经典占位符的 SQL 语句,传入一个列表 包含具有更新值的对象数组。此对象数组必须有一个条目 对于 SQL 语句中的每个占位符,它们的顺序必须与它们相同 在 SQL 语句中定义。​​?​

以下示例与前面的示例相同,只是它使用 classic JDBC占位符:​​?​

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

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

public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(), actor.getLastName(), actor.getId()};
batch.add(values);
}
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
}

// ... additional methods
}

我们之前描述的所有批处理更新方法都返回数组 包含每个批处理条目的受影响行数。此计数由 JDBC 驱动程序。如果计数不可用,JDBC 驱动程序将返回值 of。​​int​​​​-2​

3.5.3. 多批次的批处理操作

前面的批处理更新示例处理的批处理太大,以至于您想要 将它们分成几个较小的批次。您可以使用这些方法执行此操作 前面提到通过对方法进行多次调用来提及,但现在有一个 更方便的方法。除了 SQL 语句之外,此方法还采用包含参数的 aof 对象,以及要为每个对象进行的更新数 批处理和 ato 设置参数的值 的预处理声明。框架循环访问提供的值并中断 将调用更新为指定大小的批处理。​​batchUpdate​​​​Collection​​​​ParameterizedPreparedStatementSetter​

以下示例显示了使用批大小 100 的批更新:

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

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

public int[][] batchUpdate(final Collection<Actor> actors) {
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors,
100,
(PreparedStatement ps, Actor actor) -> {
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
});
return updateCounts;
}

// ... additional methods
}

此调用的批处理更新方法返回一个数组数组,其中包含 每个批次的数组条目,其中包含每次更新的受影响行数数组。 *数组的长度指示运行的批次数,第二级数组的长度指示 数组的长度指示该批处理中的更新数。中的更新次数 每个批次应为所有批次提供的批大小(最后一个批次除外) 这可能更少),具体取决于提供的更新对象的总数。更新 每个更新语句的计数是 JDBC 驱动程序报告的计数。如果计数为 不可用,则 JDBC 驱动程序返回值 of。​​int​​​​-2​

3.6. 使用类简化 JDBC 操作​​SimpleJdbc​

Theand类提供了简化的配置 通过利用可通过 JDBC 驱动程序检索的数据库元数据。 这意味着您需要预先配置的更少,尽管您可以覆盖或关闭 元数据处理(如果您希望在代码中提供所有详细信息)。​​SimpleJdbcInsert​​​​SimpleJdbcCall​

3.6.1. 使用 插入数据​​SimpleJdbcInsert​

我们首先用最少的 配置选项。您应该在数据访问中实例化 图层的初始化方法。对于此示例,初始化方法是方法。您不需要对类进行子类化。相反 您可以使用该方法创建新实例并设置表名。 此类的配置方法遵循返回实例的样式 的,这使您可以链接所有配置方法。以下 示例仅使用一种配置方法(稍后我们将展示多种方法的示例):​​SimpleJdbcInsert​​​​SimpleJdbcInsert​​​​setDataSource​​​​SimpleJdbcInsert​​​​withTableName​​​​fluid​​​​SimpleJdbcInsert​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}

public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}

// ... additional methods
}

这里使用的方法采用普通作为其唯一参数。这 这里要注意的重要一点是,用于必须与列匹配的键 数据库中定义的表的名称。这是因为我们读取了元数据 以构造实际的插入语句。​​execute​​​​java.util.Map​​​​Map​

3.6.2. 使用 检索自动生成的密钥​​SimpleJdbcInsert​

下一个示例使用与前面示例相同的插入,但是,它不是传入 检索自动生成的键并将其设置在新对象上。当它创建时 除了指定表名外,它还指定名称 使用方法生成的键列。以下 列表显示了它的工作原理:​​id​​​​Actor​​​​SimpleJdbcInsert​​​​usingGeneratedKeyColumns​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

使用第二种方法运行插入时的主要区别在于,您不 将 theto 添加到,然后调用该方法。这将返回一个对象,您可以使用该对象创建数值类型的实例 在您的域类中使用。您不能依赖所有数据库来返回特定的 Java 类 here.is 可以依赖的基类。如果你有 多个自动生成的列或生成的值是非数字的,您可以 使用 a那从方法返回。​​id​​​​Map​​​​executeAndReturnKey​​​​java.lang.Number​​​​java.lang.Number​​​​KeyHolder​​​​executeAndReturnKeyHolder​

3.6.3. 为​​SimpleJdbcInsert​

您可以通过使用 themethod指定列名列表来限制插入的列,如以下示例所示:​​usingColumns​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

插入的执行与依赖元数据来确定相同 要使用的列。

3.6.4. 用于提供参数值​​SqlParameterSource​

使用 ato 提供参数值可以正常工作,但这不是最方便的 要使用的类。Spring 提供了几个接口的实现,你可以改用它们。第一个是, 这是一个非常方便的类,如果你有一个兼容JavaBean的类,其中包含 你的价值观。它使用相应的getter方法来提取参数 值。以下示例演示如何使用:​​Map​​​​SqlParameterSource​​​​BeanPropertySqlParameterSource​​​​BeanPropertySqlParameterSource​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

Another option is the that resembles a but provides a more convenient method that can be chained. The following example shows how to use it:​​MapSqlParameterSource​​​​Map​​​​addValue​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

如您所见,配置是相同的。只有执行代码必须更改为 使用这些备用输入类。

3.6.5. 调用存储过程​​SimpleJdbcCall​

该类使用数据库中的元数据来查找 和参数的名称,这样您就不必显式声明它们。您可以 如果您愿意这样做,或者如果您的参数(如 asor)没有自动映射到 Java 类,请声明参数。第一个例子 显示一个仅返回标量值的简单过程,该方法以格式返回标量值 来自 MySQL 数据库。示例过程读取指定的参与者条目,并以参数的形式返回 、 和列。 下面的清单显示了第一个示例:​​SimpleJdbcCall​​​​in​​​​out​​​​ARRAY​​​​STRUCT​​​​VARCHAR​​​​DATE​​​​first_name​​​​last_name​​​​birth_date​​​​out​

CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;

该参数包含您正在查找的参与者。参数返回从表中读取的数据。​​in_id​​​​id​​​​out​

您可以采用类似于声明的方式进行声明。你 应在数据访问的初始化方法中实例化和配置类 层。与类相比,您不需要创建子类 并且不需要声明可以在数据库元数据中查找的参数。 以下配置示例使用前面存储的 过程(除了 之外,唯一的配置选项是名称 存储过程):​​SimpleJdbcCall​​​​SimpleJdbcInsert​​​​StoredProcedure​​​​SimpleJdbcCall​​​​DataSource​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}

public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}

// ... additional methods
}

为执行调用而编写的代码涉及创建包含 IN 参数的代码。您必须与为输入值提供的名称匹配 与在存储过程中声明的参数名称。案件没有 匹配,因为您使用元数据来确定应如何引用数据库对象 在存储过程中。在存储过程的源中指定的内容不是 必须是它在数据库中的存储方式。某些数据库将名称转换为全部 大写,而其他人使用小写或按指定使用大小写。​​SqlParameterSource​

该方法采用 IN 参数并返回包含由存储过程中指定的名称键入的任何参数。在这种情况下,它们是,和。​​execute​​​​Map​​​​out​​​​out_first_name​​​​out_last_name​​​​out_birth_date​

该方法的最后一部分创建一个实例以用于返回 检索到的数据。同样,使用参数的名称很重要,因为它们 在存储过程中声明。此外,结果映射中存储的参数名称中的大小写与 数据库,可能因数据库而异。为了使您的代码更具可移植性,您应该 执行不区分大小写的查找或指示 Spring 使用 a。 要执行后者,您可以创建自己的属性并将属性设置为。然后,您可以将此自定义实例传递给 您的构造函数。以下示例显示了此配置:​​execute​​​​Actor​​​​out​​​​out​​​​out​​​​LinkedCaseInsensitiveMap​​​​JdbcTemplate​​​​setResultsMapCaseInsensitive​​​​true​​​​JdbcTemplate​​​​SimpleJdbcCall​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}

// ... additional methods
}

通过执行此操作,可以避免用于名称的大小写发生冲突 返回参数。​​out​

3.6.6. 显式声明用于​​SimpleJdbcCall​

在本章前面,我们描述了如何从元数据中推导参数,但您可以声明它们 如果您愿意,请明确。您可以通过创建和配置来实现 方法,它接受可变数量的对象 作为输入。有关如何定义 an 的详细信息,请参阅下一节。​​SimpleJdbcCall​​​​declareParameters​​​​SqlParameter​​​​SqlParameter​

您可以选择显式声明一个、部分或所有参数。参数 在未显式声明参数的情况下仍使用元数据。绕过所有 处理潜在参数的元数据查找,并仅使用声明的 参数,您可以调用该方法作为 声明。假设您为 数据库功能。在这种情况下,您调用以指定列表 要为给定签名包含的 IN 参数名称。​​withoutProcedureColumnMetaDataAccess​​​​useInParameterNames​

下面的示例演示一个完全声明的过程调用,并使用来自 前面的示例:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}

// ... additional methods
}

这两个示例的执行和最终结果是相同的。第二个示例指定所有 明确的详细信息,而不是依赖于元数据。

3.6.7. 如何定义​​SqlParameters​

为类和 RDBMS 操作定义参数 类(在将 JDBC 操作建模为 Java 对象中介绍)您可以使用或其子类之一。 为此,通常在构造函数中指定参数名称和 SQL 类型。SQL 类型 是使用常量指定的。在本章前面,我们看到了声明 类似于以下内容:​​SimpleJdbc​​​​SqlParameter​​​​java.sql.Types​

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

带有 thes 的第一行声明了一个 IN 参数。您可以使用 IN 参数 对于存储过程调用和查询,请使用 和 其 子类(在了解SqlQuery 中介绍)。​​SqlParameter​​​​SqlQuery​

第二行(带)声明要在 存储过程调用。还有参数 (为过程提供 IN 值并返回值的参数)。​​SqlOutParameter​​​​out​​​​SqlInOutParameter​​​​InOut​

对于 IN 参数,除了名称和 SQL 类型之外,还可以为 数值数据或自定义数据库类型的类型名称。对于参数,您可以 提供从 Acursor 返回的行的 ATO 句柄映射。另一个 选项是指定 an,提供定义的机会 自定义处理返回值。​​out​​​​RowMapper​​​​REF​​​​SqlReturnType​

3.6.8. 使用 调用存储函数​​SimpleJdbcCall​

调用存储函数的方式与调用存储过程的方式几乎相同,但 您提供函数名称而不是过程名称。使用该方法作为配置的一部分来指示要使 对函数的调用,并生成函数调用的相应字符串。一个 专用调用 () 用于运行函数,并且 将函数返回值作为指定类型的对象返回,这意味着您这样做 不必从结果映射中检索返回值。类似的便利方法 (命名)也可用于只有一个参数的存储过程。以下示例(对于 MySQL)基于名为 actor 全名的存储函数:​​withFunctionName​​​​executeFunction​​​​executeObject​​​​out​​​​get_actor_name​

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;

为了调用这个函数,我们再次在初始化方法中创建, 如以下示例所示:​​SimpleJdbcCall​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall funcGetActorName;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}

public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}

// ... additional methods
}

使用的方法返回包含来自 函数调用。​​executeFunction​​​​String​

3.6.9. 从​​ResultSet​​​​SimpleJdbcCall​

调用返回结果集的存储过程或函数有点棘手。一些 数据库在 JDBC 结果处理期间返回结果集,而其他数据库需要 显式注册特定类型的参数。两种方法都需要 用于循环结果集并处理返回的行的其他处理。跟 ,您可以使用该方法并声明要用于特定参数的实现。如果结果集是 在结果处理过程中返回,没有定义名称,因此返回 结果必须与声明实现的顺序匹配。指定的名称仍用于存储已处理的结果列表 在从语句返回的结果映射中。​​out​​​​SimpleJdbcCall​​​​returningResultSet​​​​RowMapper​​​​RowMapper​​​​execute​

下一个示例(对于 MySQL)使用一个不带 IN 参数并返回的存储过程 表中的所有行:​​t_actor​

CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

若要调用此过程,可以声明 。因为你想要的类 要映射遵循JavaBean规则,您可以使用由 传入要在方法中映射到的所需类。 以下示例演示如何执行此操作:​​RowMapper​​​​BeanPropertyRowMapper​​​​newInstance​

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadAllActors;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor.class));
}

public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
}

// ... additional methods
}

Thecall 在空中传递,因为此调用不采用任何参数。 然后从结果映射中检索参与者列表并返回给调用方。​​execute​​​​Map​

3.7. 将 JDBC 操作建模为 Java 对象

该包包含允许您访问的类 以更面向对象的方式提供数据库。例如,您可以运行查询 并将结果作为列表返回,该列表包含具有关系的业务对象 映射到业务对象属性的列数据。您还可以运行存储 过程并运行更新、删除和插入语句。​​org.springframework.jdbc.object​

3.7.1. 理解​​SqlQuery​

​SqlQuery​​是一个可重用的线程安全类,用于封装 SQL 查询。子 必须实现该方法以提供可以 每行创建一个对象 通过迭代获得的那个 创建 在查询执行期间。Theclass 很少直接使用,因为 子类为 将行映射到 Java 类。其他扩展的实现。​​newRowMapper(..)​​​​RowMapper​​​​ResultSet​​​​SqlQuery​​​​MappingSqlQuery​​​​SqlQuery​​​​MappingSqlQueryWithParameters​​​​UpdatableSqlQuery​

3.7.2. 使用​​MappingSqlQuery​

​MappingSqlQuery​​是一个可重用的查询,其中具体的子类必须实现 抽象方法,将提供的每一行转换为 指定类型的对象。以下示例显示了一个自定义查询,该查询映射了 来自与类实例的关系的数据:​​mapRow(..)​​​​ResultSet​​​​t_actor​​​​Actor​

public class ActorMappingQuery extends MappingSqlQuery<Actor> {

public ActorMappingQuery(DataSource ds) {
super(ds, "select id, first_name, last_name from t_actor where id = ?");
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}

@Override
protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}

类扩展参数化为类型。构造函数 对于此客户查询,将 AAS 作为唯一的参数。在此 构造函数,您可以使用 和 SQL 调用超类上的构造函数 应运行以检索此查询的行。此 SQL 用于 创建一个,以便它可能包含任何参数的占位符 在执行过程中传入。必须使用传入 an 的方法声明每个参数。取一个名字,以及 JDBC 类型 如中所定义。定义所有参数后,可以调用该方法,以便可以准备语句并在以后运行。此类是 编译后线程安全,所以,只要在DAO创建这些实例时 初始化后,它们可以保留为实例变量并重复使用。以下 示例演示如何定义这样的类:​​MappingSqlQuery​​​​Actor​​​​DataSource​​​​DataSource​​​​PreparedStatement​​​​declareParameter​​​​SqlParameter​​​​SqlParameter​​​​java.sql.Types​​​​compile()​

private ActorMappingQuery actorMappingQuery;

@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}

public Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}

前面示例中的方法检索客户,其中作为 仅参数。由于我们只希望返回一个对象,因此我们称之为便利 方法作为参数。如果我们有一个返回 对象列表并采用其他参数,我们将使用其中一个方法,该方法采用作为 varargs 传入的参数值数组。以下 示例显示了这样的方法:​​id​​​​findObject​​​​id​​​​execute​

public List<Actor> searchForActors(int age, String namePattern) {
List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
return actors;
}

3.7.3. 使用​​SqlUpdate​

该类封装了一个 SQL 更新。与查询一样,更新对象是 可重用,并且与 all类一样,更新可以具有参数并且 在 SQL 中定义。此类提供了许多类似于查询对象方法的方法。这个类是具体的。它可以是 子类化 — 例如,添加自定义更新方法。 但是,您不必对类进行子类化,因为它可以通过设置 SQL 和声明参数轻松参数化。 下面的示例创建一个名为的自定义更新方法:​​SqlUpdate​​​​RdbmsOperation​​​​update(..)​​​​execute(..)​​​​SqlUpdate​​​​SqlUpdate​​​​execute​

public class UpdateCreditRating extends SqlUpdate {

public UpdateCreditRating(DataSource ds) {
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
declareParameter(new SqlParameter("id", Types.NUMERIC));
compile();
}

/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
public int execute(int id, int rating) {
return update(rating, id);
}
}

3.7.4. 使用​​StoredProcedure​

Theclass 是 RDBMS 对象抽象的超类 存储过程。​​StoredProcedure​​​​abstract​

继承属性是 RDBMS 中存储过程的名称。​​sql​

要定义类的参数,您可以使用 anor one 其子类。必须在构造函数中指定参数名称和 SQL 类型, 如以下代码片段所示:​​StoredProcedure​​​​SqlParameter​

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

SQL 类型是使用常量指定的。​​java.sql.Types​

第一行(带)声明一个 IN 参数。您可以使用 IN 参数 对于存储过程调用和使用 和 其 的查询 子类(在了解SqlQuery 中介绍)。​​SqlParameter​​​​SqlQuery​

第二行(带有)声明要在 存储过程调用。还有参数 (为过程提供值并返回值的参数)。​​SqlOutParameter​​​​out​​​​SqlInOutParameter​​​​InOut​​​​in​

对于参数,除了名称和 SQL 类型之外,您还可以指定 数值数据的小数位数或自定义数据库类型的类型名称。对于参数, 您可以提供从 Acursor 返回的行的 ATTO 句柄映射。 另一个选项是指定允许您定义自定义的 返回值的处理。​​in​​​​out​​​​RowMapper​​​​REF​​​​SqlReturnType​

简单 DAO 的下一个示例使用 ato 调用函数 (),它随任何 Oracle 数据库一起提供。使用存储过程 功能,您必须创建一个扩展的类。在此 例如,类是一个内部类。但是,如果需要重用,可以将其声明为*类。此示例没有输入 参数,但输出参数是使用类声明为日期类型的。该方法运行该过程并提取 从结果返回日期。结果对于每个声明都有一个条目 输出参数(在本例中为只有一个),方法是使用参数名称作为键。 下面的清单显示了我们的自定义存储过程类:​​StoredProcedure​​​​sysdate()​​​​StoredProcedure​​​​StoredProcedure​​​​StoredProcedure​​​​SqlOutParameter​​​​execute()​​​​Map​​​​Map​

public class StoredProcedureDao {

private GetSysdateProcedure getSysdate;

@Autowired
public void init(DataSource dataSource) {
this.getSysdate = new GetSysdateProcedure(dataSource);
}

public Date getSysdate() {
return getSysdate.execute();
}

private class GetSysdateProcedure extends StoredProcedure {

private static final String SQL = "sysdate";

public GetSysdateProcedure(DataSource dataSource) {
setDataSource(dataSource);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}

public Date execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
Map<String, Object> results = execute(new HashMap<String, Object>());
Date sysdate = (Date) results.get("date");
return sysdate;
}
}

}

以下示例 a有两个输出参数(在本例中为 Oracle REF 游标):​​StoredProcedure​

public class TitlesAndGenresStoredProcedure extends StoredProcedure {

private static final String SPROC_NAME = "AllTitlesAndGenres";

public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}

public Map<String, Object> execute() {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(new HashMap<String, Object>());
}
}

请注意该方法的重载变体是如何 在构造函数中使用的是传递的实现实例。这是一种非常方便和强大的重用现有方式 功能性。接下来的两个示例提供了这两个实现的代码。​​declareParameter(..)​​​​TitlesAndGenresStoredProcedure​​​​RowMapper​​​​RowMapper​

该类将 ato 中每一行的域对象映射到 提供的,如下:​​TitleMapper​​​​ResultSet​​​​Title​​​​ResultSet​

public final class TitleMapper implements RowMapper<Title> {

public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}

该类将 ato 中每一行的域对象映射到 提供的,如下:​​GenreMapper​​​​ResultSet​​​​Genre​​​​ResultSet​

public final class GenreMapper implements RowMapper<Genre> {

public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}

将参数传递给具有一个或多个输入参数的存储过程 定义,您可以编写一个强类型方法,该方法将 委托给超类中的 untyped方法,如以下示例所示:​​execute(..)​​​​execute(Map)​

public class TitlesAfterDateStoredProcedure extends StoredProcedure {

private static final String SPROC_NAME = "TitlesAfterDate";
private static final String CUTOFF_DATE_PARAM = "cutoffDate";

public TitlesAfterDateStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
compile();
}

public Map<String, Object> execute(Date cutoffDate) {
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}

3.8. 参数和数据值处理的常见问题

不同方法中存在参数和数据值的常见问题 由Spring Framework的JDBC支持提供。本节介绍如何解决这些问题。

3.8.1. 为参数提供 SQL 类型信息

通常,Spring 会根据参数的类型来确定参数的 SQL 类型 传入。可以显式提供设置时要使用的 SQL 类型 参数值。这有时是正确设置值所必需的。​​NULL​

您可以通过多种方式提供 SQL 类型信息:

  • 许多更新和查询方法在 安阵列的形式。此数组用于指示 通过使用类中的常量值进行相应的参数。提供 每个参数一个条目。JdbcTemplateintjava.sql.Types
  • 您可以使用类来包装需要此参数的参数值 其他信息。为此,请为每个值创建一个新实例并传入 SQL 类型 以及构造函数中的参数值。您还可以提供可选的秤 数值参数。SqlParameterValue
  • 对于使用命名参数的方法,可以使用类,或。他们都有方法 用于为任何命名参数值注册 SQL 类型。SqlParameterSourceBeanPropertySqlParameterSourceMapSqlParameterSource

3.8.2. 处理 BLOB 和 CLOB 对象

您可以在数据库中存储图像、其他二进制数据和大块文本。这些 大型对象称为二进制数据的 BLOB(二进制大型 OBject)和 CLOB(字符) 大 OBject) 用于字符数据。在 Spring 中,您可以使用 直接使用RDBMS提供的更高抽象时 对象和类。所有这些方法都使用 用于实际管理 LOB(大型 OBject)数据的接口。提供对 aclass 的访问,通过方法, 用于创建要插入的新 LOB 对象。​​JdbcTemplate​​​​SimpleJdbc​​​​LobHandler​​​​LobHandler​​​​LobCreator​​​​getLobCreator​

​LobCreator​​并为 LOB 输入和输出提供以下支持:​​LobHandler​

  • 斑点
  • ​byte[]​​:和getBlobAsBytessetBlobAsBytes
  • ​InputStream​​:和getBlobAsBinaryStreamsetBlobAsBinaryStream
  • 克洛布
  • ​String​​:和getClobAsStringsetClobAsString
  • ​InputStream​​:和getClobAsAsciiStreamsetClobAsAsciiStream
  • ​Reader​​:和getClobAsCharacterStreamsetClobAsCharacterStream

下一个示例演示如何创建和插入 BLOB。稍后我们将展示如何阅读 它从数据库中返回。

此示例使用 aand 的实现。它实现了一种方法,。此方法提供了我们用来设置 SQL 插入语句中的 LOB 列。​​JdbcTemplate​​​​AbstractLobCreatingPreparedStatementCallback​​​​setValues​​​​LobCreator​

对于此示例,我们假设有一个变量,它已经 设置为 A 的实例。通常通过以下方式设置此值 依赖注入。​​lobHandler​​​​DefaultLobHandler​

以下示例演示如何创建和插入 BLOB:

final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);

jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
ps.setLong(1, 1L);
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length());
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length());
}
}
);

blobIs.close();
clobReader.close();

传入那个(在本例中)是一个普通。​​lobHandler​​​​DefaultLobHandler​

使用该方法传入 CLOB 的内容。​​setClobAsCharacterStream​

使用该方法传入 BLOB 的内容。​​setBlobAsBinaryStream​

现在是时候从数据库中读取 LOB 数据了。同样,您将 a 与相同的实例变量和对 a 的引用一起使用。 以下示例演示如何执行此操作:​​JdbcTemplate​​​​lobHandler​​​​DefaultLobHandler​

List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
new RowMapper<Map<String, Object>>() {
public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
Map<String, Object> results = new HashMap<String, Object>();
String clobText = lobHandler.getClobAsString(rs, "a_clob");
results.put("CLOB", clobText);
byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");
results.put("BLOB", blobBytes);
return results;
}
});

使用该方法检索 CLOB 的内容。​​getClobAsString​

使用该方法检索 BLOB 的内容。​​getBlobAsBytes​

3.8.3. 传入 IN 子句的值列表

SQL 标准允许根据包含 变量值列表。一个典型的例子是。预准备语句不直接支持此变量列表 JDBC 标准。不能声明可变数量的占位符。你需要一个号码 准备了所需占位符数量的变体,或者您需要生成 知道需要多少占位符后动态 SQL 字符串。命名的 在 TheAndTakes 中提供的参数支持 后一种方法。您可以将值作为 aof 基元对象传入。这 list 用于插入所需的占位符并在期间传入值 语句执行。​​select * from T_ACTOR where id in (1, 2, 3)​​​​NamedParameterJdbcTemplate​​​​JdbcTemplate​​​​java.util.List​

除了值列表中的基元值之外,您还可以创建 aof 对象数组。此列表可以支持为子句定义的多个表达式,例如。当然,这要求数据库支持此语法。​​java.util.List​​​​in​​​​select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))​

3.8.4. 处理存储过程调用的复杂类型

调用存储过程时,有时可以使用特定于 数据库。为了适应这些类型,Spring 提供了用于处理的 它们从存储过程调用返回时以及当它们 作为参数传递给存储过程。​​SqlReturnType​​​​SqlTypeValue​

接口具有一个方法(命名),该方法必须 实现。此接口用作 的声明的一部分。 以下示例演示如何返回用户的 Oracle对象的值 声明类型:​​SqlReturnType​​​​getTypeValue​​​​SqlOutParameter​​​​STRUCT​​​​ITEM_TYPE​

public class TestItemStoredProcedure extends StoredProcedure {

public TestItemStoredProcedure(DataSource dataSource) {
// ...
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
STRUCT struct = (STRUCT) cs.getObject(colIndx);
Object[] attr = struct.getAttributes();
TestItem item = new TestItem();
item.setId(((Number) attr[0]).longValue());
item.setDescription((String) attr[1]);
item.setExpirationDate((java.util.Date) attr[2]);
return item;
}));
// ...
}

您可以使用将 Java 对象的值(例如)传递给 存储过程。该接口具有必须实现的单个方法(命名)。活动连接传入,并且您 可以使用它来创建特定于数据库的对象,例如实例 或者。以下示例创建一个实例:​​SqlTypeValue​​​​TestItem​​​​SqlTypeValue​​​​createTypeValue​​​​StructDescriptor​​​​ArrayDescriptor​​​​StructDescriptor​

final TestItem testItem = new TestItem(123L, "A test item",
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));

SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
Struct item = new STRUCT(itemDescriptor, conn,
new Object[] {
testItem.getId(),
testItem.getDescription(),
new java.sql.Date(testItem.getExpirationDate().getTime())
});
return item;
}
};

您现在可以将其添加到包含存储过程调用的输入参数。​​SqlTypeValue​​​​Map​​​​execute​

的另一个用途是将值数组传递给存储的 Oracle 程序。在这种情况下,Oracle 有自己的内部类,并且 您可以使用 创建 Oracle 的实例并填充 它与来自 Java 的值,如以下示例所示:​​SqlTypeValue​​​​ARRAY​​​​SqlTypeValue​​​​ARRAY​​​​ARRAY​

final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
return idArray;
}
};

3.9. 嵌入式数据库支持

该软件包提供对嵌入式的支持 Java 数据库引擎。提供对HSQL、H2 和Derby的支持 本地。您还可以使用可扩展的 API 插入新的嵌入式数据库类型和实现。​​org.springframework.jdbc.datasource.embedded​​​​DataSource​

3.9.1. 为什么要使用嵌入式数据库?

嵌入式数据库在项目的开发阶段非常有用,因为它 轻量级性质。优点包括易于配置、快速启动、 可测试性,以及在开发过程中快速发展 SQL 的能力。

3.9.2. 使用 Spring XML 创建嵌入式数据库

如果要在 Spring 中将嵌入式数据库实例公开为 bean,可以在命名空间中使用 thetag:​​ApplicationContext​​​​embedded-database​​​​spring-jdbc​

<jdbc:embedded-database  generate-name="true">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>

上述配置创建一个嵌入式 HSQL 数据库,该数据库填充了来自 类路径根目录中的 THE AND 资源。此外,作为 最佳做法是,为嵌入式数据库分配一个唯一生成的名称。这 嵌入式数据库作为类型的 Bean 提供给 Spring 容器,然后可以根据需要注入到数据访问对象中。​​schema.sql​​​​test-data.sql​​​​javax.sql.DataSource​

3.9.3. 以编程方式创建嵌入式数据库

该类提供了一个流畅的 API 来构造嵌入式 以编程方式提供数据库。当您需要在 独立环境或在独立集成测试中,如以下示例所示:​​EmbeddedDatabaseBuilder​

EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()

请参阅EmbeddedDataaseBuilder的 javadoc以获取有关所有受支持选项的更多详细信息。

您还可以使用 Java 创建嵌入式数据库 配置,如以下示例所示:​​EmbeddedDatabaseBuilder​

@Configuration
public class DataSourceConfig {

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
}
}

3.9.4. 选择嵌入式数据库类型

本节介绍如何选择Spring 的三个嵌入式数据库之一 支持。它包括以下主题:

  • 使用 HSQL
  • 使用 H2
  • 使用德比
使用 HSQL

Spring 支持 HSQL 1.8.0 及更高版本。HSQL 是默认的嵌入式数据库(如果没有类型是 显式指定。若要显式指定 HSQL,请将标记的属性设置为。如果使用构建器 API,请调用该方法。​​type​​​​embedded-database​​​​HSQL​​​​setType(EmbeddedDatabaseType)​​​​EmbeddedDatabaseType.HSQL​

使用 H2

Spring 支持 H2 数据库。要启用 H2,请将标签的属性设置为 。如果使用构建器 API,请调用该方法。​​type​​​​embedded-database​​​​H2​​​​setType(EmbeddedDatabaseType)​​​​EmbeddedDatabaseType.H2​

使用德比

Spring 支持 Apache Derby 10.5 及更高版本。若要启用 Derby,请将标记的属性设置为 。如果您使用构建器 API, 调用方法。​​type​​​​embedded-database​​​​DERBY​​​​setType(EmbeddedDatabaseType)​​​​EmbeddedDatabaseType.DERBY​

3.9.5. 使用嵌入式数据库测试数据访问逻辑

嵌入式数据库提供了一种测试数据访问代码的轻量级方法。下一个示例是 使用嵌入式数据库的数据访问集成测试模板。使用此类模板 当嵌入式数据库不需要在测试中重用时,对于一次性数据库很有用 类。但是,如果要创建在测试套件*享的嵌入式数据库, 考虑使用Spring TestContext 框架和 在 Springas 中将嵌入式数据库配置为 Bean,描述 使用 Spring XML创建嵌入式数据库并以编程方式创建嵌入式数据库。以下列表 显示测试模板:​​ApplicationContext​

public class DataAccessIntegrationTestTemplate {

private EmbeddedDatabase db;

@BeforeEach
public void setUp() {
// creates an HSQL in-memory database populated from default scripts
// classpath:schema.sql and classpath:data.sql
db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.addDefaultScripts()
.build();
}

@Test
public void testDataAccess() {
JdbcTemplate template = new JdbcTemplate(db);
template.query( /* ... */ );
}

@AfterEach
public void tearDown() {
db.shutdown();
}

}

3.9.6. 为嵌入式数据库生成唯一名称

开发团队经常遇到嵌入式数据库错误,如果他们的测试套件 无意中尝试重新创建同一数据库的其他实例。这可以 如果 XML 配置文件或类负责,则很容易发生 用于创建嵌入式数据库,然后重用相应的配置 跨同一测试套件中的多个测试场景(即在同一 JVM 内 process) — 例如,针对嵌入式数据库的集成测试,其配置仅在哪个 Bean 定义方面有所不同 配置文件处于活动状态。​​@Configuration​​​​ApplicationContext​

此类错误的根本原因是 Spring 的(使用 内部由 XML 命名空间元素和 for Java 配置)将嵌入式数据库的名称设置为 if 未另行指定。对于这种情况, 嵌入式数据库通常被分配一个与 Bean 的名称相等(通常, 类似的东西)。因此,随后尝试创建嵌入式数据库 不生成新数据库。相反,将重用相同的 JDBC 连接 URL, 并且尝试创建新的嵌入式数据库实际上指向现有的 从同一配置创建的嵌入式数据库。​​EmbeddedDatabaseFactory​​​​<jdbc:embedded-database>​​​​EmbeddedDatabaseBuilder​​​​testdb​​​​<jdbc:embedded-database>​​​​id​​​​dataSource​

为了解决这个常见问题,Spring Framework 4.2 提供了对生成 嵌入式数据库的唯一名称。要启用生成的名称,请使用以下名称之一 以下选项。

  • ​EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()​
  • ​EmbeddedDatabaseBuilder.generateUniqueName()​
  • ​<jdbc:embedded-database generate-name="true" … >​

3.9.7. 扩展嵌入式数据库支持

您可以通过两种方式扩展 Spring JDBC 嵌入式数据库支持:

  • 实现以支持新的嵌入式数据库类型。EmbeddedDatabaseConfigurer
  • 实现以支持新的实现,例如 用于管理嵌入式数据库连接的连接池。DataSourceFactoryDataSource

我们鼓励您在​​GitHub Issues​​ 上为 Spring 社区贡献扩展。

3.10. 初始化​​DataSource​

该包提供对初始化的支持 一个现有的。嵌入式数据库支持提供了一个用于创建的选项 并初始化应用程序。但是,有时可能需要初始化 在某处服务器上运行的实例。​​org.springframework.jdbc.datasource.init​​​​DataSource​​​​DataSource​

3.10.1. 使用 Spring XML 初始化数据库

如果要初始化数据库并且可以提供对 abean 的引用,则可以在命名空间中使用 thetag:​​DataSource​​​​initialize-database​​​​spring-jdbc​

<jdbc:initialize-database >
<jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
<jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
</jdbc:initialize-database>

前面的示例针对数据库运行两个指定的脚本。第一个 脚本创建一个架构,第二个架构使用测试数据集填充表。脚本 位置也可以是带有通配符的模式,通常用于资源的 Ant 样式 在春季(例如,)。如果您使用 模式中,脚本按其 URL 或文件名的词法顺序运行。​​classpath*:/com/foo/**/sql/*-data.sql​

数据库初始值设定项的默认行为是无条件运行提供的 脚本。这可能并不总是您想要的 - 例如,如果您运行 针对已包含测试数据的数据库的脚本。可能性 通过遵循常见模式(如前所示)来减少意外删除数据的情况 首先创建表,然后插入数据。第一步失败,如果 表已存在。

但是,为了更好地控制现有数据的创建和删除,XML 命名空间提供了一些其他选项。第一个是用于切换的标志 初始化打开和关闭。您可以根据环境进行设置(例如拉取 来自系统属性或环境 Bean 的布尔值)。下面的示例从系统属性中获取值:

<jdbc:initialize-database 
enabled="#{systemProperties.INITIALIZE_DATABASE}">
<jdbc:script location="..."/>
</jdbc:initialize-database>

从调用的系统属性中获取值。​​enabled​​​​INITIALIZE_DATABASE​

控制现有数据所发生情况的第二种选择是更加宽容 失败。为此,您可以控制初始值设定项忽略某些 它从脚本运行的 SQL 中的错误,如以下示例所示:

<jdbc:initialize-database  ignore-failures="DROPS">
<jdbc:script location="..."/>
</jdbc:initialize-database>

在前面的示例中,我们说我们期望有时运行脚本 针对空数据库,脚本中有一些语句 因此,会失败。所以失败的 SQL 语句将被忽略,但其他失败 将导致异常。如果您的 SQL 方言不支持(或类似),但您希望无条件删除之前的所有测试数据,这将非常有用 重新创建它。在这种情况下,第一个脚本通常是一组语句, 后跟一组语句。​​DROP​​​​DROP​​​​DROP … IF EXISTS​​​​DROP​​​​CREATE​

该选项可以设置为(默认),(忽略失败) 删除),或(忽略所有故障)。​​ignore-failures​​​​NONE​​​​DROPS​​​​ALL​

每个语句应该用新行分隔,如果不是 完全存在于脚本中。您可以全局控制它或逐个脚本进行控制,如 以下示例显示:​​;​​​​;​

<jdbc:initialize-database  separator="@@"> 
<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/>
<jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
<jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
</jdbc:initialize-database>

将分隔符脚本设置为。​​@@​

设置分隔符。​​db-schema.sql​​​​;​

在此示例中,twoscripts 使用语句分隔符,并且仅 theuses。此配置指定默认分隔符 Isand 将覆盖脚本的默认值。​​test-data​​​​@@​​​​db-schema.sql​​​​;​​​​@@​​​​db-schema​

如果您需要比从 XML 命名空间获得的更多的控制,则可以直接使用并将其定义为应用程序中的组件。​​DataSourceInitializer​

初始化依赖于数据库的其他组件

一大类应用程序(那些在 Spring 上下文之前不使用数据库的应用程序 已启动)可以使用数据库初始值设定项,无需进一步操作 并发症。如果您的应用程序不是其中之一,则可能需要阅读其余部分 的本节。

数据库初始值设定项依赖于实例并运行脚本 在其初始化回调中提供(类似于 XML Bean 中的 anin 定义、组件中的方法或实现的组件中的方法)。如果其他豆类依赖于 相同的数据源并在初始化回调中使用数据源,那里 可能是个问题,因为数据尚未初始化。一个常见的例子 这是一个急切初始化并从应用程序上的数据库加载数据的缓存 启动。​​DataSource​​​​init-method​​​​@PostConstruct​​​​afterPropertiesSet()​​​​InitializingBean​

要解决此问题,您有两种选择: 更改缓存初始化策略 到稍后阶段,或确保首先初始化数据库初始值设定项。

如果应用程序由您控制,则更改缓存初始化策略可能很容易,否则就不然。 有关如何实现此目的的一些建议包括:

  • 使缓存在首次使用时延迟初始化,从而改善应用程序启动 时间。
  • 具有缓存或初始化缓存实现器的单独组件。当应用程序上下文启动时,您可以 通过设置其标志自动启动,您可以 手动启动 Aby 调用封闭上下文。LifecycleSmartLifecycleSmartLifecycleautoStartupLifecycleConfigurableApplicationContext.start()
  • 使用 Springor 类似自定义观察器机制来触发 缓存 initialization.is 始终由上下文发布,当 它就可以使用了(在所有 bean 都初始化之后),所以这通常是一个有用的 钩子(这是默认情况下的工作方式)。ApplicationEventContextRefreshedEventSmartLifecycle

确保首先初始化数据库初始值设定项也很容易。关于如何实现这一点的一些建议包括:

  • 依靠 Spring 的默认行为,即 bean 是 按注册顺序初始化。您可以通过采用通用的 在 XML 配置中练习一组元素,这些元素对 应用程序模块并确保数据库和数据库初始化 列在最前面。BeanFactory<import/>
  • 分离使用它的业务组件并控制其 通过将它们放在单独的实例中来启动顺序(例如, 父上下文包含 ,子上下文包含业务 组件)。这种结构在Spring Web应用程序中很常见,但可以更多 一般适用。DataSourceApplicationContextDataSource