2

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

 1 year ago
source link: https://blog.51cto.com/u_15326439/5860759
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Spring数据访问和数据访问层与业务或服务层之间的交互(二)_sql

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数据访问和数据访问层与业务或服务层之间的交互(二)_层次结构_02

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 - 谁做什么?

定义连接参数。

打开连接。

指定 SQL 语句。

声明参数并提供参数值

准备并运行语句。

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

完成每次迭代的工作。

处理任何异常。

处理事务。

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

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 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​​​​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 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 的类和你的各种 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 id="dataSource" 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 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"/>

以下示例显示了 C3P0 配置:

<bean id="dataSource" 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 id="dataSource" 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
使用 HSQL

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

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 data-source="dataSource">
<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 data-source="dataSource"
enabled="#{systemProperties.INITIALIZE_DATABASE}">
<jdbc:script location="..."/>
</jdbc:initialize-database>

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

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

<jdbc:initialize-database data-source="dataSource" 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 data-source="dataSource" 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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK