数据访问
1. 交易管理
全面的事务支持是使用 Spring 的最令人信服的理由之一 框架。Spring 框架为事务提供了一致的抽象 管理带来以下好处:
以下部分描述了 Spring Framework 的事务特性和 技术:
-
Spring 框架事务支持的优势 model 描述了为什么要使用 Spring Framework 的事务抽象 而不是 EJB 容器管理事务 (CMT) 或选择驱动本地 通过专有 API(例如 Hibernate)进行交易。
-
了解 Spring Framework 事务抽象概述了核心类,并描述了如何配置和获取
DataSource
来自各种来源的实例。 -
将资源与事务同步描述 应用程序代码如何确保创建、重用和清理资源 适当地。
-
声明式事务管理描述了对 声明式事务管理。
-
程序化事务管理涵盖对 编程(即显式编码)事务管理。
-
事务绑定事件描述了如何使用应用程序 事务中的事件。
1.1. Spring 框架事务支持模型的优势
传统上,Java EE 开发人员在事务管理方面有两种选择: 全局或本地交易,两者都有深刻的局限性。全球 在接下来的两节中回顾了本地事务管理,然后是 讨论 Spring Framework 的事务管理支持如何解决 全球和本地交易模式的局限性。
1.1.1. 全局事务
全局事务允许您使用多个事务资源,通常
关系数据库和消息队列。应用程序服务器管理全局
交易,这是一个繁琐的 API(部分原因是其
异常模型)。此外,JTAUserTransaction
通常需要来源于
JNDI,这意味着您还需要使用 JNDI 才能使用 JTA。用途
的全局事务限制了应用程序代码的任何潜在重用,就像 JTA 一样
通常仅在应用程序服务器环境中可用。
以前,使用全局事务的首选方式是通过 EJB CMT (容器管理事务)。CMT 是一种声明性事务形式 管理(与程序化事务管理不同)。EJB CMT 消除了与事务相关的 JNDI 查找的需要,尽管使用 EJB 本身就需要使用 JNDI。它消除了大部分但不是全部编写的需要 Java 代码来控制事务。显着的缺点是 CMT 与 JTA 挂钩 以及应用服务器环境。此外,它仅在选择时可用 在 EJB 中实现业务逻辑(或至少在事务性 EJB 外观后面)。这 一般来说,EJB 的负面因素如此之大,以至于这不是一个有吸引力的提议, 尤其是面对令人信服的声明式事务管理替代方案。
1.1.2. 本地事务
本地事务是特定于资源的,例如与 JDBC 关联的事务 连接。本地事务可能更易于使用,但有一个明显的缺点: 它们不能跨多个事务资源工作。例如,管理 使用 JDBC 连接的事务不能在全局 JTA 事务中运行。因为 应用服务器不参与事务管理,它无法帮助确保 跨多个资源的正确性。(值得注意的是,大多数应用程序都使用 单个事务资源。另一个缺点是本地交易具有侵入性 到编程模型。
1.1.3. Spring 框架的一致编程模型
Spring 解决了全局和本地事务的缺点。它让 应用程序开发人员在任何环境中都使用一致的编程模型。 你写一次代码,它可以从不同的事务管理中受益 不同环境下的策略。Spring 框架提供了声明式和 程序化事务管理。大多数用户更喜欢声明式事务 管理,我们在大多数情况下建议这样做。
通过编程事务管理,开发人员可以使用 Spring Framework 事务抽象,可以在任何底层事务基础设施上运行。 使用首选的声明式模型,开发人员通常很少或不编写代码 与事务管理相关,因此不依赖于 Spring Framework transaction API 或任何其他事务 API。
1.2. 了解 Spring Framework 事务抽象
Spring 事务抽象的关键是事务策略的概念。一个
事务策略由TransactionManager
,具体来说org.springframework.transaction.PlatformTransactionManager
命令式接口
事务管理和org.springframework.transaction.ReactiveTransactionManager
响应式接口
事务管理。以下列表显示了PlatformTransactionManager
应用程序接口:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
这主要是一个服务提供商接口 (SPI),尽管您可以从应用程序代码中以编程方式使用它。因为PlatformTransactionManager
是一个接口,它可以很容易地被模拟或存根为
必要。它不与查找策略(如 JNDI)绑定。PlatformTransactionManager
实现的定义与任何其他对象(或 bean)一样
在 Spring Framework IoC 容器中。仅此一项好处就使 Spring Framework
事务是一个有价值的抽象,即使您使用 JTA 也是如此。您可以测试
事务代码比直接使用 JTA 要容易得多。
同样,为了与 Spring 的理念保持一致,该TransactionException
可以扔的
通过任何PlatformTransactionManager
接口的方法未选中(即
是,它扩展了java.lang.RuntimeException
类)。事务基础设施
失败几乎总是致命的。在极少数情况下,应用程序代码实际上可以
从事务失败中恢复,应用程序开发人员仍然可以选择捕获
和处理TransactionException
.突出的一点是,开发人员不是被迫这样做的。
这getTransaction(..)
方法返回一个TransactionStatus
对象,具体取决于TransactionDefinition
参数。返回的TransactionStatus
可能表示
新事务或可以表示现有事务(如果是匹配事务)
存在于当前调用堆栈中。后一种情况的含义是,与
Java EE 事务上下文,一个TransactionStatus
与
执行。
从 Spring Framework 5.2 开始,Spring 还提供了一个事务管理抽象
使用响应式类型或 Kotlin 协程的响应式应用程序。以下内容
listing 显示了由org.springframework.transaction.ReactiveTransactionManager
:
public interface ReactiveTransactionManager extends TransactionManager {
Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}
响应式事务管理器主要是一个服务提供商接口 (SPI),
尽管您可以从
应用程序代码。因为ReactiveTransactionManager
是一个接口,它可以轻松
必要时被嘲笑或砍伐。
这TransactionDefinition
接口指定:
-
传播:通常,事务范围内的所有代码都在 那笔交易。但是,如果出现以下情况,您可以指定行为 当事务上下文已存在时,将运行事务方法。为 例如,代码可以继续在现有事务中运行(常见情况),或者 可以暂停现有事务并创建新事务。Spring 提供了 EJB CMT 熟悉的所有事务传播选项。阅读 关于Spring中事务传播的语义,请参见事务传播。
-
隔离:此事务与其他事务的工作隔离的程度 交易。例如,此事务是否可以查看来自其他事务的未提交写入 交易?
-
超时:此事务在超时并自动回滚之前运行了多长时间 由底层交易基础设施。
-
只读状态:当代码读取但 不修改数据。只读事务在某些方面可能是一种有用的优化 情况,例如当您使用 Hibernate 时。
这些设置反映了标准事务概念。如有必要,请参阅资源 讨论事务隔离级别和其他核心事务概念。 理解这些概念对于使用 Spring Framework 或任何 事务管理解决方案。
这TransactionStatus
接口为事务代码提供了一种简单的方法
控制事务执行并查询事务状态。概念应该是
熟悉,因为它们是所有事务 API 通用的。以下列表显示了TransactionStatus
接口:
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
@Override
boolean isNewTransaction();
boolean hasSavepoint();
@Override
void setRollbackOnly();
@Override
boolean isRollbackOnly();
void flush();
@Override
boolean isCompleted();
}
无论您选择声明式还是编程式事务管理
弹簧,定义正确的TransactionManager
实施是绝对必要的。
通常通过依赖项注入来定义此实现。
TransactionManager
实现通常需要了解环境中的环境
它们工作:JDBC、JTA、Hibernate 等。以下示例演示了如何
定义本地PlatformTransactionManager
实现(在本例中,使用 plain
JDBC。
您可以定义 JDBCDataSource
通过创建一个类似于以下内容的 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>
相关PlatformTransactionManager
bean 定义则引用了DataSource
定义。 它应该类似于以下示例:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
如果您在 Java EE 容器中使用 JTA,则使用DataSource
获得 通过 JNDI 与 Spring 的JtaTransactionManager
. 以下示例显示了 JTA 和 JNDI 查找版本的外观:
<?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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
https://www.springframework.org/schema/jee/spring-jee.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<!-- other <bean/> definitions here -->
</beans>
这JtaTransactionManager
不需要知道DataSource
(或任何其他特定资源),因为它使用容器的全局事务管理 基础设施。
前面的定义dataSource bean 使用<jndi-lookup/> 标记 从jee Namespace。 有关详细信息,请参阅 JEE 架构。 |
如果您使用 JTA,则您的事务管理器定义应该看起来相同,无论您使用什么数据访问技术,无论是 JDBC、Hibernate JPA 还是任何其他受支持的技术 科技。 这是因为 JTA 事务是全局事务,它可以征用任何事务资源。 |
在所有 Spring 事务设置中,应用程序代码不需要更改。您可以更改 如何仅通过更改配置来管理事务,即使该更改意味着 从本地交易转移到全球交易,反之亦然。
1.2.1. Hibernate 事务设置
您还可以轻松使用 Hibernate 本地事务,如以下示例所示。
在这种情况下,您需要定义一个 HibernateLocalSessionFactoryBean
,您的
应用程序代码可用于获取 HibernateSession
实例。
这DataSource
bean 定义类似于前面显示的本地 JDBC 示例
因此,以下示例中未显示。
如果DataSource (由任何非 JTA 事务管理器使用)通过以下方式进行查找
JNDI 并由 Java EE 容器管理,它应该是非事务性的,因为
Spring Framework(而不是 Java EE 容器)管理事务。 |
这txManager
在这种情况下,bean 是HibernateTransactionManager
类型。在
与DataSourceTransactionManager
需要引用DataSource
这HibernateTransactionManager
需要引用SessionFactory
.以下内容
示例声明sessionFactory
和txManager
豆:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
如果您使用 Hibernate 和 Java EE 容器管理的 JTA 事务,则应使用
相同JtaTransactionManager
如前面的 JDBC JTA 示例,如下所示
示例显示。此外,建议通过其
事务协调器,可能还有它的连接释放模式配置:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
hibernate.transaction.coordinator_class=jta
hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
</value>
</property>
</bean>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
或者,您可以将JtaTransactionManager
进入您的LocalSessionFactoryBean
用于强制执行相同的默认值:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
<property name="jtaTransactionManager" ref="txManager"/>
</bean>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
1.3. 将资源与事务同步
如何创建不同的事务管理器以及如何将它们链接到相关资源
需要同步到事务(例如DataSourceTransactionManager
到 JDBCDataSource
,HibernateTransactionManager
到 HibernateSessionFactory
,
等等)现在应该很清楚了。本节介绍应用程序代码如何
(直接或间接地,通过使用 JDBC、Hibernate 或 JPA 等持久性 API)
确保正确创建、重用和清理这些资源。该部分
还讨论了如何(可选)通过
相关TransactionManager
.
1.3.1. 高级同步方法
首选方法是使用 Spring 最高级别的基于模板的持久化
集成 API 或将本机 ORM API 与事务感知工厂 Bean 一起使用,或
用于管理本机资源工厂的代理。这些事务感知解决方案
内部处理资源创建复用、清理、可选事务
资源同步和异常映射。因此,用户数据访问代码确实如此
不必处理这些任务,但可以纯粹专注于非样板
持久性逻辑。通常,您使用原生 ORM API 或采用模板方法
用于 JDBC 访问,使用JdbcTemplate
.这些解决方案将在后续
本参考文档的各个部分。
1.3.2. 低级同步方法
类,例如DataSourceUtils
(对于 JDBC),EntityManagerFactoryUtils
(对于JPA),SessionFactoryUtils
(对于 Hibernate),依此类推存在于较低级别。当您需要
应用程序代码直接处理本机持久化 API 的资源类型,
您使用这些类来确保获得正确的 Spring Framework 托管实例,
事务(可选)同步,进程中发生的异常是
正确映射到一致的 API。
例如,在 JDBC 的情况下,而不是传统的 JDBC 调用
这getConnection()
方法DataSource
,你可以改用 Spring 的org.springframework.jdbc.datasource.DataSourceUtils
类,如下所示:
Connection conn = DataSourceUtils.getConnection(dataSource);
如果现有事务已经有一个同步(链接)的连接,则返回该实例。否则,方法调用会触发创建一个新的connection,该连接(可选)同步到任何现有事务并使可在同一事务中进行后续重用。如前所述,任何SQLException
被包装在一个 Spring 框架中CannotGetJdbcConnectionException
一
Spring 框架的 unchecked 层次结构DataAccessException
类型。这种方法
为您提供的信息比从SQLException
和
确保跨数据库甚至跨不同持久化技术的可移植性。
这种方法也适用于没有 Spring 事务管理(事务 synchronization 是可选的),因此无论您是否使用 Spring 事务管理。
当然,一旦你使用了 Spring 的 JDBC 支持、JPA 支持或 Hibernate 支持,
您通常不喜欢使用DataSourceUtils
或其他辅助类,
因为你通过 Spring 抽象比直接工作更快乐
与相关 API 一起使用。例如,如果使用 SpringJdbcTemplate
或jdbc.object
包来简化您对 JDBC 的使用,从而进行正确的连接检索
在幕后,您无需编写任何特殊代码。
1.4. 声明式事务管理
大多数 Spring Framework 用户选择声明式事务管理。此选项具有 对应用程序代码的影响最小,因此最符合 非侵入式轻质容器。 |
Spring 框架的声明式事务管理可以通过 Spring 实现 面向方面的编程 (AOP)。但是,随着事务方面代码的出现 与 Spring Framework 发行版一起使用,并且可以以样板方式使用 AOP 通常不必理解概念即可有效使用此代码。
Spring 框架的声明式事务管理类似于 EJB CMT,因为它
您可以将事务行为(或缺乏事务行为)指定到单个方法级别。
您可以制作一个setRollbackOnly()
调用,如果
必要。两种类型的事务管理之间的区别是:
-
与与 JTA 绑定的 EJB CMT 不同,Spring Framework 的声明式事务 管理层在任何环境中工作。它可以处理 JTA 事务或本地 事务,通过调整配置来使用 JDBC、JPA 或 Hibernate 文件。
-
您可以将 Spring Framework 声明式事务管理应用于任何类, 不仅仅是特殊类,例如 EJB。
-
Spring Framework 提供了声明式回滚规则,该功能没有 EJB 等效。提供了对回滚规则的编程和声明性支持。
-
Spring 框架允许您使用 AOP 自定义事务行为。 例如,您可以在事务回滚的情况下插入自定义行为。你 还可以添加任意建议以及交易建议。使用 EJB CMT,您可以 不能影响容器的事务管理,除非
setRollbackOnly()
. -
Spring Framework 不支持跨 远程调用,就像高端应用程序服务器所做的那样。如果您需要此功能,我们 建议使用 EJB。但是,在使用此类功能之前请仔细考虑, 因为,通常情况下,人们不希望事务跨越远程调用。
回滚规则的概念很重要。它们允许您指定哪些异常
(和投掷物)应该会导致自动回滚。您可以在
配置,而不是在 Java 代码中。所以,虽然你仍然可以调用setRollbackOnly()
上
这TransactionStatus
object 回滚当前事务,最常见的是你
可以指定一个规则,该规则MyApplicationException
必须始终导致回滚。这
此选项的显着优点是业务对象不依赖于
交易基础设施。例如,它们通常不需要导入 Spring
transaction API 或其他 Spring API。
尽管 EJB 容器缺省行为会自动回滚
系统异常(通常是运行时异常),EJB CMT 不会回滚
应用程序异常(即检查异常
以外java.rmi.RemoteException
).虽然 Spring 的默认行为
声明式事务管理遵循 EJB 约定(回滚仅自动
在未检查的异常上),自定义此行为通常很有用。
1.4.1. 了解 Spring 框架的声明式事务实现
仅仅告诉您使用@Transactional
注释, 添加@EnableTransactionManagement
到您的配置,
并期望您了解这一切是如何运作的。为了提供更深入的理解,这个
部分解释了 Spring 框架声明式事务的内部工作原理
与交易相关的问题背景下的基础设施。
关于 Spring Framework 的声明式,需要掌握的最重要的概念
事务支持是通过 AOP 代理启用此支持,并且事务性
建议由元数据驱动(目前基于 XML 或注释)。AOP的组合
使用事务元数据生成一个 AOP 代理,该代理使用TransactionInterceptor
在
与适当的TransactionManager
推动交易的实施
围绕方法调用。
AOP 部分介绍了 Spring AOP。 |
Spring 框架的TransactionInterceptor
提供事务管理
命令式和响应式编程模型。拦截器检测所需的风味
通过检查方法返回类型来管理事务。返回响应式的方法
类型,例如Publisher
或 KotlinFlow
(或其中的一个子类型)符合反应性条件
事务管理。所有其他返回类型,包括void
将代码路径用于
命令式事务管理。
事务管理风格会影响需要哪个事务管理器。祈使的
事务需要PlatformTransactionManager
,而响应式事务使用ReactiveTransactionManager
实现。
由 |
下图显示了在事务代理上调用方法的概念视图:

1.4.2. 声明式事务实现示例
请考虑以下接口及其附属实现。此示例使用Foo
和Bar
类作为占位符,以便您可以专注于事务
使用而不关注特定领域模型。就本示例而言,
事实上,DefaultFooService
类投掷UnsupportedOperationException
每个实现方法的主体中的实例都是好的。该行为可让您看到
事务正在创建,然后回滚以响应UnsupportedOperationException
实例。以下列表显示了FooService
接口:
// the service interface that we want to make transactional
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
// the service interface that we want to make transactional
package x.y.service
interface FooService {
fun getFoo(fooName: String): Foo
fun getFoo(fooName: String, barName: String): Foo
fun insertFoo(foo: Foo)
fun updateFoo(foo: Foo)
}
以下示例显示了上述接口的实现:
package x.y.service;
public class DefaultFooService implements FooService {
@Override
public Foo getFoo(String fooName) {
// ...
}
@Override
public Foo getFoo(String fooName, String barName) {
// ...
}
@Override
public void insertFoo(Foo foo) {
// ...
}
@Override
public void updateFoo(Foo foo) {
// ...
}
}
package x.y.service
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
override fun getFoo(fooName: String, barName: String): Foo {
// ...
}
override fun insertFoo(foo: Foo) {
// ...
}
override fun updateFoo(foo: Foo) {
// ...
}
}
假设FooService
接口getFoo(String)
和getFoo(String, String)
,必须在具有只读的事务上下文中运行
语义学和其他方法insertFoo(Foo)
和updateFoo(Foo)
必须
在具有读写语义的事务上下文中运行。以下内容
接下来的几段将详细解释配置:
<!-- from the file 'context.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the TransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
检查前面的配置。它假设你想创建一个服务对象,
这fooService
豆,交易。要应用的事务语义被封装
在<tx:advice/>
定义。这<tx:advice/>
定义读作“所有方法
开头get
在只读事务的上下文中运行,并且所有
其他方法是使用默认事务语义运行“。这transaction-manager
属性的<tx:advice/>
标记设置为TransactionManager
bean 的 bean(在本例中为txManager
豆子)。
您可以省略transaction-manager 属性
(<tx:advice/> ) 如果 bean 名称的TransactionManager 你想
wire in 有名称transactionManager .如果TransactionManager bean 那
您要连接的有任何其他名称,则必须使用transaction-manager 属性显式,如前面的示例所示。 |
这<aop:config/>
定义确保由txAdvice
Bean 在程序中的适当点运行。首先,定义一个
与FooService
接口
(fooServiceOperation
).然后,将切入点与txAdvice
通过使用
顾问。结果表明,在执行fooServiceOperation
,
由txAdvice
运行。
在<aop:pointcut/>
元素是 AspectJ 切入点
表达。有关切入点的更多详细信息,请参阅AOP部分
Spring 中的表达式。
一个常见的要求是使整个服务层成为事务性的。最好的方法 这样做是为了更改切入点表达式以匹配 服务层。以下示例显示了如何执行此作:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
在前面的示例中,假设定义了所有服务接口
在x.y.service 包。有关更多详细信息,请参阅 AOP 部分。 |
现在我们已经分析了配置,你可能会问自己, “所有这些配置实际上有什么作用?”
前面显示的配置用于围绕对象创建事务代理
从fooService
bean 定义。代理配置了
事务性建议,以便在代理上调用适当的方法时,
事务是否启动、挂起、标记为只读等,具体取决于
与该方法关联的事务配置。考虑以下程序
该测试驱动前面显示的配置:
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
FooService fooService = ctx.getBean(FooService.class);
fooService.insertFoo(new Foo());
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("context.xml")
val fooService = ctx.getBean<FooService>("fooService")
fooService.insertFoo(Foo())
}
运行前面程序的输出应类似于以下内容(Log4J
输出和堆栈跟踪从UnsupportedOperationException
由insertFoo(..)
方法DefaultFooService
为清楚起见,类已被截断):
<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors
<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]
<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource
Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
要使用响应式事务管理,代码必须使用响应式类型。
Spring Framework 使用ReactiveAdapterRegistry 确定方法
返回类型为响应式。 |
以下列表显示了以前使用的FooService
但
这次代码使用响应式类型:
// the reactive service interface that we want to make transactional
package x.y.service;
public interface FooService {
Flux<Foo> getFoo(String fooName);
Publisher<Foo> getFoo(String fooName, String barName);
Mono<Void> insertFoo(Foo foo);
Mono<Void> updateFoo(Foo foo);
}
// the reactive service interface that we want to make transactional
package x.y.service
interface FooService {
fun getFoo(fooName: String): Flow<Foo>
fun getFoo(fooName: String, barName: String): Publisher<Foo>
fun insertFoo(foo: Foo) : Mono<Void>
fun updateFoo(foo: Foo) : Mono<Void>
}
以下示例显示了上述接口的实现:
package x.y.service;
public class DefaultFooService implements FooService {
@Override
public Flux<Foo> getFoo(String fooName) {
// ...
}
@Override
public Publisher<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
package x.y.service
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
命令式和响应式事务管理共享相同的事务语义
边界和事务属性定义。命令式之间的主要区别
反应易是后者的递延性质。TransactionInterceptor
使用事务运算符修饰返回的响应式类型以开始和清理
交易。因此,调用事务响应式方法会延迟实际的
事务管理设置为激活响应式处理的订阅类型
类型。
响应式事务管理的另一个方面与数据转义有关,即 编程模型的自然结果。
命令式事务的方法返回值是从事务性方法返回的 成功终止方法后,以便部分计算的结果不会逃逸 方法闭包。
响应式事务方法返回一个响应式包装器类型,该类型表示 计算序列以及开始和完成计算的承诺。
一个Publisher
可以在事务进行时发出数据,但不一定完成。
因此,依赖于成功完成整个事务的方法需要
以确保调用代码中的完成和缓冲结果。
1.4.3. 回滚声明性事务
上一节概述了如何为
类,通常是服务层类,以声明方式在应用程序中。本节
描述了如何以简单的声明性方式控制事务的回滚
XML 配置中的时尚。有关以声明方式控制回滚语义的详细信息
使用@Transactional
注释,请参阅@Transactional
设置.
向 Spring Framework 的事务基础设施指示的推荐方式
要回滚事务的工作是抛出一个Exception
从代码中
当前在事务的上下文中执行。Spring Framework 的
事务基础设施代码捕获任何未处理的Exception
当它冒泡时
调用堆栈,并确定是否将事务标记为回滚。
在其默认配置中,Spring Framework 的事务基础设施代码
仅在运行时未检查的异常情况下将事务标记为回滚。
也就是说,当抛出的异常是RuntimeException
.
(Error
默认情况下,实例也会导致回滚)。已检查的异常
从事务性方法抛出不会导致默认情况下的回滚
配置。
您可以准确配置哪个Exception
类型将事务标记为回滚,
通过指定回滚规则包括已检查的异常。
回滚规则
回滚规则确定当给定异常发生时是否应回滚事务
抛出,规则基于模式。模式可以是完全限定的类
name 或异常类型的完全限定类名的子字符串(必须是
的子类 回滚规则可以通过
|
以下 XML 代码片段演示了如何为已选中的
特定应用Exception
类型,通过rollback-for
属性:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
如果您不希望在抛出异常时回滚事务,您还可以
指定“无回滚”规则。以下示例告诉 Spring Framework 的
事务基础设施来提交伴随事务,即使面对
未处理InstrumentNotFoundException
:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
当 Spring Framework 的事务基础设施捕获异常并咨询
配置的回滚规则,用于确定是否将事务标记为回滚,
最强匹配规则获胜。因此,在以下配置的情况下,任何
除InstrumentNotFoundException
导致
伴随交易:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
您还可以以编程方式指示所需的回滚。虽然简单,但这个过程 具有相当的侵入性,并且将您的代码与 Spring Framework 的事务紧密耦合 基础设施。以下示例演示如何以编程方式指示必需的 反转:
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
fun resolvePosition() {
try {
// some business logic...
} catch (ex: NoProductInStockException) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
强烈建议您使用声明性方法进行回滚(如果有的话) 可能。如果您绝对需要,可以使用编程回滚,但它 使用与实现基于 POJO 的干净架构背道而驰。
1.4.4. 为不同的 Bean 配置不同的事务语义
考虑以下场景:您有多个服务层对象,并且您希望
对每个事务应用完全不同的事务配置。你可以这样做
通过定义 distinct<aop:advisor/>
具有不同pointcut
和advice-ref
属性值。
作为比较点,首先假设您的所有服务层类都是
在根中定义x.y.service
包。要使所有 bean 都是类的实例
在该包(或子包)中定义,并且名称以Service
有
默认事务配置,您可以编写以下内容:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* x.y.service..*Service.*(..))"/>
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>
<!-- these two beans will be transactional... -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
<!-- ... and these two beans won't -->
<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- other transaction infrastructure beans such as a TransactionManager omitted... -->
</beans>
以下示例显示如何配置两个具有完全不同的 Bean 事务设置:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="defaultServiceOperation"
expression="execution(* x.y.service.*Service.*(..))"/>
<aop:pointcut id="noTxServiceOperation"
expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
</aop:config>
<!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- this bean will also be transactional, but with totally different transactional settings -->
<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
<tx:advice id="defaultTxAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<tx:advice id="noTxAdvice">
<tx:attributes>
<tx:method name="*" propagation="NEVER"/>
</tx:attributes>
</tx:advice>
<!-- other transaction infrastructure beans such as a TransactionManager omitted... -->
</beans>
1.4.5. <tx:advice/>设置
本节总结了您可以使用以下命令指定的各种事务设置 这<tx:advice/>
标记。默认值<tx:advice/>
设置包括:
-
传播设置为
REQUIRED.
-
隔离级别为
DEFAULT.
-
事务是读写的。
-
事务超时默认为基础事务的默认超时 system 或 none,如果不支持超时。
-
任何
RuntimeException
触发回滚,并且任何选中的Exception
不。
您可以更改这些默认设置。下表总结了<tx:method/>
标签
嵌套在<tx:advice/>
和<tx:attributes/>
标签:
属性 | 必填? | 默认值 | 描述 |
---|---|---|---|
|
是的 |
要与事务属性关联的方法名称。这
通配符 (*) 字符可用于关联相同的事务属性
设置,其中包含多种方法(例如 |
|
|
不 |
|
事务传播行为。 |
|
不 |
|
事务隔离级别。仅适用于 |
|
不 |
-1 |
事务超时(秒)。仅适用于传播 |
|
不 |
false |
读写与只读事务。仅适用于 |
|
不 |
逗号分隔的列表 |
|
|
不 |
逗号分隔的列表 |
1.4.6. 使用@Transactional
除了基于 XML 的声明式事务配置方法外,您还可以使用基于注释的方法。直接在 Java 中声明事务语义源代码使声明更接近受影响的代码。没有太多过度耦合的危险,因为旨在以事务方式使用的代码是无论如何,几乎总是以这种方式部署。
标准javax.transaction.Transactional 注释也支持
插入替换到 Spring 自己的注释。请参阅 JTA 1.2 文档
了解更多详情。 |
使用@Transactional
注释是最好的
用一个例子来说明,在下面的文本中对此进行了解释。
考虑以下类定义:
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
@Override
public Foo getFoo(String fooName) {
// ...
}
@Override
public Foo getFoo(String fooName, String barName) {
// ...
}
@Override
public void insertFoo(Foo foo) {
// ...
}
@Override
public void updateFoo(Foo foo) {
// ...
}
}
// the service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
override fun getFoo(fooName: String, barName: String): Foo {
// ...
}
override fun insertFoo(foo: Foo) {
// ...
}
override fun updateFoo(foo: Foo) {
// ...
}
}
如上所述,在类级别使用,注释指示所有方法的默认值
声明类(及其子类)。或者,每种方法都可以是
单独注释。看方法可见性和@Transactional
为
有关 Spring 认为哪些方法是事务性的更多详细信息。请注意,类级
注释不适用于类层次结构上的祖先类;在这种情况下,
继承的方法需要在本地重新声明才能参与
子类级注释。
当像上面这样的 POJO 类被定义为 Spring 上下文中的 bean 时,
您可以通过@EnableTransactionManagement
注释@Configuration
类。有关完整详细信息,请参阅 javadoc。
在 XML 配置中,<tx:annotation-driven/>
标签提供了类似的便利:
<!-- from the file 'context.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- enable the configuration of transactional behavior based on annotations -->
<!-- a TransactionManager is still required -->
<tx:annotation-driven transaction-manager="txManager"/> (1)
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
1 | 使 Bean 实例事务性的行。 |
您可以省略transaction-manager 属性中的<tx:annotation-driven/> 标记,如果TransactionManager 您要连线的名称为transactionManager .如果TransactionManager 要依赖注入的 bean
有任何其他名称,则必须使用transaction-manager 属性,如
前面的示例。 |
响应式事务方法使用响应式返回类型,而不是命令式 节目安排如下表所示:
// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
@Override
public Publisher<Foo> getFoo(String fooName) {
// ...
}
@Override
public Mono<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Mono<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
请注意,返回的Publisher
关于
响应式流取消信号。请参阅下面的 Cancel Signals 部分
“使用 TransactionalOperator”了解更多详细信息。
方法可见性和
@Transactional 当您将事务代理与 Spring 的标准配置一起使用时,您应该将
这 使用时
Spring TestContext 框架支持非私有 |
您可以应用@Transactional
接口定义的注释, 方法
在接口、类定义或类上的方法上。然而,仅仅存在
的@Transactional
注释不足以激活事务行为。
这@Transactional
注释只是元数据,可以由相应的
运行时基础结构,它使用该元数据来配置适当的 bean
交易行为。在前面的示例中,<tx:annotation-driven/>
元素
在运行时打开实际事务管理。
Spring 团队建议您使用@Transactional 注释,而不是依赖接口中的注释方法,
即使后者确实适用于 5.0 起基于接口和目标类代理的代理。
由于 Java 注释不是从接口继承的,因此接口声明的注释
在使用 AspectJ 模式时仍然无法被编织基础设施识别,因此
aspect 不会被应用。因此,您的交易注释可能是
静默忽略:在测试回滚方案之前,您的代码可能看起来“有效”。 |
在代理模式(这是默认模式)中,只有通过
代理被拦截。这意味着自调用(实际上,是
目标对象调用目标对象的另一个方法)不会导致实际的
运行时的事务,即使调用的方法标记为@Transactional .也
代理必须完全初始化才能提供预期的行为,因此您不应该
在初始化代码中依赖此功能——例如,在@PostConstruct 方法。 |
考虑使用 AspectJ 模式(请参阅mode
属性)如果
期望自我调用也与事务包装。在这种情况下,有
首先没有代理。相反,目标类是编织的(即它的字节码
被修改)以支持@Transactional
任何类型方法的运行时行为。
XML 属性 | 注释属性 | 默认值 | 描述 |
---|---|---|---|
|
不适用(参见 |
|
要使用的事务管理器的名称。仅当事务名称
经理不是 |
|
|
|
默认模式 ( |
|
|
|
适用于 |
|
|
|
定义应用于用 |
处理的默认通知方式@Transactional 注释是proxy ,
它只允许通过代理拦截呼叫。内的本地呼叫
同一类不能以这种方式被拦截。对于更高级的拦截模式,
考虑切换到aspectj 模式与编译时或加载时编织相结合。 |
这proxy-target-class 属性控制事务代理的类型
为用@Transactional 注解。如果proxy-target-class 设置为true ,创建基于类的代理。如果proxy-target-class 是false 或者如果省略了该属性,则标准 JDK
创建基于接口的代理。(有关不同代理类型的讨论,请参阅代理机制。 |
@EnableTransactionManagement 和<tx:annotation-driven/> 查找@Transactional 仅在定义它们的同一应用程序上下文中的 bean 上。
这意味着,如果您将注释驱动的配置放在WebApplicationContext 对于一个DispatcherServlet ,它会检查@Transactional 仅在控制器中
而不是在您的服务中。有关详细信息,请参阅 MVC。 |
在评估事务设置时,派生最多的位置优先
对于一种方法。在以下示例中,DefaultFooService
class 是
在类级别使用只读事务的设置进行注释,但@Transactional
注释updateFoo(Foo)
method 接受同一类中的
优先于在类级别定义的事务设置。
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// ...
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// ...
}
}
@Transactional(readOnly = true)
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
override fun updateFoo(foo: Foo) {
// ...
}
}
@Transactional
设置
这@Transactional
注释是元数据,指定接口、类、
或方法必须具有事务语义(例如,“启动全新的只读
transaction 时,暂停任何现有事务“)。
默认值@Transactional
设置如下:
-
传播设置为
PROPAGATION_REQUIRED.
-
隔离级别为
ISOLATION_DEFAULT.
-
事务是读写的。
-
事务超时默认为基础事务的默认超时 系统,如果不支持超时,则设置为 none。
-
任何
RuntimeException
或Error
触发回滚,并且任何选中的Exception
确实 不。
您可以更改这些默认设置。下表总结了各种属性@Transactional
注解:
属性 | 类型 | 描述 |
---|---|---|
|
指定要使用的事务管理器的可选限定符。 |
|
|
|
别名 |
|
数组 |
事务管理器可以评估标签,以将特定于实现的行为与实际事务相关联。 |
|
可选的传播设置。 |
|
|
|
可选隔离级别。仅适用于 |
|
|
可选事务超时。仅适用于 |
|
|
指定 |
|
|
读写与只读事务。仅适用于 |
|
数组 |
必须导致回滚的异常类型的可选数组。 |
|
异常名称模式的数组。 |
必须导致回滚的异常名称模式的可选数组。 |
|
数组 |
不得导致回滚的异常类型的可选数组。 |
|
异常名称模式的数组。 |
不得导致回滚的异常名称模式的可选数组。 |
有关更多详细信息,请参阅回滚规则 关于回滚规则语义、模式和关于可能无意的警告 比赛。 |
目前,您无法对事务的名称进行显式控制,其中 'name'表示事务监视器中显示的事务名称(如果适用)(例如,WebLogic 的事务监视器)和日志记录输出中。对于声明性事务,事务名称始终是完全限定的类名 +.
+ 事务建议类的方法名称。例如,如果handlePayment(..)
方法BusinessService
类启动事务,则
交易名称为:com.example.BusinessService.handlePayment
.
多个事务管理器@Transactional
大多数 Spring 应用程序只需要一个事务管理器,但可能有
您希望在单个事务管理器中包含多个独立事务管理器的情况
应用。您可以使用value
或transactionManager
属性的@Transactional
注释来选择指定TransactionManager
待使用。这可以是 bean 名称或限定符值
事务管理器 Bean 的。例如,使用限定符表示法,可以
将以下 Java 代码与以下事务管理器 bean 声明相结合
在应用程序上下文中:
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
class TransactionalService {
@Transactional("order")
fun setSomething(name: String) {
// ...
}
@Transactional("account")
fun doSomething() {
// ...
}
@Transactional("reactive-account")
fun doSomethingReactive(): Mono<Void> {
// ...
}
}
以下列表显示了 bean 声明:
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>
在这种情况下,对TransactionalService
在单独的
事务管理器,由order
,account
和reactive-account
限定 符。默认值<tx:annotation-driven>
目标 bean 名称,transactionManager
,
如果没有特别限定的,则仍使用TransactionManager
豆子被找到。
自定义组合注释
如果您发现重复使用相同的属性@Transactional
在许多不同的
方法,Spring 的元注释支持可以让您
为您的特定用例定义自定义组合注释。例如,考虑
以下注释定义:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx
前面的注释让我们编写上一节中的示例如下:
public class TransactionalService {
@OrderTx
public void setSomething(String name) {
// ...
}
@AccountTx
public void doSomething() {
// ...
}
}
class TransactionalService {
@OrderTx
fun setSomething(name: String) {
// ...
}
@AccountTx
fun doSomething() {
// ...
}
}
在前面的示例中,我们使用语法来定义事务管理器限定符 和事务标签,但我们也可以包括传播行为, 回滚规则、超时和其他功能。
1.4.7. 事务传播
本节描述 Spring 中事务传播的一些语义。注意 本节不是对事务传播的适当介绍。相反,它 详细介绍了有关 Spring 中事务传播的一些语义。
在 Spring 管理的事务中,请注意物理事务和 逻辑事务,以及传播设置如何应用于此差异。
理解PROPAGATION_REQUIRED

PROPAGATION_REQUIRED
强制执行物理事务,无论是在本地,还是当前
如果尚不存在事务或参与现有的“外部”事务,则范围
为更大的范围定义。这是常见调用堆栈排列中的一个很好的默认值
在同一线程中(例如,委托给多个存储库方法的服务外观
其中所有底层资源都必须参与服务级别事务)。
默认情况下,参与事务会加入外部作用域
静默忽略本地隔离级别、超时值或只读标志(如果有)。
考虑切换validateExistingTransactions flag 到true 在您的交易中
如果希望在参与隔离级别声明时拒绝隔离级别声明,则
具有不同隔离级别的现有事务。这种非宽松模式还
拒绝只读不匹配(即尝试参与的内部读写事务
在只读外部作用域中)。 |
当传播设置为PROPAGATION_REQUIRED
,一个逻辑事务范围
为应用设置的每种方法创建。每个这样的逻辑
事务范围可以单独确定仅回滚状态,外部
事务范围在逻辑上独立于内部事务范围。
在标准的情况下PROPAGATION_REQUIRED
行为,所有这些作用域都是
映射到同一实物交易。因此,在内部设置了一个仅回滚标记
事务范围确实会影响外部事务实际提交的机会。
但是,在内部事务范围设置仅回滚标记的情况下,
外部事务尚未决定回滚本身,因此回滚(静默地
由内部事务范围触发)是意外的。A 对应的UnexpectedRollbackException
在这一点上被抛出。这是预期行为,因此
事务的调用者永远不会被误导,认为提交是
当它实际上不是时执行的。因此,如果内部事务(其中外部调用方
不知道)静默地将事务标记为仅回滚,外部调用者仍
调用 commit。外部调用方需要接收UnexpectedRollbackException
自
清楚地指示已执行回滚。
理解PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW
,与PROPAGATION_REQUIRED
,总是使用
每个受影响事务范围的独立物理事务,从不
参与外部作用域的现有事务。在这样的安排中,
底层资源事务不同,因此可以提交或回滚
独立,外部事务不受内部事务回滚的影响
状态,并在完成后立即释放内部事务的锁。
这样一个独立的内部事务也可以声明自己的隔离级别、超时、
和只读设置,并且不继承外部事务的特征。
附加到外部事务的资源将保持绑定在那里,而
内部事务获取自己的资源,例如新的数据库连接。
这可能会导致连接池耗尽,如果出现以下情况,则可能导致死锁
多个线程具有活动的外部事务并等待获取新连接
对于他们的内部交易,池无法分发任何这样的内部交易
连接。请勿使用PROPAGATION_REQUIRES_NEW 除非您的连接池
大小适当,至少超过并发线程数 1。 |
理解PROPAGATION_NESTED
PROPAGATION_NESTED
使用具有多个保存点的单个物理事务
它可以回滚到。这种部分回滚让内部事务范围
触发其作用域的回滚,外部事务能够继续
尽管某些作已被回滚,但物理事务。此设置
通常映射到 JDBC 保存点,因此它仅适用于 JDBC 资源
交易。参见 Spring 的DataSourceTransactionManager
.
1.4.8. 建议事务作
假设您要同时运行事务作和一些基本的分析建议。
在以下情况下如何实现这一点<tx:annotation-driven/>
?
当您调用updateFoo(Foo)
方法,您希望看到以下作:
-
配置的分析方面将启动。
-
事务性建议运行。
-
建议对象上的方法将运行。
-
事务提交。
-
分析方面报告整个事务方法调用的确切持续时间。
本章不涉及任何非常详细的解释 AOP(除非它 适用于交易)。有关 AOP 的详细报道,请参阅 AOP 一般配置和 AOP。 |
以下代码显示了前面讨论的简单分析方面:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
public class SimpleProfiler implements Ordered {
private int order;
// allows us to control the ordering of advice
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
// this method is the around advice
public Object profile(ProceedingJoinPoint call) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(call.toShortString());
returnValue = call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
return returnValue;
}
}
class SimpleProfiler : Ordered {
private var order: Int = 0
// allows us to control the ordering of advice
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
// this method is the around advice
fun profile(call: ProceedingJoinPoint): Any {
var returnValue: Any
val clock = StopWatch(javaClass.name)
try {
clock.start(call.toShortString())
returnValue = call.proceed()
} finally {
clock.stop()
println(clock.prettyPrint())
}
return returnValue
}
}
建议的顺序
通过Ordered
接口。有关通知排序的完整详细信息,请参阅通知排序。
以下配置创建了一个fooService
具有分析和
事务方面按所需顺序应用于它:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- this is the aspect -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- run before the transactional advice (hence the lower order number) -->
<property name="order" value="1"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" order="200"/>
<aop:config>
<!-- this advice runs around the transactional advice -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
您可以配置任何号码 以类似的方式进行其他方面。
以下示例创建与前两个示例相同的设置,但使用纯 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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- the profiling advice -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- run before the transactional advice (hence the lower order number) -->
<property name="order" value="1"/>
</bean>
<aop:config>
<aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
<!-- runs after the profiling advice (cf. the order attribute) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
<!-- order value is higher than the profiling aspect -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- other <bean/> definitions such as a DataSource and a TransactionManager here -->
</beans>
上述配置的结果是fooService
具有分析和
按该顺序应用于它的交易方面。如果您需要分析建议
在进入过程中的交易建议之后和之前运行
交易建议,您可以交换分析的价值
Aspect Bean 的order
属性,使其高于交易建议的
order 值。
您可以以类似的方式配置其他方面。
1.4.9. 使用@Transactional
与 AspectJ
您还可以使用 Spring Framework 的@Transactional
弹簧外部的支撑
容器通过 AspectJ 方面。为此,请先对您的类进行注释
(以及可选的类的方法)与@Transactional
注解
,然后将您的应用程序与org.springframework.transaction.aspectj.AnnotationTransactionAspect
在spring-aspects.jar
文件。您还必须使用事务配置方面
经理。您可以使用 Spring Framework 的 IoC 容器来处理
依赖注入方面。配置事务的最简单方法
管理方面是使用<tx:annotation-driven/>
元素并指定mode
属性设置为aspectj
如用@Transactional
.因为
我们在这里重点关注在 Spring 容器之外运行的应用程序,我们展示了
你如何以编程方式做到这一点。
在继续之前,您可能需要阅读用@Transactional 和 AOP 分别。 |
以下示例演示如何创建事务管理器并配置AnnotationTransactionAspect
使用它:
// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
// construct an appropriate transaction manager
val txManager = DataSourceTransactionManager(getDataSource())
// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().transactionManager = txManager
当你使用这个方面时,你必须注释实现类(或方法)在该类中或两者中),而不是类实现的接口(如果有)。方面J遵循 Java 的规则,即接口上的注释不会被继承。 |
这@Transactional
类上的注释指定默认事务语义
用于执行类中的任何公共方法。
这@Transactional
类中方法的注释将覆盖默认值
类注释(如果存在)给出的交易语义。您可以注释任何方法
无论能见度如何。
要使用AnnotationTransactionAspect
,您必须构建
使用 AspectJ 的应用程序(参见 AspectJ 开发
Guide)或使用加载时间编织。请参阅加载时编织
AspectJ 在 Spring 框架中讨论使用 AspectJ 进行加载时编织。
1.5. 程序化事务管理
Spring Framework 提供了两种编程事务管理方法,通过使用:
-
这
TransactionTemplate
或TransactionalOperator
. -
一个
TransactionManager
直接实现。
Spring 团队一般建议将TransactionTemplate
用于程序化
命令式流中的事务管理,以及TransactionalOperator
用于响应式代码。
第二种方法类似于使用 JTAUserTransaction
API,尽管例外
处理不那么麻烦。
1.5.1. 使用TransactionTemplate
这TransactionTemplate
采用与其他 Spring 模板相同的方法,例如
这JdbcTemplate
.它使用回调方法(使应用程序代码不必
进行样板采购并发布事务资源)并导致
意图驱动的代码,因为您的代码仅关注什么
你想做。
如下例所示,使用TransactionTemplate 绝对
将您与 Spring 的事务基础设施和 API 相结合。是否为程序化
事务管理是否适合您的开发需求是您做出的决定
必须让自己。 |
必须在事务上下文中运行并显式使用TransactionTemplate
类似于下一个示例。您作为应用程序
developer,可以编写一个TransactionCallback
实现(通常表示为
匿名内部类),其中包含您需要在
交易。然后,您可以传递自定义的实例TransactionCallback
到execute(..)
在TransactionTemplate
.以下示例显示了如何执行此作:
public class SimpleService implements Service {
// single TransactionTemplate shared amongst all methods in this instance
private final TransactionTemplate transactionTemplate;
// use constructor-injection to supply the PlatformTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// the code in this method runs in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}
// use constructor-injection to supply the PlatformTransactionManager
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
// single TransactionTemplate shared amongst all methods in this instance
private val transactionTemplate = TransactionTemplate(transactionManager)
fun someServiceMethod() = transactionTemplate.execute<Any?> {
updateOperation1()
resultOfUpdateOperation2()
}
}
如果没有返回值,可以使用方便的TransactionCallbackWithoutResult
类
使用匿名类,如下所示:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
updateOperation1()
updateOperation2()
}
})
回调中的代码可以通过调用setRollbackOnly()
提供的方法TransactionStatus
对象,如下所示:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
try {
updateOperation1()
updateOperation2()
} catch (ex: SomeBusinessException) {
status.setRollbackOnly()
}
}
})
指定事务设置
您可以指定事务设置(例如传播模式、隔离级别、
超时,依此类推)在TransactionTemplate
以编程方式或在 配置。 默认情况下,TransactionTemplate
实例具有默认的事务设置。 这 以下示例显示了事务设置的编程自定义特定TransactionTemplate:
public class SimpleService implements Service {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// the transaction settings can be set here explicitly if so desired
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); // 30 seconds
// and so forth...
}
}
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
private val transactionTemplate = TransactionTemplate(transactionManager).apply {
// the transaction settings can be set here explicitly if so desired
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
timeout = 30 // 30 seconds
// and so forth...
}
}
以下示例定义了TransactionTemplate
使用一些自定义事务
使用 Spring XML 配置进行设置:
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>
然后,您可以注入sharedTransactionTemplate
转换为所需的服务。
最后,的实例TransactionTemplate
类是线程安全的,在这种情况下
不要保持任何对话状态。TransactionTemplate
然而,实例确实如此,
维护配置状态。因此,虽然多个类可以共享一个实例
的TransactionTemplate
,如果类需要使用TransactionTemplate
跟
不同的设置(例如,不同的隔离级别),您需要创建
两个不同的TransactionTemplate
实例。
1.5.2. 使用TransactionalOperator
这TransactionalOperator
遵循与其他响应式类似的运算符设计
运营商。它使用回调方法(使应用程序代码不必执行
样板获取和释放事务资源),并生成代码
意图驱动,因为您的代码只关注您想要做的事情。
如下例所示,使用TransactionalOperator 绝对
将您与 Spring 的事务基础设施和 API 相结合。是否为程序化
事务管理是否适合您的开发需求是您的决定
让自己。 |
必须在事务上下文中运行并显式使用
这TransactionalOperator
类似于下一个示例:
public class SimpleService implements Service {
// single TransactionalOperator shared amongst all methods in this instance
private final TransactionalOperator transactionalOperator;
// use constructor-injection to supply the ReactiveTransactionManager
public SimpleService(ReactiveTransactionManager transactionManager) {
this.transactionalOperator = TransactionalOperator.create(transactionManager);
}
public Mono<Object> someServiceMethod() {
// the code in this method runs in a transactional context
Mono<Object> update = updateOperation1();
return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
}
}
// use constructor-injection to supply the ReactiveTransactionManager
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
// single TransactionalOperator shared amongst all methods in this instance
private val transactionalOperator = TransactionalOperator.create(transactionManager)
suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> {
updateOperation1()
resultOfUpdateOperation2()
}
}
TransactionalOperator
可以通过两种方式使用:
-
使用项目反应堆类型的运算符样式 (
mono.as(transactionalOperator::transactional)
) -
回调样式适用于所有其他情况 (
transactionalOperator.execute(TransactionCallback<T>)
)
回调中的代码可以通过调用setRollbackOnly()
提供的方法ReactiveTransaction
对象,如下所示:
transactionalOperator.execute(new TransactionCallback<>() {
public Mono<Object> doInTransaction(ReactiveTransaction status) {
return updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
}
}
});
transactionalOperator.execute(object : TransactionCallback() {
override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
}
})
取消信号
在响应式流中,Subscriber
可以取消其Subscription
并停止其Publisher
.Project Reactor 中的运算符,以及其他库中的运算符,例如next()
,take(long)
,timeout(Duration)
,其他人可以发出取消。没有办法
了解取消的原因,是由于错误还是仅仅缺少
兴趣进一步消费。自 5.3 版以来,取消信号会导致回滚。
因此,重要的是要考虑事务下游使用的运算符Publisher
.特别是在Flux
或其他多值Publisher
,
必须使用完整输出才能完成事务。
指定事务设置
您可以指定事务设置(例如传播模式、隔离级别、
超时,依此类推)的TransactionalOperator
. 默认情况下,TransactionalOperator
实例具有默认事务设置。 这 以下示例显示了特定事务设置的自定义TransactionalOperator:
public class SimpleService implements Service {
private final TransactionalOperator transactionalOperator;
public SimpleService(ReactiveTransactionManager transactionManager) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// the transaction settings can be set here explicitly if so desired
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
definition.setTimeout(30); // 30 seconds
// and so forth...
this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
}
}
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
private val definition = DefaultTransactionDefinition().apply {
// the transaction settings can be set here explicitly if so desired
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
timeout = 30 // 30 seconds
// and so forth...
}
private val transactionalOperator = TransactionalOperator(transactionManager, definition)
}
1.5.3. 使用TransactionManager
以下各节将说明命令式事务和响应式事务的编程用法 经理。
使用PlatformTransactionManager
对于命令式事务,您可以使用org.springframework.transaction.PlatformTransactionManager
直接管理您的 交易。 为此,请通过PlatformTransactionManager
你 通过 bean 引用对 bean 使用。然后,通过使用TransactionDefinition
和TransactionStatus
对象,您可以启动事务、回滚和提交。 这 以下示例显示了如何执行此作:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// put your business logic here
} catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
val def = DefaultTransactionDefinition()
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
val status = txManager.getTransaction(def)
try {
// put your business logic here
} catch (ex: MyException) {
txManager.rollback(status)
throw ex
}
txManager.commit(status)
使用ReactiveTransactionManager
处理响应式事务时,可以使用org.springframework.transaction.ReactiveTransactionManager
直接管理您的 交易。 为此,请通过ReactiveTransactionManager
你 通过 bean 引用对 bean 使用。然后,通过使用TransactionDefinition
和ReactiveTransaction
对象,您可以启动事务、回滚和提交。 这 以下示例显示了如何执行此作:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);
reactiveTx.flatMap(status -> {
Mono<Object> tx = ...; // put your business logic here
return tx.then(txManager.commit(status))
.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});
val def = DefaultTransactionDefinition()
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
val reactiveTx = txManager.getReactiveTransaction(def)
reactiveTx.flatMap { status ->
val tx = ... // put your business logic here
tx.then(txManager.commit(status))
.onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) }
}
1.6. 在编程事务管理和声明式事务管理之间进行选择
程序化事务管理通常只有在您有一个小
事务作数。例如,如果您有一个 Web 应用程序
仅对某些更新作需要事务,您可能不想将
使用 Spring 或任何其他技术进行事务代理。在这种情况下,使用TransactionTemplate
可能是一个好方法。能够设置交易名称
显式也是只能使用编程方法完成的事情
到交易管理。
另一方面,如果您的应用程序具有许多事务作, 声明式事务管理通常是值得的。它保持交易 管理脱离业务逻辑,配置起来并不难。使用 Spring Framework,而不是 EJB CMT,声明式事务的配置成本 管理大大减少。
1.7. 事务绑定事件
从 Spring 4.2 开始,事件的侦听器可以绑定到事务的某个阶段。 典型的示例是在事务成功完成时处理事件。 这样做可以更灵活地使用事件,当当前的结果 交易实际上对监听器很重要。
您可以使用@EventListener
注解。
如果需要将其绑定到事务,请使用@TransactionalEventListener
.
当您这样做时,侦听器默认绑定到事务的提交阶段。
下一个示例显示了这个概念。假设组件发布订单创建的 事件,我们想要定义一个监听器,该监听器应该只处理一次 已发布该事务的事务已成功提交。以下内容 示例设置这样的事件监听器:
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
// ...
}
}
@Component
class MyComponent {
@TransactionalEventListener
fun handleOrderCreatedEvent(creationEvent: CreationEvent<Order>) {
// ...
}
}
这@TransactionalEventListener
注释会公开一个phase
属性,让您
自定义侦听器应绑定到的事务阶段。
有效阶段是BEFORE_COMMIT
,AFTER_COMMIT
(默认),AFTER_ROLLBACK
,以及AFTER_COMPLETION
聚合事务完成(无论是提交还是回滚)。
如果没有事务正在运行,则根本不会调用侦听器,因为我们无法遵循必需的语义。但是,您可以通过将fallbackExecution
属性的注释设置为true
.
|
1.8. 特定于应用程序服务器的集成
Spring 的事务抽象通常与应用程序服务器无关。 此外 Spring的JtaTransactionManager
类(可以选择对JTA 执行 JNDI 查找UserTransaction
和TransactionManager
objects)自动检测后一个对象的位置,该对象因应用程序服务器而异。访问 JTATransactionManager
允许增强事务语义——特别是支持事务暂停。请参阅JtaTransactionManager
javadoc 了解详情。
Spring的JtaTransactionManager
是在 Java EE 应用程序上运行的标准选择服务器,并且已知可以在所有常见服务器上运行。高级功能,例如事务暂停,也适用于许多服务器(包括 GlassFish、JBoss 和Geronimo),无需任何特殊配置。但是,对于完全支持事务暂停和进一步的高级集成,Spring 包括特殊的适配器用于 WebLogic Server 和 WebSphere。这些适配器将在下文中讨论 部分。
对于标准场景,包括 WebLogic Server 和 WebSphere,请考虑使用
方便<tx:jta-transaction-manager/>
配置元素。配置后,
此元素会自动检测底层服务器并选择最佳
交易平台可用的事务管理器。这意味着您不需要明确
配置特定于服务器的适配器类(如以下部分所述)。
相反,它们是自动选择的,标准JtaTransactionManager
作为默认回退。
1.9. 常见问题的解决方案
本节介绍一些常见问题的解决方案。
1.9.1. 对特定事务管理器使用错误的事务管理器DataSource
使用正确的PlatformTransactionManager
根据您选择的实现
交易技术和要求。正确使用,Spring 框架只是
提供了一个简单且可移植的抽象。如果使用 global
transactions,则必须使用org.springframework.transaction.jta.JtaTransactionManager
类(或特定于应用程序服务器的子类
它)用于您的所有事务作。否则,事务基础设施
尝试对容器等资源执行本地事务DataSource
实例。这样的本地事务没有意义,一个好的应用服务器
将它们视为错误。
1.10. 更多资源
有关 Spring Framework 的事务支持的更多信息,请参阅:
-
分散式 Spring 中的事务,有和没有 XA 是一个 JavaWorld 演示,其中 Spring 的 David Syer 指导您了解分布式的七种模式 Spring 应用程序中的事务,其中三个有 XA,四个没有。
-
Java 事务设计策略是一本书 可从 InfoQ 获得,提供节奏良好的介绍 到 Java 中的事务。它还包括如何配置的并排示例 并将事务与 Spring Framework 和 EJB3 一起使用。
2. DAO 支持
Spring 中的数据访问对象 (DAO) 支持旨在使其易于使用 以一致的方式访问数据技术(例如 JDBC、Hibernate 或 JPA)。这 让你相当轻松地在上述持久性技术之间切换, 它还允许您编写代码,而不必担心捕获异常 特定于每种技术。
2.1. 一致的异常层次结构
Spring 提供了特定于技术的例外的便捷翻译,例如SQLException
到它自己的异常类层次结构,该层次结构具有DataAccessException
如
根本异常。这些异常包装原始异常,因此永远不会有
任何可能丢失有关可能出错的信息的风险。
除了 JDBC 异常之外,Spring 还可以包装特定于 JPA 和 Hibernate 的异常, 将它们转换为一组集中的运行时异常。这使您可以处理大多数 不可恢复的持久性异常仅在适当的层中,而没有 DAO 中烦人的样板捕获和抛出块和异常声明。 (不过,您仍然可以在需要的任何地方捕获和处理异常。如上所述, JDBC 异常(包括特定于数据库的方言)也转换为相同的 层次结构,这意味着您可以在一致的 编程模型。
前面的讨论适用于 Spring 支持中的各种模板类
适用于各种 ORM 框架。如果使用基于拦截器的类,则应用程序必须
关心处理HibernateExceptions
和PersistenceExceptions
本身,最好是
委托给convertHibernateAccessException(..)
或convertJpaAccessException(..)
方法,分别SessionFactoryUtils
.这些方法将异常
到与org.springframework.dao
异常层次结构。如PersistenceExceptions
如果不检查,它们也会被扔掉
(不过,在例外方面牺牲了通用 DAO 抽象)。
下图显示了 Spring 提供的异常层次结构。
(请注意,图中详述的类层次结构仅显示整个DataAccessException
层次结构。

2.2. 用于配置 DAO 或存储库类的注释
保证数据访问对象 (DAO) 或存储库提供的最佳方式
异常翻译是使用@Repository
注解。此注释还
让组件扫描支持查找和配置您的 DAO 和存储库
无需为它们提供 XML 配置条目。以下示例显示
如何使用@Repository
注解:
@Repository (1)
public class SomeMovieFinder implements MovieFinder {
// ...
}
1 | 这@Repository 注解。 |
@Repository (1)
class SomeMovieFinder : MovieFinder {
// ...
}
1 | 这@Repository 注解。 |
任何 DAO 或存储库实现都需要访问持久化资源,
取决于所使用的持久性技术。例如,基于 JDBC 的存储库
需要访问 JDBCDataSource
,并且基于 JPA 的存储库需要访问EntityManager
.实现此目的的最简单方法是具有此资源依赖关系
通过使用其中一个@Autowired
,@Inject
,@Resource
或@PersistenceContext
附注。以下示例适用于 JPA 存储库:
@Repository
public class JpaMovieFinder implements MovieFinder {
@PersistenceContext
private EntityManager entityManager;
// ...
}
@Repository
class JpaMovieFinder : MovieFinder {
@PersistenceContext
private lateinit var entityManager: EntityManager
// ...
}
如果您使用经典的 Hibernate API,则可以注入SessionFactory
,如下所示
示例显示:
@Repository
public class HibernateMovieFinder implements MovieFinder {
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
// ...
}
@Repository
class HibernateMovieFinder(private val sessionFactory: SessionFactory) : MovieFinder {
// ...
}
我们在这里展示的最后一个示例是针对典型的 JDBC 支持。你可以拥有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);
}
// ...
}
@Repository
class JdbcMovieFinder(dataSource: DataSource) : MovieFinder {
private val jdbcTemplate = JdbcTemplate(dataSource)
// ...
}
有关如何 配置应用程序上下文以利用这些注释。 |
3. 使用 JDBC 访问数据
Spring Framework JDBC 抽象提供的值可能最好地显示为 下表中概述的作顺序。该表显示了哪些作 Spring 照顾哪些行动是你的责任。
行动 | Spring | 你 |
---|---|---|
定义连接参数。 |
X |
|
打开连接。 |
X |
|
指定 SQL 语句。 |
X |
|
声明参数并提供参数值 |
X |
|
准备并运行语句。 |
X |
|
设置循环以循环访问结果(如果有)。 |
X |
|
为每次迭代做工作。 |
X |
|
处理任何异常。 |
X |
|
处理事务。 |
X |
|
关闭连接、语句和结果集。 |
X |
Spring Framework 负责所有可以使 JDBC 成为 繁琐的 API。
3.1. 选择JDBC数据库访问的方法
您可以在多种方法中进行选择,以构成 JDBC 数据库访问的基础。
除了三种口味JdbcTemplate
,新的SimpleJdbcInsert
和SimpleJdbcCall
方法优化数据库元数据,RDBMS 对象样式采用
更面向对象的方法类似于 JDO 查询设计。一旦开始使用
其中一种方法,您仍然可以混合和匹配以包含来自
不同的方法。所有方法都需要符合 JDBC 2.0 的驱动程序,而某些
高级功能需要 JDBC 3.0 驱动程序。
-
JdbcTemplate
是经典且最流行的 Spring JDBC 方法。这 “最低级别”方法,所有其他方法都在幕后使用 JdbcTemplate。 -
NamedParameterJdbcTemplate
包装一个JdbcTemplate
提供命名参数 而不是传统的 JDBC?
占位符。这种方法提供了更好的 文档和 SQL 语句有多个参数时的易用性。 -
SimpleJdbcInsert
和SimpleJdbcCall
优化数据库元数据以限制数量 必要的配置。这种方法简化了编码,因此您需要 仅提供表或过程的名称,并提供匹配的参数映射 列名称。仅当数据库提供足够的元数据时,这才有效。如果 数据库不提供此元数据,您必须提供显式的 参数的配置。 -
RDBMS 对象 — 包括
MappingSqlQuery
,SqlUpdate
和StoredProcedure
— 要求您在初始化期间创建可重用且线程安全的对象 数据访问层。这种方法是以 JDO 查询为模型的,您可以在其中定义 查询字符串,声明参数,并编译查询。一旦你这样做了,execute(…)
,update(…)
和findObject(…)
方法可以调用多个 具有各种参数值的次数。
3.2. 包层次结构
Spring 框架的 JDBC 抽象框架由四个不同的包组成:
-
core
:这org.springframework.jdbc.core
包包含JdbcTemplate
类 及其各种回调接口,以及各种相关类。子包 叫org.springframework.jdbc.core.simple
包含SimpleJdbcInsert
和SimpleJdbcCall
类。另一个名为org.springframework.jdbc.core.namedparam
包含NamedParameterJdbcTemplate
类和相关的支持类。请参阅使用 JDBC 核心类控制基本 JDBC 处理和错误处理、JDBC 批处理作和使用SimpleJdbc
类. -
datasource
:这org.springframework.jdbc.datasource
包包含一个实用程序类 为了方便DataSource
访问和各种简单的DataSource
您可以实现的实现 用于在 Java EE 容器外部测试和运行未修改的 JDBC 代码。子包 叫org.springframework.jdbc.datasource.embedded
提供创建支持 使用 Java 数据库引擎(例如 HSQL、H2 和 Derby)进行嵌入式数据库。请参阅控制数据库连接和嵌入式数据库支持。 -
object
:这org.springframework.jdbc.object
package 包含将RDBMS 查询、更新和存储过程表示为线程安全、可重用对象的类。请参阅将 JDBC作建模为 Java 对象。这种方法由 JDO 建模,尽管查询返回的对象自然会与数据库断开连接。这种更高级别的 JDBC 抽象取决于org.springframework.jdbc.core
包。 -
support
:这org.springframework.jdbc.support
package 提供SQLException
转换功能和一些实用程序类。JDBC 处理期间抛出的异常被转换为org.springframework.dao
包。 这意味着使用 Spring JDBC 抽象层的代码不需要实现 JDBC 或RDBMS 特定的错误处理。所有翻译的异常都未选中,这为您提供了捕获异常的选项,您可以从中恢复,同时让其他异常传播给调用者。 看用SQLExceptionTranslator
.
3.3. 使用 JDBC 核心类控制基本的 JDBC 处理和错误处理
本节介绍如何使用 JDBC 核心类来控制基本的 JDBC 处理,包括错误处理。它包括以下主题:
3.3.1. 使用JdbcTemplate
JdbcTemplate
是 JDBC 核心包中的中心类。它处理
创建和释放资源,这有助于避免常见错误,例如
忘记关闭连接。它执行核心 JDBC 的基本任务
工作流(如语句创建和执行),让应用程序代码提供
SQL 和提取结果。这JdbcTemplate
类:
-
运行 SQL 查询
-
更新语句和存储过程调用
-
执行迭代
ResultSet
实例和返回参数值的提取。 -
捕获 JDBC 异常,并将其转换为通用的、信息量更大的异常 层次结构在
org.springframework.dao
包。(请参阅一致异常层次结构。
当您使用JdbcTemplate
对于您的代码,您只需实现回调
接口,为它们提供明确定义的契约。给定一个Connection
由JdbcTemplate
类,则PreparedStatementCreator
callback 接口创建一个准备好的
语句,提供 SQL 和任何必要的参数。对于CallableStatementCreator
接口,它创建可调用语句。这RowCallbackHandler
接口从每个行中提取值ResultSet
.
您可以使用JdbcTemplate
通过直接实例化在 DAO 实现中
使用DataSource
引用,或者您可以在 Spring IoC 容器中配置它并将其提供给
DAO 作为 bean 参考。
这DataSource 应始终配置为 Spring IoC 容器中的 bean。在
第一种情况是 bean 直接提供给服务;在第二种情况下,它给出了
到准备好的模板。 |
此类发出的所有 SQL 都记录在DEBUG
类别下的级别
对应于模板实例的完全限定类名(通常JdbcTemplate
,但如果您使用JdbcTemplate
类)。
以下部分提供了一些示例JdbcTemplate
用法。这些例子
不是JdbcTemplate
.
请参阅随附的 javadoc 。
查询 (SELECT
)
以下查询获取关系中的行数:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!
以下查询使用绑定变量:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
"select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!
以下查询查找String
:
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);
val lastName = this.jdbcTemplate.queryForObject<String>(
"select last_name from t_actor where id = ?",
arrayOf(1212L))!!
以下查询查找并填充单个域对象:
Actor actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
(resultSet, rowNum) -> {
Actor newActor = new Actor();
newActor.setFirstName(resultSet.getString("first_name"));
newActor.setLastName(resultSet.getString("last_name"));
return newActor;
},
1212L);
val actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
arrayOf(1212L)) { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
}
以下查询查找并填充域对象列表:
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
(resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
});
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
如果最后两个代码片段实际上存在于同一个应用程序中,它将使
消除两者中存在的重复的意义RowMapper
lambda 表达式和
将它们提取到单个字段中,然后可以根据需要由 DAO 方法引用。
例如,最好按如下方式编写前面的代码片段:
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
};
public List<Actor> findAllActors() {
return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}
val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
}
fun findAllActors(): List<Actor> {
return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
}
更新 (INSERT
,UPDATE
和DELETE
) 替换为JdbcTemplate
您可以使用update(..)
执行插入、更新和删除作的方法。
参数值通常作为变量参数提供,或者作为对象数组提供。
以下示例插入一个新条目:
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling")
以下示例更新现有条目:
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L)
以下示例删除条目:
this.jdbcTemplate.update(
"delete from t_actor where id = ?",
Long.valueOf(actorId));
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
其他JdbcTemplate
操作
您可以使用execute(..)
方法来运行任何任意 SQL。因此,方法通常用于 DDL 语句。它因采用callback 接口、绑定变量数组等的变体而严重重载。以下示例创建了一个 桌子:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
以下示例调用存储过程:
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
unionId.toLong())
稍后将介绍更复杂的存储过程支持。
JdbcTemplate
最佳实践
的实例JdbcTemplate
一旦配置,类是线程安全的。 这是 重要,因为这意味着您可以配置JdbcTemplate
然后安全地将此共享引用注入多个 DAO(或存储库)。 这JdbcTemplate
是有状态的,因为它维护对DataSource
但 此状态不是对话状态。
使用JdbcTemplate
类(以及关联的NamedParameterJdbcTemplate
类)是配置一个DataSource
在您的 Spring 配置文件中,然后 dependency-inject共享的DataSource
bean 加入你的 DAO 类。 这JdbcTemplate
创建于的 setterDataSource
. 这导致了类似于以下内容的 DAO:
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
以下示例显示了相应的 XML 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
</beans>
显式配置的替代方法是使用组件扫描和注释
支持依赖注入。在这种情况下,您可以使用@Repository
(这使其成为组件扫描的候选者)并注释DataSource
塞特
方法@Autowired
.以下示例显示了如何执行此作:
@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
@Autowired (2)
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource); (3)
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 | 用@Repository . |
2 | 注释DataSource setter 方法与@Autowired . |
3 | 创建一个新的JdbcTemplate 使用DataSource . |
@Repository (1)
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { (2)
private val jdbcTemplate = JdbcTemplate(dataSource) (3)
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 | 用@Repository . |
2 | 构造函数注入DataSource . |
3 | 创建一个新的JdbcTemplate 使用DataSource . |
以下示例显示了相应的 XML 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
<context:component-scan base-package="org.springframework.docs.test" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
</beans>
如果您使用 Spring 的JdbcDaoSupport
类和各种 JDBC 支持的 DAO 类
从中扩展,您的子类继承了setDataSource(..)
方法JdbcDaoSupport
类。您可以选择是否继承自此类。这JdbcDaoSupport
课程仅为方便起见而提供。
无论您选择使用上述哪种模板初始化样式(或
not),很少需要创建JdbcTemplate
每个类别
时间。配置后,一个JdbcTemplate
实例是线程安全的。
如果您的应用程序访问多个
databases,您可能需要多个JdbcTemplate
实例,这需要多个DataSources
随后,以不同的方式进行多次
配置JdbcTemplate
实例。
3.3.2. 使用NamedParameterJdbcTemplate
这NamedParameterJdbcTemplate
class 添加了对 JDBC 语句编程的支持通过使用命名参数,而不是仅使用经典占位符 ('?'
) 参数。 这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);
}
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = MapSqlParameterSource("first_name", firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
请注意,在分配给sql
变量,并插入namedParameters
variable(类型MapSqlParameterSource
).
或者,您可以将命名参数及其相应的值传递给NamedParameterJdbcTemplate
实例,使用Map
-based 样式。剩余的方法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);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = mapOf("first_name" to firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
与NamedParameterJdbcTemplate
(并存在于同一
Java 包)是SqlParameterSource
接口。你已经看过一个例子
在前面的代码片段之一中实现此接口(MapSqlParameterSource
类)。一SqlParameterSource
是命名参数的来源
值设置为NamedParameterJdbcTemplate
.这MapSqlParameterSource
class 是一个
简单的实现,即围绕java.util.Map
,其中键
是参数名称,值是参数值。
另一个SqlParameterSource
实现是BeanPropertySqlParameterSource
类。此类包装一个任意 JavaBean(即,类的实例
遵守
JavaBean 约定),并使用包装的 JavaBean 的属性作为源
的命名参数值。
以下示例显示了一个典型的 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...
}
data class Actor(val id: Long, val firstName: String, val lastName: String)
以下示例使用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);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActors(exampleActor: Actor): Int {
// notice how the named parameters match the properties of the above 'Actor' class
val sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName"
val namedParameters = BeanPropertySqlParameterSource(exampleActor)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
请记住,NamedParameterJdbcTemplate
Class 包装经典JdbcTemplate
模板。 如果您需要访问包装的JdbcTemplate
要访问的实例仅存在于中的功能JdbcTemplate
类,您可以使用getJdbcOperations()
访问包装的JdbcTemplate
通过JdbcOperations
接口。
也可以看看JdbcTemplate
最佳实践有关使用NamedParameterJdbcTemplate
类。
3.3.3. 使用SQLExceptionTranslator
SQLExceptionTranslator
是一个由可以转换的类实现的接口 之间SQLException
s 和 Spring 自己的org.springframework.dao.DataAccessException
, 这与数据访问策略无关。实现可以是通用的(例如例如,使用 JDBC 的 SQLState 代码)或专有的(例如,使用 Oracle 错误代码)以提高精度。这种异常转换机制用于常见的JdbcTemplate
和JdbcTransactionManager
没有的入口点 传播SQLException
而是DataAccessException
.
SQLErrorCodeSQLExceptionTranslator
是实现SQLExceptionTranslator
默认使用。此实现使用特定的提供商代码。它比更精确SQLState
实现。 错误代码翻译基于代码保存在名为SQLErrorCodes
. 创建此类,并且由SQLErrorCodesFactory
,它(顾名思义)是一个工厂 创建SQLErrorCodes
基于名为sql-error-codes.xml
. 此文件填充了提供商代码,并基于DatabaseProductName
取自DatabaseMetaData
. 实际的代码使用您正在使用的数据库。
这SQLErrorCodeSQLExceptionTranslator
按以下顺序应用匹配规则:
-
由子类实现的任何自定义翻译。通常,提供的混凝土
SQLErrorCodeSQLExceptionTranslator
,因此此规则不适用。它 仅当您实际提供了子类实现时才适用。 -
任何自定义实现
SQLExceptionTranslator
提供的接口 作为customSqlExceptionTranslator
属性的SQLErrorCodes
类。 -
的实例列表
CustomSQLErrorCodesTranslation
类(为customTranslations
属性的SQLErrorCodes
class) 中搜索匹配项。 -
应用错误代码匹配。
-
使用回退转换器。
SQLExceptionSubclassTranslator
是默认回退 在线翻译。如果此翻译不可用,则下一个回退转换器为 这SQLStateSQLExceptionTranslator
.
这SQLErrorCodesFactory 默认用于定义错误代码和自定义
异常翻译。它们在名为sql-error-codes.xml 从
类路径,以及匹配的SQLErrorCodes 实例基于数据库定位
名称,来自正在使用的数据库的数据库元数据。 |
您可以扩展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;
}
}
class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() {
override fun customTranslate(task: String, sql: String?, sqlEx: SQLException): DataAccessException? {
if (sqlEx.errorCode == -12345) {
return DeadlockLoserDataAccessException(task, sqlEx)
}
return null
}
}
在前面的示例中,特定的错误代码 (-12345
) 被翻译,而其他错误则留待默认翻译器实现翻译。要使用此自定义翻译器,您必须将其传递给JdbcTemplate
通过 方法setExceptionTranslator
,并且您必须使用JdbcTemplate
对于所有需要此转换器的数据访问处理。以下示例显示了如何使用此自定义转换器:
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
// create a JdbcTemplate and set data source
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);
// create a custom translator and set the DataSource for the default translation lookup
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
tr.setDataSource(dataSource);
this.jdbcTemplate.setExceptionTranslator(tr);
}
public void updateShippingCharge(long orderId, long pct) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId);
}
// create a JdbcTemplate and set data source
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
// create a custom translator and set the DataSource for the default translation lookup
exceptionTranslator = CustomSQLErrorCodesTranslator().apply {
this.dataSource = dataSource
}
}
fun updateShippingCharge(orderId: Long, pct: Long) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate!!.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId)
}
自定义转换器会传递一个数据源,以便在sql-error-codes.xml
.
3.3.4. 运行语句
运行 SQL 语句需要很少的代码。你需要一个DataSource
和JdbcTemplate
,包括JdbcTemplate
.以下示例显示了您需要包含哪些内容,以实现最小但
创建新表的全功能类:
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAStatement {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void doExecute() {
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class ExecuteAStatement(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun doExecute() {
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
}
}
3.3.5. 运行查询
某些查询方法返回单个值。要从
一行,使用queryForObject(..)
.后者转换返回的 JDBCType
到
作为参数传入的 Java 类。如果类型转换无效,则InvalidDataAccessApiUsageException
被抛出。以下示例包含两个
查询方法,一个用于int
以及查询String
:
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class RunAQuery {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int getCount() {
return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
}
public String getName() {
return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class RunAQuery(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
val count: Int
get() = jdbcTemplate.queryForObject("select count(*) from mytable")!!
val name: String?
get() = jdbcTemplate.queryForObject("select name from mytable")
}
除了单个结果查询方法外,还有几个方法返回一个列表,其中包含
查询返回的每行的条目。最通用的方法是queryForList(..)
,
它返回一个List
其中每个元素都是Map
每列包含一个条目,
使用列名作为键。如果向前面的示例添加一个方法以检索
列表,可能如下所示:
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Map<String, Object>> getList() {
return this.jdbcTemplate.queryForList("select * from mytable");
}
private val jdbcTemplate = JdbcTemplate(dataSource)
fun getList(): List<Map<String, Any>> {
return jdbcTemplate.queryForList("select * from mytable")
}
返回的列表将类似于以下内容:
[{name=Bob, id=1}, {name=Mary, id=2}]
3.3.6. 更新数据库
以下示例更新特定主键的列:
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAnUpdate {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setName(int id, String name) {
this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
}
}
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate
class ExecuteAnUpdate(dataSource: DataSource) {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun setName(id: Int, name: String) {
jdbcTemplate.update("update mytable set name = ? where id = ?", name, id)
}
}
在前面的示例中,SQL 语句具有行参数的占位符。您可以将参数值in 作为变量传递,或者作为对象数组传递。因此,您应该显式包装原语在原语包装类中,或者您应该使用自动装箱。
3.3.7. 检索自动生成的密钥
一update()
方便方法支持检索由
数据库。此支持是 JDBC 3.0 标准的一部分。请参阅第 13.6 章
规格了解详情。该方法采用PreparedStatementCreator
作为其第一个参数,这是指定所需 insert 语句的方式。另一个参数是一个KeyHolder
,其中包含从 更新。 没有标准的单一方法来创建适当的PreparedStatement
(这解释了为什么方法签名是这样的)。以下示例适用于在 Oracle 上,但在其他平台上可能不起作用:
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
ps.setString(1, name);
return ps;
}, keyHolder);
// keyHolder.getKey() now contains the generated key
val INSERT_SQL = "insert into my_test (name) values(?)"
val name = "Rob"
val keyHolder = GeneratedKeyHolder()
jdbcTemplate.update({
it.prepareStatement (INSERT_SQL, arrayOf("id")).apply { setString(1, name) }
}, keyHolder)
// keyHolder.getKey() now contains the generated key
3.4. 控制数据库连接
本节涵盖:
3.4.1. 使用DataSource
Spring 通过DataSource
.一个DataSource
是
JDBC 规范的一部分,是一个通用的连接工厂。它允许
容器或框架隐藏连接池和事务管理问题
从应用程序代码。作为开发人员,您无需了解有关如何
连接到数据库。这是设置的管理员的责任
数据源。在开发和测试代码时,您很可能会同时担任这两个角色,但您
不一定知道生产数据源的配置方式。
当您使用 Spring 的 JDBC 层时,您可以从 JNDI 获取数据源,也可以
使用第三方提供的连接池实现配置您自己的连接池实现。
传统的选择是 Apache Commons DBCP 和 C3P0 with bean styleDataSource
类;
对于现代 JDBC 连接池,请考虑使用 HikariCP 及其构建器风格的 API。
您应该使用DriverManagerDataSource 和SimpleDriverDataSource 类
(包含在 Spring 发行版中)仅用于测试目的!这些变体没有
提供池,并且在发出多个连接请求时性能不佳。 |
以下部分使用 Spring 的DriverManagerDataSource
实现。
其他几个DataSource
稍后将介绍变体。
要配置DriverManagerDataSource
:
-
获取与
DriverManagerDataSource
因为您通常会获得 JDBC 连接。 -
指定 JDBC 驱动程序的完全限定类名,以便
DriverManager
可以加载驱动程序类。 -
提供 JDBC 驱动程序之间的 URL。(请参阅驱动程序的文档 以获取正确的值。
-
提供用户名和密码以连接到数据库。
以下示例演示如何配置DriverManagerDataSource
在 Java 中:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
val dataSource = DriverManagerDataSource().apply {
setDriverClassName("org.hsqldb.jdbcDriver")
url = "jdbc:hsqldb:hsql://localhost:"
username = "sa"
password = ""
}
以下示例显示了相应的 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
这DataSourceUtils
class 是一个方便而强大的辅助类,它提供了static
从 JNDI 获取连接并在必要时关闭连接的方法。
它支持线程绑定的 JDBCConnection
跟DataSourceTransactionManager
但
还与JtaTransactionManager
和JpaTransactionManager
.
请注意JdbcTemplate
意味 着DataSourceUtils
连接访问, 使用它
在每个 JDBC作背后,隐式参与正在进行的事务。
3.4.3. 实现SmartDataSource
这SmartDataSource
接口应该由可以提供与关系数据库的连接。它扩展了DataSource
接口让使用它的类会查询是否应该在给定的 操作。 当您知道需要重用连接时,这种用法非常有效。
3.4.4. 扩展AbstractDataSource
AbstractDataSource
是一个abstract
Spring 的DataSource
实现。 它实现了所有人通用的代码DataSource
实现。 您应该扩展AbstractDataSource
class 如果你自己写的话DataSource
实现。
3.4.5. 使用SingleConnectionDataSource
这SingleConnectionDataSource
class 是SmartDataSource
将单个Connection
每次使用后不会关闭。这不支持多线程。
如果任何客户端代码调用close
假设有池连接(如使用持久化工具时),您应该将suppressClose
属性设置为true
. 此设置返回一个封装物理连接的紧密抑制代理。请注意,您可以不再将其转换为本机 OracleConnection
或类似对象。
SingleConnectionDataSource
主要是一个测试类。它通常可以轻松测试
应用程序服务器外部的代码,结合简单的 JNDI 环境。
与DriverManagerDataSource
,它一直重复使用相同的连接,
避免过度创建物理连接。
3.4.6. 使用DriverManagerDataSource
这DriverManagerDataSource
class 是标准的实现DataSource
接口,该接口通过 bean 属性配置一个普通的 JDBC 驱动程序,并返回一个新的Connection
每次。
此实现对于 Java EE 之外的测试和独立环境非常有用
容器,可以作为DataSource
Spring IoC 容器中的 bean 或结合使用
使用简单的 JNDI 环境。池假设Connection.close()
调用
关闭连接,因此任何DataSource
-aware 持久性代码应该可以工作。然而
使用 JavaBean 样式的连接池(例如commons-dbcp
)是如此简单,即使在测试中也是如此
环境,几乎总是最好使用这样的连接池而不是DriverManagerDataSource
.
3.4.7. 使用TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy
是目标的代理DataSource
.代理将
目标DataSource
以增加对 Spring 管理的事务的认识。在这方面,它
类似于事务性 JNDIDataSource
,由 Java EE 服务器提供。
很少需要使用此类,除非现有代码必须
调用并传递标准 JDBCDataSource 接口实现。在这种情况下,
您仍然可以使用此代码,同时拥有此代码
参与 Spring 管理的事务。通常最好将您的
使用更高级别的抽象进行资源管理来拥有新代码,例如JdbcTemplate 或DataSourceUtils . |
请参阅TransactionAwareDataSourceProxy
javadoc 了解更多详情。
3.4.8. 使用DataSourceTransactionManager
/ JdbcTransactionManager
这DataSourceTransactionManager
class 是一个PlatformTransactionManager
单个 JDBC 的实现DataSource
.它绑定一个 JDBCConnection
从指定的DataSource
到当前正在执行的线程,可能
允许一个线程绑定Connection
每DataSource
.
需要应用程序代码才能检索 JDBCConnection
通过DataSourceUtils.getConnection(DataSource)
而不是 Java EE 的标准DataSource.getConnection
.它不受检查地抛出org.springframework.dao
异常
而不是选中SQLExceptions
.所有框架类(例如JdbcTemplate
)使用
这种策略隐含。如果不与事务管理器一起使用,则查找策略
行为与DataSource.getConnection
因此可以在任何情况下使用。
这DataSourceTransactionManager
类支持保存点 (PROPAGATION_NESTED
),
自定义隔离级别,以及作为适当 JDBC 语句应用的超时
查询超时。要支持后者,应用程序代码必须使用JdbcTemplate
或
调用DataSourceUtils.applyTransactionTimeout(..)
方法。
您可以使用DataSourceTransactionManager
而不是JtaTransactionManager
在
单资源情况,因为它不需要容器支持 JTA 事务
协调者。在这些事务管理器之间切换只是配置问题,
前提是您坚持所需的连接查找模式。请注意,JTA 不支持
保存点或自定义隔离级别,并且具有不同的超时机制,但除此之外
在 JDBC 资源和 JDBC 提交/回滚管理方面公开了类似的行为。
从 5.3 开始,Spring 提供了一个扩展的 在异常行为方面, |
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): IntArray {
return jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
object: BatchPreparedStatementSetter {
override fun setValues(ps: PreparedStatement, i: Int) {
ps.setString(1, actors[i].firstName)
ps.setString(2, actors[i].lastName)
ps.setLong(3, actors[i].id)
}
override fun getBatchSize() = actors.size
})
}
// ... additional methods
}
如果您处理更新流或从文件中读取,则可能会有
首选批次大小,但最后一个批次可能没有该数量的条目。在这个
case 中,您可以使用InterruptibleBatchPreparedStatementSetter
接口,这允许
一旦输入源用尽,您就会中断批处理。这isBatchExhausted
方法
允许您发出批次结束的信号。
3.5.2. 使用对象列表的批处理作
两个JdbcTemplate
和NamedParameterJdbcTemplate
提供了另一种方式
提供批量更新。您无需实现特殊的批处理接口,而是
将调用中的所有参数值作为列表提供。框架循环这些
值,并使用内部准备好的语句 setter。API 因
是否使用命名参数。对于命名参数,请提供SqlParameterSource
,批处理的每个成员一个条目。您可以使用SqlParameterSourceUtils.createBatch
方便的方法来创建此数组,将
在 bean 样式对象数组中(具有与参数对应的 getter 方法),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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): IntArray {
return this.namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
SqlParameterSourceUtils.createBatch(actors));
}
// ... additional methods
}
对于使用 classic 的 SQL 语句?
占位符,您传入一个列表包含一个带有更新值的对象数组。此对象数组必须有一个条目对于 SQL 语句中的每个占位符,并且它们的顺序必须与它们相同在 SQL 语句中定义。
以下示例与前面的示例相同,只是它使用经典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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): IntArray {
val batch = mutableListOf<Array<Any>>()
for (actor in actors) {
batch.add(arrayOf(actor.firstName, actor.lastName, actor.id))
}
return jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?", batch)
}
// ... additional methods
}
我们之前描述的所有批量更新方法都会返回一个int
数组
包含每个批处理条目的受影响行数。此计数由
JDBC 驱动程序。如果计数不可用,则 JDBC 驱动程序将返回-2
.
在这种情况下,在基础上自动设置值 或者,您可以考虑显式指定相应的 JDBC 类型,
要么通过 |
3.5.3. 具有多个批次的批处理作
前面的批处理更新示例处理的批处理量太大,以至于您想要
将它们分成几个较小的批次。您可以使用以下方法来做到这一点
前面提到的,通过对batchUpdate
方法,但现在有一个
更方便的方法。除了 SQL 语句之外,此方法还采用Collection
包含参数的对象数,要对每个对象进行的更新次数
batch,以及一个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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): Array<IntArray> {
return jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors, 100) { ps, argument ->
ps.setString(1, argument.firstName)
ps.setString(2, argument.lastName)
ps.setLong(3, argument.id)
}
}
// ... additional methods
}
此调用的批量更新方法返回一个数组int
数组,其中包含
每个批次的数组条目,其中包含每次更新的受影响行数的数组。
顶级数组的长度表示运行的批次数,第二级
数组的长度表示该批次中的更新次数。中的更新次数
每个批次都应是为所有批次提供的批次大小(最后一个批次除外
这可能会更少),具体取决于提供的更新对象总数。更新
每个更新语句的计数是 JDBC 驱动程序报告的。如果计数为
不可用,则 JDBC 驱动程序返回-2
.
3.6. 使用SimpleJdbc
类
这SimpleJdbcInsert
和SimpleJdbcCall
类提供简化的配置
通过利用可通过 JDBC 驱动程序检索的数据库元数据。
这意味着您可以预先配置更少的内容,尽管您可以覆盖或关闭
元数据处理,如果您希望在代码中提供所有详细信息。
3.6.1. 使用SimpleJdbcInsert
我们首先查看SimpleJdbcInsert
类,其中
配置选项。您应该实例化SimpleJdbcInsert
在数据访问中
层的初始化方法。对于此示例,初始化方法是setDataSource
方法。您不需要将SimpleJdbcInsert
类。相反
您可以创建一个新实例,并使用withTableName
方法。 此类的配置方法遵循fluid
style 返回实例的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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
fun add(actor: Actor) {
val parameters = mutableMapOf<String, Any>()
parameters["id"] = actor.id
parameters["first_name"] = actor.firstName
parameters["last_name"] = actor.lastName
insertActor.execute(parameters)
}
// ... additional methods
}
这execute
这里使用的方法采用普通java.util.Map
作为其唯一参数。这
这里需要注意的重要一点是,用于Map
必须与列匹配
数据库中定义的表的名称。这是因为我们读取了元数据
构造实际的 INSERT 语句。
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor").usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
使用第二种方法运行插入时的主要区别在于,您没有
添加id
到Map
,然后调用executeAndReturnKey
方法。这将返回一个java.lang.Number
对象,您可以使用该对象创建数字类型的实例,该
用于您的域类。您不能依赖所有数据库返回特定的 Java
class 在这里。java.lang.Number
是您可以依赖的基类。如果您有
多个自动生成的列或生成的值是非数字的,您可以
使用KeyHolder
从executeAndReturnKeyHolder
方法。
3.6.3. 为SimpleJdbcInsert
您可以通过使用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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
插入的执行与依赖元数据来确定相同 要使用哪些列。
3.6.4. 使用SqlParameterSource
提供参数值
使用Map
提供参数值可以正常工作,但不是最方便的
类。Spring 提供了SqlParameterSource
界面。第一个是BeanPropertySqlParameterSource
,
如果您有一个符合 JavaBean 的类,其中包含
你的价值观。它使用相应的 getter 方法来提取参数
值。以下示例演示如何使用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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
另一种选择是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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = MapSqlParameterSource()
.addValue("first_name", actor.firstName)
.addValue("last_name", actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
如您所见,配置是相同的。只有执行的代码必须更改为 使用这些替代输入类。
3.6.5. 使用SimpleJdbcCall
这SimpleJdbcCall
类使用数据库中的元数据来查找in
和out
参数,这样您就不必显式声明它们。您可以
声明参数,如果你愿意这样做,或者你有参数(例如ARRAY
或STRUCT
)没有自动映射到 Java 类。第一个例子
显示了一个简单的过程,该过程仅返回VARCHAR
和DATE
格式
来自 MySQL 数据库。示例过程读取指定的执行组件条目并返回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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
fun readActor(id: Long): Actor {
val source = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(source)
return Actor(
id,
output["out_first_name"] as String,
output["out_last_name"] as String,
output["out_birth_date"] as Date)
}
// ... additional methods
}
您为执行调用编写的代码涉及创建SqlParameterSource
包含 IN 参数。您必须匹配为输入值提供的名称
替换为存储过程中声明的参数名称。案例没有
匹配,因为您使用元数据来确定应如何引用数据库对象
在存储过程中。在存储过程的源中指定的内容不是
必然是它在数据库中的存储方式。某些数据库将名称转换为
大写,而其他人则使用小写或按指定大小写使用。
这execute
方法采用 IN 参数并返回Map
包含任何out
参数由名称键,如存储过程中所指定。在这种情况下,它们是out_first_name
,out_last_name
和out_birth_date
.
最后一部分execute
方法会创建一个Actor
实例,用于返回
检索到的数据。同样,使用out
参数作为它们
在存储过程中声明。此外,名称中的大小写out
结果映射中存储的参数与out
参数名称
数据库,这可能因数据库而异。为了使您的代码更具可移植性,您应该
进行不区分大小写的查找或指示 Spring 使用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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
// ... additional methods
}
通过执行此作,您可以避免在用于您的名称的大小写中发生冲突
返回out
参数。
3.6.6. 显式声明要用于SimpleJdbcCall
在本章的前面,我们描述了如何从元数据中推导出参数,但您可以声明它们
如果您愿意,请明确使用。您可以通过创建和配置SimpleJdbcCall
跟
这declareParameters
方法,该方法采用可变数SqlParameter
对象
作为输入。有关如何定义SqlParameter
.
如果您使用的数据库不是 Spring 支持的数据库,则需要显式声明 数据库。目前,Spring 支持对 以下数据库:Apache Derby、DB2、MySQL、Microsoft SQL Server、Oracle 和 Sybase。 我们还支持 MySQL、Microsoft SQL Server、 和甲骨文。 |
您可以选择显式声明一个、部分或所有参数。参数
元数据在未显式声明参数的情况下仍会使用。要绕过所有
处理潜在参数的元数据查找,并仅使用声明的
参数,可以调用withoutProcedureColumnMetaDataAccess
作为
声明。假设您为
数据库函数。在这种情况下,调用useInParameterNames
指定列表
给定签名要包含的 IN 参数名称。
以下示例显示了完全声明的过程调用,并使用来自 前面的示例:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SqlOutParameter("out_last_name", Types.VARCHAR),
SqlOutParameter("out_birth_date", Types.DATE)
)
// ... additional methods
}
两个示例的执行和最终结果是相同的。第二个示例指定 显式细节,而不是依赖元数据。
3.6.7. 如何定义SqlParameters
要定义参数SimpleJdbc
类以及 RDBMS作
类(在将 JDBC作建模为 Java 对象中介绍),您可以使用SqlParameter
或其子类之一。
为此,通常在构造函数中指定参数名称和 SQL 类型。SQL 类型
通过使用java.sql.Types
常数。在本章前面,我们看到了声明
类似于以下内容:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
第一行SqlParameter
声明一个 IN 参数。您可以使用 IN 参数
对于存储过程调用和查询,使用SqlQuery
及其
子类(涵盖在理解SqlQuery
).
第二行(带有SqlOutParameter
) 声明out
参数,用于
存储过程调用。还有一个SqlInOutParameter
为InOut
参数
(为过程提供 IN 值并返回值的参数)。
仅声明为SqlParameter 和SqlInOutParameter 习惯于
提供输入值。这与StoredProcedure 类,其中(对于
向后兼容的原因)允许为参数提供输入值
声明为SqlOutParameter . |
对于 IN 参数,除了名称和 SQL 类型外,您还可以指定
自定义数据库类型的数字数据或类型名称。为out
参数,您可以
提供一个RowMapper
处理从REF
光标。另一个
选项是指定一个SqlReturnType
这提供了定义
返回值的自定义处理。
3.6.8. 使用SimpleJdbcCall
您可以像调用存储过程一样调用存储函数,但
提供函数名称而不是过程名称。您可以使用withFunctionName
方法作为配置的一部分,以指示您要使
调用函数,并生成函数调用的相应字符串。一个
专业呼叫 (executeFunction
) 用于运行该函数,并且它
将函数返回值作为指定类型的对象返回,这意味着您
不必从结果映射中检索返回值。类似的便利方法
(名为executeObject
) 也可用于只有一个out
参数。以下示例(对于 MySQL)基于名为get_actor_name
返回 actor 的全名:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}
private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name")
fun getActorName(id: Long): String {
val source = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, source)
}
// ... additional methods
}
这executeFunction
使用的方法返回一个String
包含来自函数调用的返回值。
3.6.9. 返回ResultSet
或 REF 光标SimpleJdbcCall
调用返回结果集的存储过程或函数有点棘手。 一些 数据库在 JDBC 结果处理期间返回结果集,而其他数据库则需要显式注册out
特定类型的参数。这两种方法都需要额外的处理来循环结果集并处理返回的行。 跟 这SimpleJdbcCall
,您可以使用returningResultSet
方法并声明一个RowMapper
用于特定参数的实现。如果结果集是返回的结果,则没有定义任何名称,因此返回的results 必须与您声明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;
要调用此过程,您可以声明RowMapper
. 因为要映射到的类遵循 JavaBean 规则,所以您可以使用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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
fun getActorsList(): List<Actor> {
val m = procReadAllActors.execute(mapOf<String, Any>())
return m["actors"] as List<Actor>
}
// ... additional methods
}
这execute
呼叫传递在空的Map
,因为此调用不采用任何参数。然后从结果映射中检索参与者列表并返回给调用者。
3.7. 将JDBC作建模为Java对象
这org.springframework.jdbc.object
package 包含允许您以更面向对象的方式访问数据库的类。例如,您可以运行查询并将结果作为列表返回,其中包含具有关系列数据映射到业务对象属性的业务对象。您还可以运行存储的过程并运行 update、delete 和 insert 语句。
许多 Spring 开发人员认为,下面描述的各种 RDBMS作类(除了 但是,如果您从使用 RDBMS作类中获得可衡量的价值,您应该继续使用这些类。 |
3.7.1. 理解SqlQuery
SqlQuery
是一个可重用的线程安全类,用于封装 SQL 查询。 子 必须实现newRowMapper(..)
方法提供RowMapper
实例可以通过迭代获得的每行创建一个对象ResultSet
创建的在执行查询期间。 这SqlQuery
class 很少直接使用,因为 这MappingSqlQuery
子类提供了一个更方便的实现将行映射到 Java 类。其他扩展的实现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;
}
}
class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") {
init {
declareParameter(SqlParameter("id", Types.INTEGER))
compile()
}
override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name")
)
}
该类扩展MappingSqlQuery
参数化为Actor
类型。构造函数
对于此客户查询,查询采用DataSource
作为唯一的参数。在此构造函数中,您可以使用DataSource
和 SQL应运行以检索此查询的行。此 SQL 用于创建一个PreparedStatement
,因此它可能包含任何参数的占位符在执行过程中传入。您必须使用declareParameter
方法传入SqlParameter
.这SqlParameter
采用名称,JDBC 类型
如java.sql.Types
.定义所有参数后,可以调用compile()
方法,以便可以准备语句并稍后运行。这个类是
编译后线程安全,因此,只要在 DAO
初始化时,它们可以保留为实例变量并重用。以下内容
示例显示了如何定义这样的类:
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
前面示例中的方法检索客户,其中包含id
作为
only 参数。由于我们只希望返回一个对象,因此我们调用findObject
方便
方法与id
作为参数。如果我们有一个返回
列表并采用其他参数,我们将使用execute
接受作为 varargs 传递的参数值数组的方法。以下内容
示例显示了这样的方法:
public List<Actor> searchForActors(int age, String namePattern) {
List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
return actors;
}
fun searchForActors(age: Int, namePattern: String) =
actorSearchMappingQuery.execute(age, namePattern)
3.7.3. 使用SqlUpdate
这SqlUpdate
class 封装 SQL 更新。与查询一样,更新对象是
可重用,并且,与所有RdbmsOperation
类,更新可以有参数,并且是
在 SQL 中定义。此类提供了许多update(..)
类似于execute(..)
查询对象的方法。这SqlUpdate
class 是具体的。它可以是
subclassed — 例如,添加自定义更新方法。
但是,您不必将SqlUpdate
类,因为它可以通过设置 SQL 和声明参数轻松参数化。
以下示例创建一个名为execute
:
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
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);
}
}
import java.sql.Types
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object
.SqlUpdate
class UpdateCreditRating(ds: DataSource) : SqlUpdate() {
init {
setDataSource(ds)
sql = "update customer set credit_rating = ? where id = ?"
declareParameter(SqlParameter("creditRating", Types.NUMERIC))
declareParameter(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
*/
fun execute(id: Int, rating: Int): Int {
return update(rating, id)
}
}
3.7.4. 使用StoredProcedure
这StoredProcedure
class 是一个abstract
用于 RDBMS 对象抽象的超类
存储过程。
继承的sql
属性是 RDBMS 中存储过程的名称。
要定义参数StoredProcedure
类,您可以使用SqlParameter
或一个
其子类。您必须在构造函数中指定参数名称和 SQL 类型,
如以下代码片段所示:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SQL 类型是使用java.sql.Types
常数。
第一行(使用SqlParameter
) 声明一个 IN 参数。您可以使用 IN 参数
对于存储过程调用和使用SqlQuery
及其
子类(涵盖在理解SqlQuery
).
第二行(带有SqlOutParameter
) 声明out
参数用于
存储过程调用。还有一个SqlInOutParameter
为InOut
参数
(提供in
value 添加到过程中,并且也返回一个值)。
为in
参数,除了名称和 SQL 类型之外,您还可以指定一个
数值数据的缩放或自定义数据库类型的类型名称。为out
参数
你可以提供一个RowMapper
处理从REF
光标。
另一种选择是指定SqlReturnType
允许您定义自定义
返回值的处理。
简单 DAO 的下一个示例使用StoredProcedure
调用函数
(sysdate()
),它随任何 Oracle 数据库一起提供。使用存储过程
功能,您必须创建一个扩展StoredProcedure
.在这个
示例,StoredProcedure
class 是一个内部类。但是,如果您需要重用StoredProcedure
,您可以将其声明为顶级类。此示例没有输入
参数,但输出参数通过使用SqlOutParameter
类。这execute()
方法运行该过程并提取
从结果返回的日期Map
.结果Map
每个声明的条目都有一个条目
输出参数(在本例中仅使用一个)使用参数名称作为键。
以下列表显示了我们的自定义 StoredProcedure 类:
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
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;
}
}
}
import java.sql.Types
import java.util.Date
import java.util.Map
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure
class StoredProcedureDao(dataSource: DataSource) {
private val SQL = "sysdate"
private val getSysdate = GetSysdateProcedure(dataSource)
val sysdate: Date
get() = getSysdate.execute()
private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() {
init {
setDataSource(dataSource)
isFunction = true
sql = SQL
declareParameter(SqlOutParameter("date", Types.DATE))
compile()
}
fun execute(): Date {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
val results = execute(mutableMapOf<String, Any>())
return results["date"] as Date
}
}
}
以下示例StoredProcedure
有两个输出参数(在本例中,
Oracle REF 游标):
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.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>());
}
}
import java.util.HashMap
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object
.StoredProcedure
class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "AllTitlesAndGenres"
}
init {
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper()))
compile()
}
fun execute(): Map<String, Any> {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(HashMap<String, Any>())
}
}
请注意declareParameter(..)
方法已经
用于TitlesAndGenresStoredProcedure
构造函数被传递RowMapper
实现实例。这是一种非常方便且强大的重用现有方法
功能性。接下来的两个示例提供了两个RowMapper
实现。
这TitleMapper
类映射ResultSet
设置为Title
domain 中每一行的对象
提供的ResultSet
如下:
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;
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;
}
}
import java.sql.ResultSet
import com.foo.domain.Title
import org.springframework.jdbc.core.RowMapper
class TitleMapper : RowMapper<Title> {
override fun mapRow(rs: ResultSet, rowNum: Int) =
Title(rs.getLong("id"), rs.getString("name"))
}
这GenreMapper
类映射ResultSet
设置为Genre
domain 中每一行的对象
提供的ResultSet
如下:
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;
public final class GenreMapper implements RowMapper<Genre> {
public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}
import java.sql.ResultSet
import com.foo.domain.Genre
import org.springframework.jdbc.core.RowMapper
class GenreMapper : RowMapper<Genre> {
override fun mapRow(rs: ResultSet, rowNum: Int): Genre {
return Genre(rs.getString("name"))
}
}
将参数传递给具有一个或多个输入参数的存储过程
RDBMS 中的定义,您可以编写一个强类型的execute(..)
方法将
委托给非类型execute(Map)
方法,如以下示例所示:
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;
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);
}
}
import java.sql.Types
import java.util.Date
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object
.StoredProcedure
class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "TitlesAfterDate"
private const val CUTOFF_DATE_PARAM = "cutoffDate"
}
init {
declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE))
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
compile()
}
fun execute(cutoffDate: Date) = super.execute(
mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
}
3.8. 参数和数据值处理的常见问题
不同方法中存在参数和数据值的常见问题 由 Spring Framework 的 JDBC 支持提供。本节介绍如何解决这些问题。
3.8.1. 为参数提供 SQL 类型信息
通常,Spring 会根据参数的类型确定参数的 SQL 类型
通过了。可以显式提供在设置时要使用的 SQL 类型
参数值。有时需要正确设置NULL
值。
您可以通过多种方式提供 SQL 类型信息:
-
许多更新和查询方法
JdbcTemplate
在 的int
数组。此数组用于指示 相应的参数,使用来自java.sql.Types
类。提供 每个参数一个条目。 -
您可以使用
SqlParameterValue
类来包装需要此参数值 附加信息。为此,请为每个值创建一个新实例并传入 SQL 类型 以及构造函数中的参数值。您还可以提供可选的比例 参数。 -
对于使用命名参数的方法,您可以使用
SqlParameterSource
类BeanPropertySqlParameterSource
或MapSqlParameterSource
.他们都有方法 用于注册任何命名参数值的 SQL 类型。
3.8.2. 处理 BLOB 和 CLOB 对象
您可以在数据库中存储图像、其他二进制数据和大块文本。这些
大对象称为二进制数据的 BLOB(Binary Large OBject),而 CLOB(字符
Large OBject) 来获取字符数据。在 Spring 中,您可以使用以下命令来处理这些大型对象
这JdbcTemplate
直接使用RDBMS提供的高级抽象时也是如此
Objects 和SimpleJdbc
类。所有这些方法都使用
这LobHandler
用于实际管理 LOB(大型 OBject)数据的接口。LobHandler
提供对LobCreator
类,通过getLobCreator
方法
用于创建要插入的新 LOB 对象。
LobCreator
和LobHandler
为 LOB 输入和输出提供以下支持:
-
斑点
-
byte[]
:getBlobAsBytes
和setBlobAsBytes
-
InputStream
:getBlobAsBinaryStream
和setBlobAsBinaryStream
-
-
CLOB
-
String
:getClobAsString
和setClobAsString
-
InputStream
:getClobAsAsciiStream
和setClobAsAsciiStream
-
Reader
:getClobAsCharacterStream
和setClobAsCharacterStream
-
下一个示例演示如何创建和插入 BLOB。稍后我们将展示如何阅读 它从数据库中返回。
此示例使用JdbcTemplate
以及AbstractLobCreatingPreparedStatementCallback
.它实现了一种方法setValues
.此方法提供了一个LobCreator
我们用来设置
LOB 列。
对于这个例子,我们假设有一个变量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) { (1)
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
ps.setLong(1, 1L);
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); (2)
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); (3)
}
}
);
blobIs.close();
clobReader.close();
1 | 传入lobHandler (在本例中)是一个普通的DefaultLobHandler . |
2 | 使用该方法setClobAsCharacterStream 传入 CLOB 的内容。 |
3 | 使用该方法setBlobAsBinaryStream 传入 BLOB 的内容。 |
val blobIn = File("spring2004.jpg")
val blobIs = FileInputStream(blobIn)
val clobIn = File("large.txt")
val clobIs = FileInputStream(clobIn)
val clobReader = InputStreamReader(clobIs)
jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
object: AbstractLobCreatingPreparedStatementCallback(lobHandler) { (1)
override fun setValues(ps: PreparedStatement, lobCreator: LobCreator) {
ps.setLong(1, 1L)
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, clobIn.length().toInt()) (2)
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, blobIn.length().toInt()) (3)
}
}
)
blobIs.close()
clobReader.close()
1 | 传入lobHandler (在本例中)是一个普通的DefaultLobHandler . |
2 | 使用该方法setClobAsCharacterStream 传入 CLOB 的内容。 |
3 | 使用该方法setBlobAsBinaryStream 传入 BLOB 的内容。 |
如果调用 请参阅用于验证其是否支持流式处理的 JDBC 驱动程序的文档 一个 LOB,但不提供内容长度。 |
现在是时候从数据库中读取 LOB 数据了。同样,您使用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"); (1)
results.put("CLOB", clobText);
byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); (2)
results.put("BLOB", blobBytes);
return results;
}
});
1 | 使用该方法getClobAsString 检索 CLOB 的内容。 |
2 | 使用该方法getBlobAsBytes 检索 BLOB 的内容。 |
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ ->
val clobText = lobHandler.getClobAsString(rs, "a_clob") (1)
val blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob") (2)
mapOf("CLOB" to clobText, "BLOB" to blobBytes)
}
1 | 使用该方法getClobAsString 检索 CLOB 的内容。 |
2 | 使用该方法getBlobAsBytes 检索 BLOB 的内容。 |
3.8.3. 传入 IN 子句的值列表
SQL 标准允许根据包含
变量值列表。一个典型的例子是select * from t_actor where id in
(1, 2, 3)
.预处理语句不直接支持此变量列表
JDBC 标准。不能声明可变数量的占位符。您需要一个号码
的变体,并准备了所需数量的占位符,或者您需要生成
一旦您知道需要多少个占位符,就会动态地显示 SQL 字符串。命名的
参数支持NamedParameterJdbcTemplate
采取后一种方法。
您可以将值作为java.util.List
(或任何Iterable
) 的简单值。
此列表用于将所需的占位符插入到实际的 SQL 语句中
并在语句执行期间传入值。
传入许多值时要小心。JDBC 标准不保证
您可以对IN 表达式列表。各种数据库超过
这个数字,但它们通常对允许的值数有硬性限制。
例如,Oracle 的限制是 1000。 |
除了值列表中的基元值外,还可以创建java.util.List
对象数组。此列表可以支持为in
子句,例如select * from t_actor where (id, last_name) in ((1, 'Johnson'), (2,
'Harrop'))
.当然,这需要您的数据库支持此语法。
3.8.4. 处理存储过程调用的复杂类型
调用存储过程时,有时可以使用特定于
数据库。为了适应这些类型,Spring 提供了一个SqlReturnType
用于搬运
它们从存储过程调用返回时,以及SqlTypeValue
当他们
作为参数传递给存储过程。
这SqlReturnType
接口有一个方法(名为getTypeValue
) 必须是
实现。此接口用作声明的一部分SqlOutParameter
.
以下示例演示返回 Oracle 的值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;
}));
// ...
}
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
init {
// ...
declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
val struct = cs.getObject(colIndx) as STRUCT
val attr = struct.getAttributes()
TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
})
// ...
}
}
您可以使用SqlTypeValue
传递 Java 对象的值(例如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;
}
};
val (id, description, expirationDate) = TestItem(123L, "A test item",
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
val value = object : AbstractSqlTypeValue() {
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
val itemDescriptor = StructDescriptor(typeName, conn)
return STRUCT(itemDescriptor, conn,
arrayOf(id, description, java.sql.Date(expirationDate.time)))
}
}
您现在可以添加此SqlTypeValue
到Map
包含execute
调用存储过程。
的另一个用途SqlTypeValue
将值数组传递给存储的 Oracle
程序。Oracle 有自己的内部ARRAY
在这种情况下必须使用的类,以及
您可以使用SqlTypeValue
创建 Oracle 的实例ARRAY
并填充
it 与 Java 中的值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;
}
};
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
init {
val ids = arrayOf(1L, 2L)
val value = object : AbstractSqlTypeValue() {
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
val arrayDescriptor = ArrayDescriptor(typeName, conn)
return ARRAY(arrayDescriptor, conn, ids)
}
}
}
}
3.9. 嵌入式数据库支持
这org.springframework.jdbc.datasource.embedded
package 提供对嵌入式的支持
Java 数据库引擎。提供对 HSQL、H2 和 Derby 的支持
本地。您还可以使用可扩展的 API 来插入新的嵌入式数据库类型,并DataSource
实现。
3.9.2. 使用 Spring XML 创建嵌入式数据库
如果要将嵌入式数据库实例公开为 Spring 中的 beanApplicationContext
,您可以使用embedded-database
标记中的spring-jdbc
Namespace:
<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 数据库,该数据库填充了来自
这schema.sql
和test-data.sql
类路径根目录中的资源。此外,作为
最佳做法是为嵌入式数据库分配一个唯一生成的名称。这
嵌入式数据库作为类型javax.sql.DataSource
然后可以根据需要将其注入到数据访问对象中。
3.9.3. 以编程方式创建嵌入式数据库
这EmbeddedDatabaseBuilder
class 提供了一个流畅的 API,用于构建嵌入式
数据库。当您需要在
独立环境或独立集成测试,如以下示例所示:
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()
val db = 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()
请参阅javadoc 的EmbeddedDatabaseBuilder
有关所有支持选项的更多详细信息。
您还可以使用EmbeddedDatabaseBuilder
使用 Java 创建嵌入式数据库
配置,如以下示例所示:
@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();
}
}
@Configuration
class DataSourceConfig {
@Bean
fun dataSource(): DataSource {
return 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
Spring 支持 HSQL 1.8.0 及更高版本。如果没有类型
显式指定。要显式指定 HSQL,请将type
属性的embedded-database
标签设置为HSQL
.如果您使用构建器 API,请调用setType(EmbeddedDatabaseType)
方法EmbeddedDatabaseType.HSQL
.
3.9.5. 使用嵌入式数据库测试数据访问逻辑
嵌入式数据库提供了一种轻量级的方法来测试数据访问代码。下一个示例是
使用嵌入式数据库的数据访问集成测试模板。使用这样的模板
当嵌入式数据库不需要在测试中重用时,对于一次性可能很有用
类。但是,如果您希望创建在测试套件中共享的嵌入式数据库,
考虑使用 Spring TestContext 框架和
在 Spring 中将嵌入式数据库配置为 beanApplicationContext
如所述
使用 Spring XML 创建嵌入式数据库和以编程方式创建嵌入式数据库。以下列表
显示测试模板:
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();
}
}
class DataAccessIntegrationTestTemplate {
private lateinit var db: EmbeddedDatabase
@BeforeEach
fun setUp() {
// creates an HSQL in-memory database populated from default scripts
// classpath:schema.sql and classpath:data.sql
db = EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.addDefaultScripts()
.build()
}
@Test
fun testDataAccess() {
val template = JdbcTemplate(db)
template.query( /* ... */)
}
@AfterEach
fun tearDown() {
db.shutdown()
}
}
3.9.6. 为嵌入式数据库生成唯一名称
开发团队经常会遇到嵌入式数据库的错误,如果他们的测试套件
无意中尝试重新创建同一数据库的其他实例。这可以
如果 XML 配置文件或@Configuration
阶级负责
用于创建嵌入式数据库,然后重复使用相应的配置
跨同一测试套件中的多个测试场景(即在同一 JVM 中)
process) — 例如,针对嵌入式数据库的集成测试,其ApplicationContext
配置仅与哪个 Bean 定义不同
配置文件处于活动状态。
此类错误的根本原因是 Spring 的EmbeddedDatabaseFactory
(用过
在内部由<jdbc:embedded-database>
XML 命名空间元素和EmbeddedDatabaseBuilder
对于 Java 配置)将嵌入式数据库的名称设置为testdb
如无特别说明。对于以下情况<jdbc:embedded-database>
这
嵌入式数据库通常被分配一个与 bean 的id
(通常,
类似dataSource
).因此,后续尝试创建嵌入式数据库
不会导致新数据库。相反,将重复使用相同的 JDBC 连接 URL,
并且尝试创建新的嵌入式数据库实际上指向现有的
从相同配置创建的嵌入式数据库。
为了解决这个常见问题,Spring Framework 4.2 提供了对生成 嵌入式数据库的唯一名称。要启用生成的名称,请使用 以下选项。
-
EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()
-
EmbeddedDatabaseBuilder.generateUniqueName()
-
<jdbc:embedded-database generate-name="true" … >
3.9.7. 扩展嵌入式数据库支持
您可以通过两种方式扩展 Spring JDBC 嵌入式数据库支持:
-
实现
EmbeddedDatabaseConfigurer
以支持新的嵌入式数据库类型。 -
实现
DataSourceFactory
以支持新的DataSource
实现,例如连接池来管理嵌入式数据库连接。
我们鼓励你在 GitHub Issues 上向 Spring 社区贡献扩展。
3.10. 初始化DataSource
这org.springframework.jdbc.datasource.init
package 提供初始化支持
现有的DataSource
.嵌入式数据库支持提供了一个用于创建
并初始化DataSource
对于应用程序。但是,有时可能需要初始化
在某处的服务器上运行的实例。
3.10.1. 使用 Spring XML 初始化数据库
如果要初始化数据库,并且可以提供对DataSource
bean,您可以使用initialize-database
标记中的spring-jdbc
Namespace:
<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>
前面的示例针对数据库运行两个指定的脚本。第一个
脚本创建一个架构,第二个模板使用测试数据集填充表。脚本
locations 也可以是带有通配符的模式,通常用于资源
在 Spring 中(例如classpath*:/com/foo/**/sql/*-data.sql
).如果您使用
pattern,脚本按其 URL 或文件名的词法顺序运行。
数据库初始值设定项的默认行为是无条件运行提供的 脚本。这可能并不总是您想要的——例如,如果您运行 脚本针对已包含测试数据的数据库。可能性 通过遵循常见模式(如上所示)减少意外删除数据的次数 首先创建表,然后插入数据。如果出现以下情况,第一步将失败 这些表已经存在。
但是,为了更好地控制现有数据的创建和删除,XML 命名空间提供了一些附加选项。第一个是将 初始化打开和关闭。您可以根据环境进行设置(例如将 来自系统属性或环境 Bean 的布尔值)。以下示例从系统属性获取值:
<jdbc:initialize-database data-source="dataSource"
enabled="#{systemProperties.INITIALIZE_DATABASE}"> (1)
<jdbc:script location="..."/>
</jdbc:initialize-database>
1 | 获取enabled 从名为INITIALIZE_DATABASE . |
控制现有数据发生的情况的第二个选项是更宽容 失败。为此,您可以控制初始值设定项忽略某些 错误,如以下示例所示:
<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
<jdbc:script location="..."/>
</jdbc:initialize-database>
在前面的示例中,我们说我们期望有时运行脚本
针对空数据库,并且有一些DROP
脚本中的语句
因此,会失败。所以失败的 SQLDROP
语句将被忽略,但其他失败
将导致异常。如果您的 SQL 方言不支持DROP … IF
EXISTS
(或类似),但您想在之前无条件删除所有测试数据
重新创建它。在这种情况下,第一个脚本通常是一组DROP
语句
后跟一组CREATE
语句。
这ignore-failures
选项可以设置为NONE
(默认值),DROPS
(忽略失败
drops),或ALL
(忽略所有失败)。
如果字符不是,则每个语句都应用或换行符分隔
完全存在于剧本中。您可以全局控制该作,也可以逐个脚本控制该脚本,如
以下示例显示:;
;
<jdbc:initialize-database data-source="dataSource" separator="@@"> (1)
<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> (2)
<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>
1 | 将分隔符脚本设置为 。@@ |
2 | 设置分隔符db-schema.sql 自。; |
在此示例中,两个test-data
脚本使用 as 语句分隔符,并且仅
这@@
db-schema.sql
使用。此配置指定默认分隔符
is 并覆盖;
@@
db-schema
脚本。
如果您需要比从 XML 命名空间获得的更多的控制,您可以使用DataSourceInitializer
直接将其定义为应用程序中的组件。
初始化依赖于数据库的其他组件
一大类应用程序(那些在 Spring 上下文 started)可以使用数据库初始值设定项,无需进一步 并发症。如果您的应用程序不是其中之一,您可能需要阅读其余部分 本节的。
数据库初始值设定项依赖于DataSource
实例并运行脚本
在其初始化回调中提供(类似于init-method
在 XML Bean 中
定义,一个@PostConstruct
组件中的方法,或afterPropertiesSet()
实现InitializingBean
).如果其他 bean 依赖于
相同的数据源,并在初始化回调中使用该数据源,在那里
可能是个问题,因为数据尚未初始化。一个常见的例子
这是一个缓存,它急切地初始化并在应用程序上从数据库加载数据
启动。
若要解决此问题,您有两个选择:更改缓存初始化策略 到稍后的阶段,或者确保首先初始化数据库初始值设定项。
如果应用程序在您的控制范围内,则更改缓存初始化策略可能很容易,否则则不然。 关于如何实现这一点的一些建议包括:
-
使缓存在首次使用时延迟初始化,从而改善应用程序启动 时间。
-
实现缓存或初始化缓存的单独组件
Lifecycle
或SmartLifecycle
.当应用程序上下文启动时,您可以 自动启动SmartLifecycle
通过设置其autoStartup
标志,你可以 手动启动Lifecycle
通过调用ConfigurableApplicationContext.start()
在封闭上下文上。 -
使用弹簧
ApplicationEvent
或类似的自定义观察者机制来触发 缓存初始化。ContextRefreshedEvent
总是由上下文发布 它已准备好使用(在所有 bean 都初始化之后),因此这通常是一个有用的 钩子(这是SmartLifecycle
默认情况下有效)。
确保首先初始化数据库初始值设定项也很容易。关于如何实现这一点的一些建议包括:
-
依赖 Spring 的默认行为
BeanFactory
,即豆子是 按注册顺序初始化。您可以通过采用通用的 一套<import/>
XML 配置中的元素,将 应用程序模块,并确保数据库和数据库初始化 列在第一位。 -
将
DataSource
以及使用它并控制其 通过将它们分开来排序ApplicationContext
实例(例如, parent 上下文包含DataSource
,子上下文包含业务 组件)。这种结构在 Spring Web 应用程序中很常见,但可以更多 普遍适用。
4. 使用 R2DBC 访问数据
R2DBC(“响应式关系数据库连接”)是一种社区驱动的 规范工作,以使用响应式模式标准化对 SQL 数据库的访问。
4.1. 包层次结构
Spring Framework 的 R2DBC 抽象框架由两个不同的包组成:
-
core
:这org.springframework.r2dbc.core
包包含DatabaseClient
班级加上各种相关班级。请参阅使用 R2DBC 核心类控制基本 R2DBC 处理和错误处理。 -
connection
:这org.springframework.r2dbc.connection
包包含一个实用程序类 为了方便ConnectionFactory
访问和各种简单的ConnectionFactory
实现 可用于测试和运行未修改的 R2DBC。请参阅控制数据库连接。
4.2. 使用 R2DBC 核心类控制基本的 R2DBC 处理和错误处理
本节介绍如何使用 R2DBC 核心类来控制基本的 R2DBC 处理, 包括错误处理。它包括以下主题:
4.2.1. 使用DatabaseClient
DatabaseClient
是 R2DBC 核心包中的中心类。它处理
创建和释放资源,这有助于避免常见错误,例如
忘记关闭连接。它执行核心 R2DBC 的基本任务
工作流(如语句创建和执行),让应用程序代码提供
SQL 和提取结果。这DatabaseClient
类:
-
运行 SQL 查询
-
更新语句和存储过程调用
-
执行迭代
Result
实例 -
捕获 R2DBC 异常并将它们转换为通用的、信息量更大的异常 层次结构在
org.springframework.dao
包。(请参阅一致异常层次结构。
客户端有一个功能齐全的流畅 API,使用响应式类型进行声明性组合。
当您使用DatabaseClient
对于您的代码,您只需实现java.util.function
接口,为它们提供明确定义的契约。
给定一个Connection
由DatabaseClient
类,一个Function
回调会创建一个Publisher
.对于映射函数也是如此
提取一个Row
结果。
您可以使用DatabaseClient
通过直接实例化在 DAO 实现中
使用ConnectionFactory
引用,也可以在 Spring IoC 容器中配置它
并将其作为 bean 参考提供给 DAO。
创建DatabaseClient
object 是通过静态工厂方法,如下所示:
DatabaseClient client = DatabaseClient.create(connectionFactory);
val client = DatabaseClient.create(connectionFactory)
这ConnectionFactory 应该始终在 Spring IoC 中配置为 bean
容器。 |
前面的方法创建了一个DatabaseClient
使用默认设置。
您还可以获得Builder
实例来自DatabaseClient.builder()
.
您可以通过调用以下方法自定义客户端:
-
….bindMarkers(…)
:提供特定的BindMarkersFactory
配置命名 参数到数据库绑定标记翻译。 -
….executeFunction(…)
:设置ExecuteFunction
如何Statement
对象获取 跑。 -
….namedParameters(false)
:禁用命名参数扩展。默认启用。
方言由BindMarkersFactoryResolver 从ConnectionFactory ,通常通过检查ConnectionFactoryMetadata .你可以让 Spring 自动发现你的 BindMarkersFactory 通过注册
实现org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider 通过META-INF/spring.factories .BindMarkersFactoryResolver 从
使用 Spring 的SpringFactoriesLoader . |
当前支持的数据库包括:
-
H2
-
玛丽亚数据库
-
Microsoft SQL Server
-
MySQL
-
Postgres
此类发出的所有 SQL 都记录在DEBUG
类别下的级别
对应于客户端实例的完全限定类名(通常DefaultDatabaseClient
).此外,每次执行都会在
响应式序列以帮助调试。
以下部分提供了一些示例DatabaseClient
用法。这些例子
不是DatabaseClient
.
请参阅随附的 javadoc 。
执行语句
DatabaseClient
提供运行语句的基本功能。
以下示例显示了需要包含哪些内容才能实现最小但功能齐全
创建新表的代码:
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.then();
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.await()
DatabaseClient
专为方便、流畅的使用而设计。
它公开了中间方法、延续方法和终端方法的每个阶段
执行规范。上面的示例使用then()
返回完成Publisher
在查询(或查询,如果 SQL 查询包含
多个语句)完成。
execute(…) 接受 SQL 查询字符串或查询Supplier<String> 将实际查询创建推迟到执行。 |
查询 (SELECT
)
SQL 查询可以通过Row
对象或受影响的行数。DatabaseClient
可以返回更新的行数或行本身,
取决于发出的查询。
以下查询获取id
和name
表中的列:
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
.fetch().first();
val first = client.sql("SELECT id, name FROM person")
.fetch().awaitSingle()
以下查询使用绑定变量:
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().first();
val first = client.sql("SELECT id, name FROM person WHERE WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitSingle()
您可能已经注意到fetch()
在上面的例子中。fetch()
是一个
continuation 运算符,用于指定要使用的数据量。
叫first()
返回结果中的第一行并丢弃其余行。
您可以使用以下运算符使用数据:
-
first()
返回整个结果的第一行。其 Kotlin 协程变体 被命名为awaitSingle()
对于不可为 null 的返回值,以及awaitSingleOrNull()
如果该值是可选的。 -
one()
只返回一个结果,如果结果包含更多行,则失败。 使用 Kotlin 协程,awaitOne()
对于一个值或awaitOneOrNull()
如果值可能为null
. -
all()
返回结果的所有行。使用 Kotlin 协程时,请使用flow()
. -
rowsUpdated()
返回受影响的行数 (INSERT
/UPDATE
/DELETE
计数)。其 Kotlin 协程变体名为awaitRowsUpdated()
.
如果不指定进一步的映射详细信息,查询将返回表格结果
如Map
其键是映射到其列值的不区分大小写的列名。
您可以通过提供Function<Row, T>
这得到了
每个Row
因此它可以返回任意值(奇异值,
集合和地图以及对象)。
以下示例提取name
列并发出其值:
Flux<String> names = client.sql("SELECT name FROM person")
.map(row -> row.get("name", String.class))
.all();
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("name", String.class) }
.flow()
更新 (INSERT
,UPDATE
和DELETE
) 替换为DatabaseClient
修改语句的唯一区别是这些语句通常
不要返回表格数据,因此使用rowsUpdated()
消费结果。
以下示例显示了UPDATE
返回数字的语句
更新行数:
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().rowsUpdated();
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
将值绑定到查询
典型的应用程序需要参数化的 SQL 语句来选择或
根据某些输入更新行。这些通常是SELECT
语句
受WHERE
子句或INSERT
和UPDATE
接受的语句
输入参数。参数化语句在以下情况下承担 SQL 注入的风险
参数未正确转义。DatabaseClient
利用 R2DBC 的bind
API 以消除查询参数的 SQL 注入风险。
您可以提供参数化 SQL 语句,其中包含execute(…)
算子
并将参数绑定到实际的Statement
.然后,您的 R2DBC 驱动程序运行
使用准备好的语句和参数替换来调用语句。
参数绑定支持两种绑定策略:
-
按索引,使用从零开始的参数索引。
-
按名称,使用占位符名称。
以下示例显示了查询的参数绑定:
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
名为Collection
参数转换为一系列绑定标记,以消除基于参数数量的动态查询创建的需要。嵌套对象数组被扩展以允许使用(例如)选择列表。
请考虑以下查询:
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
前面的查询可以参数化并按如下方式运行:
List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
选择列表的使用取决于提供商。 |
以下示例显示了一个更简单的变体,使用IN
谓词:
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("tuples", arrayOf(35, 50))
R2DBC 本身不支持类似集合的值。不过
扩展给定的List 在上面的示例中,适用于命名参数
在 Spring 的 R2DBC 支持中,例如用于IN 如上所示的条款。
但是,插入或更新数组类型的列(例如在 Postgres 中)
需要底层 R2DBC 驱动程序支持的数组类型:
通常是 Java 数组,例如String[] 要更新text[] 列。
不通过Collection<String> 或类似作为数组参数。 |
语句过滤器
有时您需要在实际的Statement
在它运行之前。注册一个Statement
Filter
(StatementFilterFunction
) 到DatabaseClient
拦截和
modify 语句,如以下示例所示:
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
.bind("name", …)
.bind("state", …)
DatabaseClient
曝光也简化filter(…)
过载接受Function<Statement, Statement>
:
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
StatementFilterFunction
实现允许过滤Statement
和过滤Result
对象。
DatabaseClient
最佳实践
的实例DatabaseClient
一旦配置,类是线程安全的。 这是 重要,因为这意味着您可以配置DatabaseClient
然后安全地将此共享引用注入多个 DAO(或存储库)。 这DatabaseClient
是有状态的,因为它维护对ConnectionFactory
,
但这种状态不是对话状态。
使用DatabaseClient
class 是配置一个ConnectionFactory
在您的 Spring 配置文件中,然后 dependency-inject共享的ConnectionFactory
bean 加入你的 DAO 类。 这DatabaseClient
创建于的 setterConnectionFactory
. 这导致了类似于以下内容的 DAO:
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory);
}
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {
private val databaseClient = DatabaseClient.create(connectionFactory)
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
显式配置的替代方法是使用组件扫描和注释
支持依赖注入。在这种情况下,您可以使用@Component
(这使其成为组件扫描的候选者)并注释ConnectionFactory
塞特
方法@Autowired
.以下示例显示了如何执行此作:
@Component (1)
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
@Autowired (2)
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory); (3)
}
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 | 用@Component . |
2 | 注释ConnectionFactory setter 方法与@Autowired . |
3 | 创建一个新的DatabaseClient 使用ConnectionFactory . |
@Component (1)
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { (2)
private val databaseClient = DatabaseClient(connectionFactory) (3)
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 | 用@Component . |
2 | 构造函数注入ConnectionFactory . |
3 | 创建一个新的DatabaseClient 使用ConnectionFactory . |
无论您选择使用上述哪种模板初始化样式(或
not),很少需要创建DatabaseClient
每个类别
时间。配置后,一个DatabaseClient
实例是线程安全的。
如果您的应用程序访问多个
databases,您可能需要多个DatabaseClient
实例,这需要多个ConnectionFactory
随后,多个不同配置的DatabaseClient
实例。
4.3. 检索自动生成的密钥
INSERT
语句在将行插入表时可能会生成键
定义自动递增或标识列。要完全控制
要生成的列名,只需注册一个StatementFilterFunction
那
请求为所需列生成的键。
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"))
.map(row -> row.get("id", Integer.class))
.first();
// generatedId emits the generated key once the INSERT statement has finished
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.map { row -> row.get("id", Integer.class) }
.awaitOne()
// generatedId emits the generated key once the INSERT statement has finished
4.4. 控制数据库连接
本节涵盖:
4.4.1. 使用ConnectionFactory
Spring 通过ConnectionFactory
.
一个ConnectionFactory
是 R2DBC 规范的一部分,是一个常见的入口点
对于Drivers。它允许容器或框架隐藏连接池
以及应用程序代码中的事务管理问题。作为开发者,
您无需了解有关如何连接到数据库的详细信息。那就是
设置ConnectionFactory
.你
在开发和测试代码时,很可能会同时担任这两个角色,但您没有
必须知道生产数据源是如何配置的。
当您使用 Spring 的 R2DBC 层时,您可以使用第三方提供的连接池实现来配置您自己的层。一个流行的实现是 R2DBC 池(r2dbc-pool
). Spring 中的实现分发仅用于测试目的,不提供池化。
要配置ConnectionFactory
:
-
获取与
ConnectionFactory
因为您通常会获得 R2DBCConnectionFactory
. -
提供 R2DBC URL(有关正确的值,请参阅驱动程序的文档)。
以下示例演示如何配置ConnectionFactory
:
ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
val factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
4.4.2. 使用ConnectionFactoryUtils
这ConnectionFactoryUtils
class 是一个方便且功能强大的辅助类它提供了static
从ConnectionFactory
并关闭连接(如有必要)。
它支持订阅者Context
-绑定连接,例如,与R2dbcTransactionManager
.
4.4.3. 使用SingleConnectionFactory
这SingleConnectionFactory
class 是DelegatingConnectionFactory
将单个Connection
每次使用后不会关闭。
如果任何客户端代码调用close
假设有池连接(如使用持久化工具时),您应该将suppressClose
属性设置为true
. 此设置返回一个封装物理连接的紧密抑制代理。请注意,您可以不再将其转换为原生Connection
或类似对象。
SingleConnectionFactory
主要是一个测试类,可用于特定要求例如流水线,如果您的 R2DBC 驱动程序允许此类使用。与池化ConnectionFactory
,它始终重用相同的连接,避免过度创建物理连接。
4.4.4. 使用TransactionAwareConnectionFactoryProxy
TransactionAwareConnectionFactoryProxy
是目标的代理ConnectionFactory
.
代理包装该目标ConnectionFactory
以增加对 Spring 管理的事务的认识。
如果您使用未集成的 R2DBC 客户端,则需要使用此类
与 Spring 的 R2DBC 支持。在这种情况下,您仍然可以使用此客户端,并且
同时,让这个客户端参与 Spring 管理的事务。一般是
最好集成具有适当访问权限的 R2DBC 客户端ConnectionFactoryUtils 用于资源管理。 |
请参阅TransactionAwareConnectionFactoryProxy
javadoc 了解更多详情。
4.4.5. 使用R2dbcTransactionManager
这R2dbcTransactionManager
class 是一个ReactiveTransactionManager
实现
单个 R2DBCConnectionFactory
.它绑定一个 R2DBCConnection
从指定的ConnectionFactory
给订阅者Context
,可能允许一个订阅者Connection
对于每个ConnectionFactory
.
检索 R2DBC 需要应用程序代码Connection
通过ConnectionFactoryUtils.getConnection(ConnectionFactory)
,而不是 R2DBC 的标准ConnectionFactory.create()
.所有框架类(例如DatabaseClient
)使用这个
隐含的策略。如果不与事务管理器一起使用,则查找策略的行为
一模一样ConnectionFactory.create()
因此可以在任何情况下使用。
5. 对象关系映射 (ORM) 数据访问
本节介绍使用对象关系映射 (ORM) 时的数据访问。
5.1. 带 Spring 的 ORM 简介
Spring Framework 支持与 Java Persistence API (JPA) 集成,并且支持原生 Hibernate 进行资源管理、数据访问对象 (DAO) 实现、和事务策略。例如,对于 Hibernate,有一流的支持几个方便的 IoC 功能,可以解决许多典型的 Hibernate 集成问题。您可以配置 OR(对象关系)映射的所有受支持功能通过依赖注入工具。它们可以参与 Spring 的资源和事务管理,并且它们符合 Spring 的通用事务和 DAO异常层次结构。推荐的集成风格是针对普通Hibernate 或 JPA API 编写 DAO。
当您创建时,Spring 为您选择的 ORM 层添加了显着的增强功能数据访问应用程序。您可以根据需要利用尽可能多的集成支持希望,您应该将这种集成工作与构建的成本和风险进行比较内部类似的基础设施。您可以像使用类似的内部基础设施一样使用大部分 ORM 支持库,无论技术如何,因为一切都被设计为一组可重用的JavaBeans。Spring IoC 容器中的 ORM 有助于配置和部署。 因此 本节中的大多数示例都显示了 Spring 容器内的配置。
使用 Spring Framework 创建 ORM DAO 的好处包括:
-
更轻松的测试。Spring 的 IoC 方法使交换实现变得容易 和 Hibernate 的配置位置
SessionFactory
实例, JDBCDataSource
实例、事务管理器和映射对象实现(如果需要)。这 反过来,可以更轻松地测试 隔离。 -
常见数据访问异常。Spring 可以包装来自 ORM 工具的异常, 将它们从专有(可能检查的)异常转换为公共运行时
DataAccessException
等级制度。此功能可让您处理大多数持久性 异常,这些异常是不可恢复的,仅在适当的层中,而没有 烦人的样板捕获、抛出和异常声明。你仍然可以诱捕 并根据需要处理异常。请记住,JDBC 异常(包括 特定于数据库的方言)也会转换为相同的层次结构,这意味着您可以 在一致的编程模型中使用 JDBC 执行一些作。 -
一般资源管理。Spring 应用程序上下文可以处理位置 和 Hibernate 的配置
SessionFactory
实例,JPAEntityManagerFactory
实例, JDBCDataSource
实例和其他相关资源。这使得这些 价值观易于管理和更改。弹簧提供高效、简单和安全的处理 持久性资源。例如,使用 Hibernate 的相关代码通常需要 使用相同的休眠Session
以确保效率和适当的交易处理。 Spring 可以轻松创建和绑定Session
透明地到当前线程, 通过暴露电流Session
通过 HibernateSessionFactory
.因此,Spring 解决了典型 Hibernate 使用的许多长期问题,适用于任何本地或 JTA 交易环境。 -
集成交易管理。你可以用声明式 面向方面编程 (AOP) 样式的方法拦截器,通过
@Transactional
注释或通过在 XML 配置文件。在这两种情况下,事务语义和异常处理 (回滚等)为您处理。如资源和事务管理中所述, 您还可以交换各种事务管理器,而不会影响与 ORM 相关的代码。 例如,您可以在本地事务和 JTA 之间进行交换,并使用相同的完整服务 (例如声明性事务)在这两种情况下都可用。此外 与 JDBC 相关的代码可以与用于执行 ORM 的代码完全事务集成。 这对于不适合 ORM 的数据访问非常有用(例如批处理和 BLOB 流),但仍需要与 ORM作共享公共事务。
获得更全面的 ORM 支持,包括对替代数据库的支持 MongoDB 等技术,您可能想查看 Spring Data 项目套件。如果你是 JPA 用户,则 Getting Started Accessing https://spring.io 的 JPA 指南数据提供了很好的介绍。 |
5.2. 一般 ORM 集成注意事项
本节重点介绍适用于所有 ORM 技术的注意事项。 Hibernate 部分提供了更多详细信息,还显示了这些功能和 具体上下文中的配置。
Spring 的 ORM 集成的主要目标是清晰的应用程序分层(使用任何数据 访问和事务技术)以及应用程序对象的松散耦合 — 否 更多业务服务依赖于数据访问或事务策略,不再 硬编码资源查找,不再有难以替换的单例,不再有自定义服务 登记处。目标是采用一种简单且一致的方法来连接应用程序对象,保持 它们尽可能可重用且不受容器依赖。所有个人 数据访问功能可以单独使用,但与 Spring 的 应用程序上下文概念,提供基于 XML 的配置和交叉引用 不需要 Spring 感知的普通 JavaBean 实例。在典型的 Spring 应用程序中, 许多重要的对象都是 JavaBeans:数据访问模板、数据访问对象、 事务管理器、使用数据访问对象和事务的业务服务 管理器、Web 视图解析器、使用业务服务的 Web 控制器等。
5.2.1. 资源和事务管理
典型的业务应用程序充斥着重复的资源管理代码。 许多项目试图发明自己的解决方案,有时会牺牲适当的处理 为了方便编程而失败。Spring 提倡简单的解决方案,以实现适当的 资源处理,即 JDBC 的情况下通过模板化和应用 AOP 进行 IoC ORM 技术的拦截器。
基础结构提供适当的资源处理和适当的转换
特定 API 异常到未选中的基础结构异常层次结构。Spring
引入了适用于任何数据访问策略的 DAO 异常层次结构。对于直接
JDBC 中,则JdbcTemplate
上一节中提到的类提供了连接处理和正确的转换SQLException
到DataAccessException
层次结构,包括特定于数据库的 SQL 错误的转换
代码到有意义的异常类。对于 ORM 技术,请参阅下一节,了解如何获取相同的异常
翻译好处。
在事务管理方面,JdbcTemplate
类连接到 Spring
事务支持并支持 JTA 和 JDBC 事务,通过各自的
Spring 事务管理器。对于支持的 ORM 技术,Spring 提供了 Hibernate
以及通过 Hibernate 和 JPA 事务管理器以及 JTA 支持的 JPA 支持。
有关事务支持的详细信息,请参阅事务管理一章。
5.2.2. 异常翻译
当你在 DAO 中使用 Hibernate 或 JPA 时,你必须决定如何处理持久性
技术的本机异常类。DAO 抛出一个HibernateException
或PersistenceException
,取决于技术。这些异常都是运行时
例外,并且不必声明或捕获。您可能还必须处理IllegalArgumentException
和IllegalStateException
.这意味着调用方只能
将异常视为通常是致命的,除非它们想要依赖于持久性
技术自己的异常结构。捕捉特定原因(例如乐观
锁定失败)如果不将调用者绑定到实现策略,则不可能。
对于强基于 ORM 或
不需要任何特殊的例外处理(或两者兼而有之)。但是,Spring 让例外
翻译通过@Repository
注解。以下内容
示例(一个用于 Java 配置,一个用于 XML 配置)显示了如何执行此作:
@Repository
public class ProductDaoImpl implements ProductDao {
// class body here...
}
@Repository
class ProductDaoImpl : ProductDao {
// class body here...
}
<beans>
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
后处理器自动查找所有异常转换器(
这PersistenceExceptionTranslator
接口),并通知所有标有@Repository
注释,以便发现的翻译器可以拦截并应用
对引发的异常进行适当的翻译。
综上所述,您可以基于普通持久化技术的 API 和 注释,同时仍然受益于 Spring 管理的事务、依赖项 注入,以及透明异常转换(如果需要)到 Spring 的自定义 异常层次结构。
5.3. 休眠
我们首先介绍 Spring 环境中的 Hibernate 5,用它来演示 Spring 集成 OR 映射器所采取的方法。本节详细介绍了许多问题,并展示了 DAO 的不同变体实现和事务划分。这些模式中的大多数可以直接转换为所有其他受支持的 ORM 工具。本章的后面部分涵盖其他 ORM 技术并展示简短的示例。
从 Spring Framework 5.3 开始,Spring 需要 Hibernate ORM 5.2+ 才能用于 Spring 的HibernateJpaVendorAdapter 以及原生休眠SessionFactory 设置。 强烈建议为新启动的应用程序使用 Hibernate ORM 5.4。用于HibernateJpaVendorAdapter ,Hibernate Search 需要升级到 5.11.6。 |
5.3.1.SessionFactory
在 Spring 容器中设置
为了避免将应用程序对象绑定到硬编码的资源查找,您可以定义资源(例如 JDBCDataSource
或 HibernateSessionFactory
) 作为 bean 中的Spring 容器。需要访问资源的应用程序对象通过 bean 引用接收引用到此类预定义实例,如 DAO 中所示定义。
以下摘录自 XML 应用程序上下文定义,演示了如何设置
JDBC 公司DataSource
和 HibernateSessionFactory
在它之上:
<beans>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingResources">
<list>
<value>product.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>
从本地 Jakarta Commons DBCP 切换BasicDataSource
到位于 JNDI 的DataSource
(通常由应用程序服务器管理)只是
配置,如以下示例所示:
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
您还可以访问位于 JNDI 的SessionFactory
,使用 Spring 的JndiObjectFactoryBean
/ <jee:jndi-lookup>
以检索和公开它。但是,这在 EJB 上下文之外通常并不常见。
Spring 还提供了一个 双 从 Spring Framework 5.1 开始,这样的原生 Hibernate 设置也可以公开 JPA |
5.3.2. 基于 Plain Hibernate API 实现 DAO
Hibernate 有一个称为上下文会话的功能,其中 Hibernate 本身管理
一个电流Session
每笔交易。这大致相当于 Spring 的
一个休眠的同步Session
每笔交易。对应的 DAO
实现类似于以下示例,基于纯 Hibernate API:
public class ProductDaoImpl implements ProductDao {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Collection loadProductsByCategory(String category) {
return this.sessionFactory.getCurrentSession()
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list();
}
}
class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao {
fun loadProductsByCategory(category: String): Collection<*> {
return sessionFactory.currentSession
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list()
}
}
这种风格类似于 Hibernate 参考文档和示例,
除了持有SessionFactory
在实例变量中。我们强烈推荐
这种基于实例的设置优于老式static
HibernateUtil
类从
Hibernate 的 CaveatEmptor 示例应用程序。(一般情况下,不要将任何资源保留在static
变量,除非绝对必要。
前面的 DAO 示例遵循依赖注入模式。它非常适合 Spring IoC
容器,就像针对 Spring 的HibernateTemplate
.
您还可以在纯 Java 中设置这样的 DAO(例如,在单元测试中)。为此,
实例化它并调用setSessionFactory(..)
与所需的工厂参考。作为
Spring bean 定义,DAO 将类似于以下内容:
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
这种 DAO 风格的主要优点是它仅依赖于 Hibernate API。无导入 任何春季课程的课程都是必需的。这从非侵入性中很有吸引力 视角,对于 Hibernate 开发人员来说可能感觉更自然。
然而,DAO 抛出的HibernateException
(未选中,因此它没有
被声明或捕获),这意味着调用者只能将异常视为
通常是致命的——除非他们想依赖 Hibernate 自己的异常层次结构。
如果没有特定原因,就不可能捕获特定原因(例如乐观锁定失败)
将调用方与实施策略绑定。这种权衡可能是可以接受的
基于休眠的应用程序不需要任何特殊例外
治疗,或两者兼而有之。
幸运的是,Spring的LocalSessionFactoryBean
支持 Hibernate 的SessionFactory.getCurrentSession()
任何 Spring 事务策略的方法,
返回当前 Spring 管理的事务Session
,即使有HibernateTransactionManager
.该方法的标准行为仍然存在
返回当前Session
与正在进行的 JTA 事务(如果有)相关联。
无论您是否使用 Spring 的JtaTransactionManager
、EJB 容器管理事务 (CMT) 或 JTA。
综上所述,您可以基于纯 Hibernate API 实现 DAO,同时仍然 能够参与 Spring 管理的交易。
5.3.3. 声明性事务分界
我们建议您使用 Spring 的声明式事务支持,它允许您 将 Java 代码中的显式事务分界 API 调用替换为 AOP 事务拦截器。您可以在 Spring 中配置此事务拦截器 容器,使用 Java 注释或 XML。这种声明式事务功能 让您保持业务服务不受重复交易分界代码的影响,并且 专注于添加业务逻辑,这是应用程序的真正价值。
在继续之前,我们强烈建议您阅读声明式事务管理(如果您还没有阅读过)。 |
您可以使用@Transactional
注释并指示
Spring 容器来查找这些注释并为
这些带注释的方法。以下示例显示了如何执行此作:
public class ProductServiceImpl implements ProductService {
private ProductDao productDao;
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
@Transactional
public void increasePriceOfAllProductsInCategory(final String category) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// ...
}
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return this.productDao.findAllProducts();
}
}
class ProductServiceImpl(private val productDao: ProductDao) : ProductService {
@Transactional
fun increasePriceOfAllProductsInCategory(category: String) {
val productsToChange = productDao.loadProductsByCategory(category)
// ...
}
@Transactional(readOnly = true)
fun findAllProducts() = productDao.findAllProducts()
}
在容器中,您需要设置PlatformTransactionManager
实现
(作为 bean)和<tx:annotation-driven/>
条目,选择进入@Transactional
运行时处理。以下示例显示了如何执行此作:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- SessionFactory, DataSource, etc. omitted -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
5.3.4. 程序化事务分界
您可以在应用程序的更高级别中划分事务,在
跨越任意数量作的较低级别的数据访问服务。限制也没有
存在于周边业务服务的实施上。它只需要一个弹簧PlatformTransactionManager
.同样,后者可以来自任何地方,但最好是
作为 bean 引用,通过setTransactionManager(..)
方法。此外,productDAO
应由setProductDao(..)
方法。以下一对片段显示
Spring 应用程序上下文中的事务管理器和业务服务定义
以及业务方法实现的示例:
<beans>
<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="myProductService" class="product.ProductServiceImpl">
<property name="transactionManager" ref="myTxManager"/>
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
public class ProductServiceImpl implements ProductService {
private TransactionTemplate transactionTemplate;
private ProductDao productDao;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
public void increasePriceOfAllProductsInCategory(final String category) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// do the price increase...
}
});
}
}
class ProductServiceImpl(transactionManager: PlatformTransactionManager,
private val productDao: ProductDao) : ProductService {
private val transactionTemplate = TransactionTemplate(transactionManager)
fun increasePriceOfAllProductsInCategory(category: String) {
transactionTemplate.execute {
val productsToChange = productDao.loadProductsByCategory(category)
// do the price increase...
}
}
}
Spring的TransactionInterceptor
允许抛出任何已检查的应用程序异常
替换为回调代码,而TransactionTemplate
被限制为未选中
异常。TransactionTemplate
在以下情况下触发回滚
未选中的应用程序异常,或者事务是否被标记为仅回滚
应用程序(通过将TransactionStatus
).默认情况下,TransactionInterceptor
行为方式相同,但允许每个方法可配置回滚策略。
5.3.5. 事务管理策略
双TransactionTemplate
和TransactionInterceptor
委托实际交易
handling 到PlatformTransactionManager
实例(可以是HibernateTransactionManager
(对于单个休眠SessionFactory
) 通过使用ThreadLocal
Session
在引擎盖下)或JtaTransactionManager
(委托给
JTA 子系统)用于 Hibernate 应用程序。您甚至可以使用自定义PlatformTransactionManager
实现。从本机 Hibernate 事务切换
管理到 JTA (例如在面对某些分布式事务需求时)
应用程序的部署)只是配置问题。您可以将
Hibernate 事务管理器与 Spring 的 JTA 事务实现。双
事务分界和数据访问代码无需更改即可工作,因为它们
使用通用事务管理 API。
对于跨多个 Hibernate 会话工厂的分布式事务,您可以将JtaTransactionManager
作为具有多个LocalSessionFactoryBean
定义。然后每个 DAO 都会获得一个特定的SessionFactory
引用传递到其相应的 bean 属性中。如果所有基础 JDBC 数据
源是事务容器,业务服务可以划分事务
跨任意数量的 DAO 和任意数量的会话工厂,无需特别考虑,如
只要它使用JtaTransactionManager
作为战略。
双HibernateTransactionManager
和JtaTransactionManager
允许使用 Hibernate 进行适当的JVM 级缓存处理,无需特定于容器的事务管理器lookup 或 JCA 连接器(如果您不使用 EJB 来启动事务)。
HibernateTransactionManager
可以导出 Hibernate JDBCConnection
到普通 JDBC访问特定 JDBC 的DataSource
. 此功能允许高级混合 Hibernate 和 JDBC 数据访问的事务分界,完全没有JTA,前提是您只访问一个数据库。HibernateTransactionManager
自然而然 如果您已设置传入的SessionFactory
使用DataSource
通过dataSource
属性的LocalSessionFactoryBean
类。 或者,您可以显式指定DataSource
事务应该通过dataSource
属性的HibernateTransactionManager
类。
5.3.6. 比较容器管理的资源和本地定义的资源
您可以在容器管理的 JNDI 之间切换SessionFactory
和本地定义的一个,无需更改任何一行应用程序代码。是保留资源定义在容器中还是在应用程序中本地,主要是您使用的事务策略的问题。与 Spring 定义的本地相比SessionFactory
,手动注册的 JNDISessionFactory
不提供任何 好处。 部署SessionFactory
通过 Hibernate 的 JCA 连接器提供了参与 Java EE 服务器管理基础设施的附加值,但确实不会增加超出此范围的实际价值。
Spring 的事务支持不绑定到容器。当配置任何策略除了 JTA 之外,事务支持也适用于独立或测试环境。特别是在单数据库事务的典型情况下,Spring 的单资源本地事务支持是 JTA 的轻量级且功能强大的替代方案。当您使用本地 EJB 无状态会话 Bean 来驱动事务时,您同时依赖于 EJB容器和 JTA,即使您只访问单个数据库并且仅使用无状态session bean 通过容器管理提供声明性事务 交易。 以编程方式直接使用 JTA 还需要一个 Java EE 环境。JTA 不仅涉及 JTA 本身和JNDI 的容器依赖关系DataSource
实例。 对于非 Spring JTA 驱动的 Hibernate 事务,您有要使用 Hibernate JCA 连接器或额外的 Hibernate 事务代码,请将TransactionManagerLookup
配置为正确的 JVM 级缓存。
Spring驱动的事务也可以与本地定义的Hibernate一起使用SessionFactory
就像他们对本地 JDBC 所做的那样DataSource
,前提是他们访问一个单个数据库。因此,您只需在以下情况下使用 Spring 的 JTA 事务策略有分布式事务要求。JCA 连接器需要特定于容器的部署步骤,以及(显然)首先的 JCA 支持。此配置比使用本地资源部署简单的 Web 应用程序需要更多的工作定义和 Spring 驱动的事务。此外,您经常需要企业版如果您使用 WebLogic Express,则容器的 WebLogic Express 不提供 JCA。具有跨越一个单个数据库在任何 Java EE Web 容器(没有 JTA、JCA 或 EJB)中工作,例如Tomcat、Resin,甚至是普通的 Jetty。此外,您可以轻松地重用这样的中间桌面应用程序或测试套件中的层。
考虑到所有因素,如果您不使用 EJB,请坚持使用本地SessionFactory
设置 和 Spring 的HibernateTransactionManager
或JtaTransactionManager
. 您将获得所有好处,包括适当的事务性 JVM 级缓存和分布式事务,而不会带来容器部署的不便。JNDI 注册 HibernateSessionFactory
通过 JCA 连接器仅在用于与 EJB 结合使用时才增加价值。
5.3.7. Hibernate 的虚假应用程序服务器警告
在一些非常严格的 JTA 环境中XADataSource
实现(当前某些 WebLogic Server 和 WebSphere 版本),当 Hibernate 配置时没有关于该环境的 JTA 事务管理器,虚假警告或异常可能会显示在应用程序服务器日志中。这些警告或异常表示正在访问的连接不再有效或 JDBC 访问不再有效不再有效,可能是因为事务不再处于活动状态。举个例子,这是 WebLogic 的一个实际异常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
另一个常见问题是 JTA 事务后的连接泄漏,使用 Hibernate 会话(以及潜在的底层 JDBC 连接)未正确关闭。
您可以通过让 Hibernate 了解 JTA 事务管理器 它与之同步(与 Spring 一起)。您有两个选项可以执行此作:
-
通过你的Spring
JtaTransactionManager
bean 添加到您的 Hibernate 设置中。最简单的 way 是对jtaTransactionManager
为您的财产LocalSessionFactoryBean
bean(参见 Hibernate 事务设置)。 然后,Spring 将相应的 JTA 策略提供给 Hibernate。 -
您还可以显式配置 Hibernate 的 JTA 相关属性,特别是 “hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode” 以及可能的“hibernateProperties”中的“hibernate.transaction.jta.platform” 上
LocalSessionFactoryBean
(有关这些属性的详细信息,请参阅 Hibernate 的手册)。
本节的其余部分描述了 和 发生的事件序列
没有 Hibernate 对 JTA 的认识PlatformTransactionManager
.
当 Hibernate 未配置任何 JTA 事务管理器感知时, 当 JTA 事务提交时,会发生以下事件:
-
JTA 事务提交。
-
Spring的
JtaTransactionManager
与 JTA 事务同步,因此它是 通过afterCompletion
JTA 事务管理器的回调。 -
在其他活动中,这种同步可以触发 Spring 的回调 Hibernate,通过 Hibernate 的
afterTransactionCompletion
回调(用于清除 Hibernate 缓存),后跟显式close()
调用 Hibernate 会话, 这会导致 Hibernate 尝试close()
JDBC 连接。 -
在某些环境中,此
Connection.close()
调用,然后触发警告或 错误,因为应用程序服务器不再考虑Connection
可用, 因为事务已经提交。
当 Hibernate 配置为 JTA 事务管理器的感知时,当 JTA 事务提交时,会发生以下事件:
-
JTA 事务已准备好提交。
-
Spring的
JtaTransactionManager
同步到 JTA 事务,因此transaction 通过beforeCompletion
JTA 的回调事务管理器。 -
Spring 知道 Hibernate 本身与 JTA 事务同步,并且行为与上一个场景不同。特别是,它与Hibernate 的事务资源管理保持一致。
-
JTA 事务提交。
-
Hibernate 与 JTA 事务同步,因此该事务被回调 通过
afterCompletion
JTA 事务管理器的回调,并且可以 正确清除其缓存。
5.4. JPA
Spring JPA 可在org.springframework.orm.jpa
包,提供对 Java 持久性的全面支持API 以类似于与 Hibernate 集成的方式,同时了解底层实现以提供附加功能。
5.4.1. 在 Spring 环境中设置 JPA 的三个选项
Spring JPA 支持提供了三种设置 JPA 的方法EntityManagerFactory
应用程序用于获取实体管理器。
用LocalEntityManagerFactoryBean
只能在简单的部署环境(如独立部署环境)中使用此选项 应用和集成测试。
这LocalEntityManagerFactoryBean
创建一个EntityManagerFactory
适合人群
应用程序仅使用 JPA 进行数据访问的简单部署环境。
工厂 bean 使用 JPAPersistenceProvider
自动检测机制(根据
到 JPA 的 Java SE 引导),并且在大多数情况下,仅要求您指定
持久性单元名称。以下 XML 示例配置了这样的 bean:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</beans>
这种形式的 JPA 部署是最简单和最有限的。您不能引用
现有 JDBCDataSource
bean 定义,并且不支持全局事务
存在。此外,持久类的编织(字节码转换)是
特定于提供者,通常需要在启动时指定特定的 JVM 代理。这
选项仅适用于独立应用程序和测试环境,对于以下情况
JPA 规范已设计。
从 JNDI 获取 EntityManagerFactory
在部署到 Java EE 服务器时,可以使用此选项。检查服务器的文档 关于如何将自定义 JPA 提供程序部署到您的服务器中,允许不同的 provider 比服务器的默认值高。
获取EntityManagerFactory
来自 JNDI(例如在 Java EE 环境中),
是更改 XML 配置的问题,如以下示例所示:
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>
此作假定标准 Java EE 引导。Java EE 服务器自动检测
持久性单位(实际上,META-INF/persistence.xml
应用程序 jar 中的文件)和persistence-unit-ref
Java EE 部署描述符中的条目(例如,web.xml
) 并定义这些持久性单元的环境命名上下文位置。
在这种情况下,整个持久性单元的部署,包括编织
(字节码转换)的持久性类,取决于 Java EE 服务器。The JDBCDataSource
通过META-INF/persistence.xml
文件。EntityManager
事务与服务器的 JTA 子系统集成。Spring只是
使用获得的EntityManagerFactory
,通过
持久性单元的依赖注入和管理事务(通常
通过JtaTransactionManager
).
如果在同一应用程序中使用多个持久性单元,则此类 Bean 名称
JNDI 检索到的持久性单元应与持久性单元名称匹配,而
应用程序用来引用它们(例如,在@PersistenceUnit
和@PersistenceContext
注释)。
用LocalContainerEntityManagerFactoryBean
您可以在基于 Spring 的应用程序环境中使用此选项来实现完整的 JPA 功能。 这包括 Tomcat 等 Web 容器、独立应用程序和 具有复杂持久性要求的集成测试。
如果您想专门配置 Hibernate 设置,则立即替代
就是设置一个原生的 HibernateLocalSessionFactoryBean 而不是普通的 JPALocalContainerEntityManagerFactoryBean ,让它与 JPA 访问代码交互
以及本机 Hibernate 访问代码。
有关详细信息,请参阅 JPA 交互的本机休眠设置。 |
这LocalContainerEntityManagerFactoryBean
完全控制EntityManagerFactory
配置,适用于以下环境:
需要细粒度定制。这LocalContainerEntityManagerFactoryBean
创建一个PersistenceUnitInfo
实例基于persistence.xml
文件,则
提供dataSourceLookup
策略,并指定的loadTimeWeaver
.因此,
可以使用 JNDI 之外的自定义数据源并控制编织
过程。以下示例显示了LocalContainerEntityManagerFactoryBean
:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>
以下示例显示了典型的persistence.xml
文件:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
<mapping-file>META-INF/orm.xml</mapping-file>
<exclude-unlisted-classes/>
</persistence-unit>
</persistence>
这<exclude-unlisted-classes/> 快捷方式表示未扫描
带注释的实体类应该出现。显式“true”值
(<exclude-unlisted-classes>true</exclude-unlisted-classes/> )也意味着不扫描。<exclude-unlisted-classes>false</exclude-unlisted-classes/> 确实会触发扫描。
但是,我们建议省略exclude-unlisted-classes 元素
如果要进行实体类扫描。 |
使用LocalContainerEntityManagerFactoryBean
是最强大的 JPA 设置
选项,允许在应用程序内进行灵活的本地配置。它支持
指向现有 JDBC 的链接DataSource
,支持本地和全局事务,以及
依此类推。但是,它也对运行时环境提出了要求,例如
如果持久性提供程序要求,则提供具有 weaving 功能的类加载器的可用性
字节码转换。
此选项可能与 Java EE 服务器的内置 JPA 功能冲突。在
完整的 Java EE 环境,请考虑获取EntityManagerFactory
来自 JNDI。
或者,指定自定义persistenceXmlLocation
在您的LocalContainerEntityManagerFactoryBean
定义(例如,
META-INF/my-persistence.xml),并在
应用程序 jar 文件。因为 Java EE 服务器只查找默认值META-INF/persistence.xml
文件,它会忽略此类自定义持久性单元,因此,
避免与 Spring 驱动的 JPA 设置发生冲突。(这适用于 Resin 3.1,对于
示例。
这LoadTimeWeaver
interface 是一个 Spring 提供的类,它允许 JPAClassTransformer
实例是否以特定方式插入,具体取决于
environment 是 Web 容器或应用程序服务器。挂钩ClassTransformers
通过代理通常效率不高。代理针对整个虚拟机工作,并且
检查每个已加载的类,这在生产中通常是不受欢迎的
服务器环境。
Spring 提供了许多LoadTimeWeaver
针对各种环境的实现,
让ClassTransformer
实例仅应用于每个类加载器,而不是
对于每个虚拟机。
请参阅 AOP 章节中的 Spring 配置
更多关于LoadTimeWeaver
实现及其设置,无论是
通用或针对各种平台(例如 Tomcat、JBoss 和 WebSphere)进行定制。
如 Spring 配置中所述,您可以配置
一个上下文范围LoadTimeWeaver
通过使用@EnableLoadTimeWeaving
注释或context:load-time-weaver
XML 元素。这样的全球织工自动捡起
由所有 JPALocalContainerEntityManagerFactoryBean
实例。以下示例
显示了设置加载时编织器的首选方法,提供自动检测
平台(例如 Tomcat 的具有编织功能的类加载器或 Spring 的 JVM 代理)
以及织布机自动传播到所有织工感知豆子:
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
但是,如果需要,您可以通过loadTimeWeaver
属性,如以下示例所示:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
无论 LTW 是如何配置的,通过使用此技术,JPA 应用程序依赖于 检测可以在目标平台(例如 Tomcat)中运行,而无需代理。 当托管应用程序依赖于不同的 JPA 时,这一点尤其重要 实现,因为 JPA 转换器仅应用于类加载器级别,并且 因此,彼此隔离。
处理多个持久性单元
对于依赖于多个持久性单元位置(存储在各种
JARS 在类路径中),Spring 提供了PersistenceUnitManager
充当
一个中央存储库,并避免持久性单元发现过程,这可能是
贵。默认实现允许指定多个位置。这些位置是
解析,然后通过持久性单元名称进行检索。(默认情况下,类路径
被搜索META-INF/persistence.xml
文件。以下示例配置
多个地点:
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
<value>classpath:/my/package/**/custom-persistence.xml</value>
<value>classpath*:META-INF/persistence.xml</value>
</list>
</property>
<property name="dataSources">
<map>
<entry key="localDataSource" value-ref="local-db"/>
<entry key="remoteDataSource" value-ref="remote-db"/>
</map>
</property>
<!-- if no datasource is specified, use this one -->
<property name="defaultDataSource" ref="remoteDataSource"/>
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="pum"/>
<property name="persistenceUnitName" value="myCustomUnit"/>
</bean>
默认实现允许自定义PersistenceUnitInfo
实例
(在将它们提供给 JPA 提供者之前)要么是声明性的(通过其属性,其中
影响所有托管单元)或以编程方式(通过PersistenceUnitPostProcessor
,允许持久性单元选择)。如果没有PersistenceUnitManager
指定时,由LocalContainerEntityManagerFactoryBean
.
后台引导
LocalContainerEntityManagerFactoryBean
支持后台引导
这bootstrapExecutor
属性,如以下示例所示:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="bootstrapExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
实际的 JPA 提供程序引导被移交给指定的执行器,然后,
并行运行,到应用程序引导线程。暴露的EntityManagerFactory
代理可以注入到其他应用程序组件中,甚至能够响应EntityManagerFactoryInfo
配置检查。但是,一旦实际的 JPA 提供者
正在被其他组件访问(例如,调用createEntityManager
),这些调用
块,直到后台引导完成。特别是,当您使用
Spring Data JPA,请确保也为其存储库设置延迟引导。
5.4.2. 实现基于 JPA 的 DAO:EntityManagerFactory
和EntityManager
虽然EntityManagerFactory 实例是线程安全的,EntityManager 实例是
不。注入的 JPAEntityManager 行为类似于EntityManager 从
应用程序服务器的 JNDI 环境,如 JPA 规范所定义。它委托
对当前事务的所有调用EntityManager ,如果有的话。否则,它会回退
到新创建的EntityManager 实际上使其使用线程安全。 |
可以在没有任何 Spring 依赖项的情况下针对普通 JPA 编写代码,通过以下方式
使用注入的EntityManagerFactory
或EntityManager
.Spring 可以理解@PersistenceUnit
和@PersistenceContext
字段和方法级别的注释
如果PersistenceAnnotationBeanPostProcessor
已启用。以下示例显示了一个普通的 JPA DAO 实现
使用@PersistenceUnit
注解:
public class ProductDaoImpl implements ProductDao {
private EntityManagerFactory emf;
@PersistenceUnit
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
public Collection loadProductsByCategory(String category) {
EntityManager em = this.emf.createEntityManager();
try {
Query query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.getResultList();
}
finally {
if (em != null) {
em.close();
}
}
}
}
class ProductDaoImpl : ProductDao {
private lateinit var emf: EntityManagerFactory
@PersistenceUnit
fun setEntityManagerFactory(emf: EntityManagerFactory) {
this.emf = emf
}
fun loadProductsByCategory(category: String): Collection<*> {
val em = this.emf.createEntityManager()
val query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.resultList;
}
}
前面的 DAO 不依赖于 Spring,并且仍然很好地融入 Spring
应用程序上下文。此外,DAO 利用注释来要求
注入默认值EntityManagerFactory
,如以下示例 bean 定义所示:
<beans>
<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
作为显式定义PersistenceAnnotationBeanPostProcessor
,
考虑使用 Springcontext:annotation-config
应用程序中的 XML 元素
上下文配置。这样做会自动注册所有 Spring 标准
用于基于注释的配置的后处理器,包括CommonAnnotationBeanPostProcessor
等等。
请考虑以下示例:
<beans>
<!-- post-processors for all standard config annotations -->
<context:annotation-config/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
这种 DAO 的主要问题是它总是创建一个新的EntityManager
通过
工厂。您可以通过请求事务性EntityManager
(还
称为“共享 EntityManager”,因为它是实际的共享、线程安全的代理
事务性 EntityManager)而不是工厂。以下示例显示了如何执行此作:
public class ProductDaoImpl implements ProductDao {
@PersistenceContext
private EntityManager em;
public Collection loadProductsByCategory(String category) {
Query query = em.createQuery("from Product as p where p.category = :category");
query.setParameter("category", category);
return query.getResultList();
}
}
class ProductDaoImpl : ProductDao {
@PersistenceContext
private lateinit var em: EntityManager
fun loadProductsByCategory(category: String): Collection<*> {
val query = em.createQuery("from Product as p where p.category = :category")
query.setParameter("category", category)
return query.resultList
}
}
这@PersistenceContext
注释有一个可选属性,名为type
,默认为PersistenceContextType.TRANSACTION
. 您可以使用此默认值接收共享的EntityManager
代理。 替代方案PersistenceContextType.EXTENDED
,是一个完全不同的事情。这导致了所谓的扩展EntityManager
,这不是线程安全,因此不得在并发访问的组件中使用,例如Spring 管理的单例 bean。 扩展EntityManager
实例只能用于有状态组件,例如,驻留在会话中的组件,具有EntityManager
不与当前交易绑定,而是完全取决于 应用。
注射的EntityManager
是 Spring 管理的(知道正在进行的事务)。即使新的 DAO 实现使用方法级注入一个EntityManager
而不是EntityManagerFactory
,没有变化是
由于注释的使用,在应用程序上下文 XML 中需要。
这种 DAO 风格的主要优点是它仅依赖于 Java Persistence API。 不需要导入任何 Spring 类。此外,正如 JPA 注释所理解的那样, 注射由弹簧容器自动应用。这是有吸引力的 非侵入性的视角,对 JPA 开发人员来说感觉更自然。
5.4.3. Spring驱动的JPA事务
如果您还没有阅读声明式事务管理,我们强烈建议您阅读 Declarative Transaction Management 已经这样做了,以更详细地了解 Spring 的声明式事务支持。 |
JPA 的推荐策略是通过 JPA 的本机事务进行本地事务
支持。Spring的JpaTransactionManager
提供了许多本地已知的功能
JDBC 事务(例如特定于事务的隔离级别和资源级别
只读优化)与任何常规 JDBC 连接池(无 XA 要求)。
Spring JPA 还允许配置的JpaTransactionManager
公开 JPA 事务
到 JDBC 访问代码,该代码访问相同的DataSource
,前提是注册的JpaDialect
支持检索底层 JDBCConnection
.
Spring 为 EclipseLink 和 Hibernate JPA 实现提供了方言。
有关JpaDialect
机制。
作为直接替代方案,Spring 的原生HibernateTransactionManager 能够与 JPA 访问代码交互,适应几个 Hibernate 细节并提供JDBC 交互。这与LocalSessionFactoryBean 设置。有关详细信息,请参阅 JPA 交互的本机休眠设置。 |
5.4.4. 理解JpaDialect
和JpaVendorAdapter
作为一项高级功能,JpaTransactionManager
和AbstractEntityManagerFactoryBean
允许自定义JpaDialect
传递给jpaDialect
bean 属性。一个JpaDialect
实现可以启用以下高级
Spring 支持的功能,通常以特定于提供商的方式:
-
应用特定的事务语义(例如自定义隔离级别或事务 超时)
-
检索事务性 JDBC
Connection
(用于接触基于 JDBC 的 DAO) -
高级翻译
PersistenceException
到 Spring 的DataAccessException
这对于特殊事务语义和高级
例外的翻译。默认实现 (DefaultJpaDialect
) 会
不提供任何特殊能力,如果需要前面列出的功能,则有
以指定适当的方言。
作为一个更广泛的提供商适配设施,主要针对 Spring 的全功能LocalContainerEntityManagerFactoryBean 设置JpaVendorAdapter 将
能力JpaDialect 与其他特定于提供程序的默认值。指定HibernateJpaVendorAdapter 或EclipseLinkJpaVendorAdapter 最方便
自动配置EntityManagerFactory Hibernate 或 EclipseLink 的设置,
分别。请注意,这些提供程序适配器主要设计用于
Spring 驱动的事务管理(即,用于JpaTransactionManager ). |
请参阅JpaDialect
和JpaVendorAdapter
javadoc 的
有关其作以及如何在 Spring 的 JPA 支持中使用它们的更多详细信息。
5.4.5. 使用 JTA 事务管理设置 JPA
作为替代方案JpaTransactionManager
,Spring 还允许多资源
通过 JTA 进行事务协调,无论是在 Java EE 环境中还是在 Java EE 环境中使用
独立事务协调器,例如 Atomikos。除了选择 Spring 的JtaTransactionManager
而不是JpaTransactionManager
,你需要更进一步
步骤:
-
底层 JDBC 连接池需要支持 XA 并与 您的交易协调员。这在 Java EE 环境中通常很简单, 暴露一种不同类型的
DataSource
通过 JNDI。查看您的应用程序服务器 文档了解详细信息。类似地,独立事务协调器通常 带有特殊的 XA 集成DataSource
变种。再次检查其文档。 -
太平绅士协会
EntityManagerFactory
需要为 JTA 配置设置。这是 provider-specific 的,通常通过指定为jpaProperties
上LocalContainerEntityManagerFactoryBean
.对于 Hibernate,这些属性 甚至是特定于版本的。有关详细信息,请参阅您的 Hibernate 文档。 -
Spring的
HibernateJpaVendorAdapter
强制执行某些面向 Spring 的默认值,例如 作为连接释放方式,on-close
,它与 Hibernate 自己的默认值 Hibernate 5.0,但在 Hibernate 5.1+ 中不再如此。对于 JTA 设置,请确保声明 持久性单元事务类型为“JTA”。或者,将 Hibernate 5.2 的hibernate.connection.handling_mode
属性设置为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
恢复 Hibernate 自己的默认值。 有关相关说明,请参阅 Spurious Application Server Warnings with Hibernate。 -
或者,考虑获取
EntityManagerFactory
从您的应用程序 服务器本身(即,通过 JNDI 查找而不是本地声明的LocalContainerEntityManagerFactoryBean
).服务器提供的EntityManagerFactory
可能需要在服务器配置中进行特殊定义(使部署 可移植性较差),但针对服务器的 JTA 环境进行了设置。
5.4.6. JPA 交互的本机休眠设置和本机休眠事务
一个本地人LocalSessionFactoryBean
设置与HibernateTransactionManager
允许与@PersistenceContext
和其他 JPA 访问代码。A 休眠SessionFactory
原生实现 JPA 的EntityManagerFactory
现在界面
和 HibernateSession
handle 本地是 JPAEntityManager
.
Spring 的 JPA 支持工具会自动检测本机 Hibernate 会话。
因此,这种本机 Hibernate 设置可以替代标准 JPALocalContainerEntityManagerFactoryBean
和JpaTransactionManager
组合
在许多情况下,允许与SessionFactory.getCurrentSession()
(并且还HibernateTemplate
) 旁边@PersistenceContext EntityManager
在
相同的本地事务。这样的设置还提供了更强大的 Hibernate 集成
以及更大的配置灵活性,因为它不受 JPA 引导合约的约束。
您不需要HibernateJpaVendorAdapter
配置,
由于 Spring 的原生 Hibernate 设置提供了更多功能
(例如,自定义 Hibernate Integrator 设置、Hibernate 5.3 Bean 容器集成、
以及对只读事务的更强优化)。最后但并非最不重要的一点是,您还可以
express native Hibernate 设置LocalSessionFactoryBuilder
,
无缝集成@Bean
样式配置(否FactoryBean
涉及)。
上 |
6. 使用 Object-XML 映射器编组 XML
6.1. 简介
本章介绍了 Spring 的 Object-XML Mapping 支持。对象-XML 映射(简称 O-X 映射)是将 XML 文档与 XML 文档进行转换和从中转换的行为 一个对象。此转换过程也称为 XML 封送处理或 XML 序列化。本章可以互换使用这些术语。
在 O-X 映射领域中,封送程序负责序列化 对象(图形)转换为 XML。以类似的方式,unmarshaller 将 XML 反序列化为 对象图。此 XML 可以采用 DOM 文档、输入或输出的形式 流或 SAX 处理程序。
使用 Spring 满足 O/X 映射需求的一些好处是:
6.1.1. 易于配置
Spring 的 bean 工厂可以轻松配置封送程序,而无需 构造 JAXB 上下文、JiBX 绑定工厂等。您可以配置封送程序 就像处理应用程序上下文中的任何其他 Bean 一样。此外,基于 XML 命名空间的 配置可用于许多编组程序,使配置均匀 简单。
6.1.2. 一致的接口
Spring 的 O-X 映射通过两个全局接口运行:Marshaller
和Unmarshaller
. 这些抽象允许您切换 O-X 映射框架相对容易,几乎不需要对执行 编组。 这种方法还有一个额外的好处,那就是可以以非侵入式的方式进行 XML使用混合和匹配方法进行编组(例如,一些使用 JAXB和一些由 XStream 执行的编组),让您能够利用每个方法的优势 科技。
6.2.Marshaller
和Unmarshaller
如简介中所述,封送器序列化对象 到 XML,unmarshaller 将 XML 流反序列化为对象。本节介绍 用于此目的的两个 Spring 接口。
6.2.1. 理解Marshaller
Spring 抽象了org.springframework.oxm.Marshaller
接口,其主要方法如下:
public interface Marshaller {
/**
* Marshal the object graph with the given root into the provided Result.
*/
void marshal(Object graph, Result result) throws XmlMappingException, IOException;
}
这Marshaller
interface 有一个 main 方法,它将给定对象封送到一个
鉴于javax.xml.transform.Result
.结果是一个标记接口,基本上
表示 XML 输出抽象。具体实现包装各种 XML
表示形式,如下表所示:
结果实现 | 包装 XML 表示 |
---|---|
|
|
|
|
|
|
尽管marshal() 方法接受一个普通对象作为其第一个参数,大多数Marshaller 实现不能处理任意对象。相反,对象类
必须映射到映射文件中,用注释标记,向
marshaller,或者具有公共基类。请参阅本章的后面部分
以确定您的 OX 技术如何管理这一点。 |
6.2.2. 理解Unmarshaller
类似于Marshaller
,我们有org.springframework.oxm.Unmarshaller
界面,如下表所示:
public interface Unmarshaller {
/**
* Unmarshal the given provided Source into an object graph.
*/
Object unmarshal(Source source) throws XmlMappingException, IOException;
}
该接口还有一个方法,它从给定的javax.xml.transform.Source
(XML 输入抽象)并返回读取的对象。如
跟Result
,Source
是一个标记接口,具有三个具体实现。每
包装不同的 XML 表示形式,如下表所示:
源实现 | 包装 XML 表示 |
---|---|
|
|
|
|
|
|
即使有两个单独的封送接口 (Marshaller
和Unmarshaller
),Spring-WS 中的所有实现都在一个类中实现。
这意味着您可以连接一个封送程序类,并将其都称为
编组者和作为 unmarshallerapplicationContext.xml
.
6.3. 使用Marshaller
和Unmarshaller
您可以在各种情况下使用 Spring 的 OXM。在下面的示例中,我们使用它将 Spring 托管应用程序的设置编组为 XML 文件。在下面的示例中,我们使用一个简单的 JavaBean 来表示设置:
public class Settings {
private boolean fooEnabled;
public boolean isFooEnabled() {
return fooEnabled;
}
public void setFooEnabled(boolean fooEnabled) {
this.fooEnabled = fooEnabled;
}
}
class Settings {
var isFooEnabled: Boolean = false
}
应用程序类使用此 Bean 来存储其设置。除了一个主要方法之外,
class 有两个方法:saveSettings()
将设置 bean 保存到名为settings.xml
和loadSettings()
再次加载这些设置。以下内容main()
方法
构造一个 Spring 应用程序上下文并调用以下两个方法:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
public class Application {
private static final String FILE_NAME = "settings.xml";
private Settings settings = new Settings();
private Marshaller marshaller;
private Unmarshaller unmarshaller;
public void setMarshaller(Marshaller marshaller) {
this.marshaller = marshaller;
}
public void setUnmarshaller(Unmarshaller unmarshaller) {
this.unmarshaller = unmarshaller;
}
public void saveSettings() throws IOException {
try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
this.marshaller.marshal(settings, new StreamResult(os));
}
}
public void loadSettings() throws IOException {
try (FileInputStream is = new FileInputStream(FILE_NAME)) {
this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
}
}
public static void main(String[] args) throws IOException {
ApplicationContext appContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Application application = (Application) appContext.getBean("application");
application.saveSettings();
application.loadSettings();
}
}
class Application {
lateinit var marshaller: Marshaller
lateinit var unmarshaller: Unmarshaller
fun saveSettings() {
FileOutputStream(FILE_NAME).use { outputStream -> marshaller.marshal(settings, StreamResult(outputStream)) }
}
fun loadSettings() {
FileInputStream(FILE_NAME).use { inputStream -> settings = unmarshaller.unmarshal(StreamSource(inputStream)) as Settings }
}
}
private const val FILE_NAME = "settings.xml"
fun main(args: Array<String>) {
val appContext = ClassPathXmlApplicationContext("applicationContext.xml")
val application = appContext.getBean("application") as Application
application.saveSettings()
application.loadSettings()
}
这Application
需要同时marshaller
和unmarshaller
属性。我们
可以使用以下命令来实现此目的applicationContext.xml
:
<beans>
<bean id="application" class="Application">
<property name="marshaller" ref="xstreamMarshaller" />
<property name="unmarshaller" ref="xstreamMarshaller" />
</bean>
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</beans>
此应用程序上下文使用 XStream,但我们可以使用任何其他封送程序
本章后面将描述的实例。请注意,默认情况下,XStream 不需要
任何进一步的配置,因此 bean 定义相当简单。另请注意,XStreamMarshaller
实现两者Marshaller
和Unmarshaller
,所以我们可以参考xstreamMarshaller
bean 在marshaller
和unmarshaller
属性的
应用。
此示例应用程序生成以下内容settings.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>
6.4. XML配置命名空间
您可以使用 OXM 命名空间中的标记更简洁地配置封送程序。 要使这些标记可用,您必须首先在 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:oxm="http://www.springframework.org/schema/oxm" (1)
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> (2)
1 | 引用oxm 图式。 |
2 | 指定oxm 模式位置。 |
架构使以下元素可用:
每个标记在其各自的封送程序部分中都有说明。不过,作为一个例子, JAXB2 封送程序的配置可能类似于以下内容:
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>
6.5. JAXB
JAXB 绑定编译器将 W3C XML 模式转换为一个或多个 Java 类,即jaxb.properties
文件,可能还有一些资源文件。JAXB 还提供了一种方法
从带注释的 Java 类生成模式。
Spring 支持 JAXB 2.0 API 作为 XML 编组策略,遵循Marshaller
和Unmarshaller
中描述的接口Marshaller
和Unmarshaller
.
相应的集成类驻留在org.springframework.oxm.jaxb
包。
6.5.1. 使用Jaxb2Marshaller
这Jaxb2Marshaller
class 实现了 Spring 的Marshaller
和Unmarshaller
接口。它需要上下文路径才能运行。您可以通过设置contextPath
财产。上下文路径是冒号分隔的 Java 包列表
包含架构派生类的名称。它还提供了一个classesToBeBound
财产
它允许您设置封送处理程序要支持的类数组。图式
通过向 Bean 指定一个或多个模式资源来执行验证,如以下示例所示:
<beans>
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>org.springframework.oxm.jaxb.Flight</value>
<value>org.springframework.oxm.jaxb.Flights</value>
</list>
</property>
<property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
</bean>
...
</beans>
XML 配置命名空间
这jaxb2-marshaller
元素配置一个org.springframework.oxm.jaxb.Jaxb2Marshaller
,
如以下示例所示:
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>
或者,可以使用class-to-be-bound
child 元素:
<oxm:jaxb2-marshaller id="marshaller">
<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
...
</oxm:jaxb2-marshaller>
下表描述了可用属性:
属性 | 描述 | 必填 |
---|---|---|
|
封送程序的 ID |
不 |
|
JAXB 上下文路径 |
不 |
6.6. 吉布克斯
JiBX 框架提供了一个类似于 Hibernate 为 ORM 提供的解决方案:一个 绑定定义定义了如何将 Java 对象转换为 Java 对象或从 Java 对象转换的规则 准备好绑定并编译类后,JiBX 绑定编译器 增强类文件并添加代码来处理类的转换实例 from 或 to XML。
有关 JiBX 的更多信息,请参阅 JiBX Web
网站。Spring 集成类驻留在org.springframework.oxm.jibx
包。
6.6.1. 使用JibxMarshaller
这JibxMarshaller
类实现了Marshaller
和Unmarshaller
接口。要作,它需要要封组的类的名称,您可以
使用targetClass
财产。或者,您可以通过设置bindingName
财产。在下面的示例中,我们将Flights
类:
<beans>
<bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
<property name="targetClass">org.springframework.oxm.jibx.Flights</property>
</bean>
...
</beans>
一个JibxMarshaller
为单个类配置。如果要封送多个
类,您必须配置多个JibxMarshaller
具有不同targetClass
属性值。
6.7. XStream
XStream 是一个简单的库,用于将对象序列化为 XML,然后再序列化回来。它不会 需要任何映射并生成干净的 XML。
有关 XStream 的详细信息,请参阅 XStream
网站。Spring 集成类驻留在org.springframework.oxm.xstream
包。
6.7.1. 使用XStreamMarshaller
这XStreamMarshaller
不需要任何配置,可以在
应用程序上下文。要进一步自定义 XML,您可以设置别名映射
它由映射到类的字符串别名组成,如以下示例所示:
<beans>
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="aliases">
<props>
<prop key="Flight">org.springframework.oxm.xstream.Flight</prop>
</props>
</property>
</bean>
...
</beans>
默认情况下,XStream 允许解组任意类,这可能导致
不安全的 Java 序列化效果。因此,我们不建议使用 如果您选择使用
这样做可以确保只有注册的类才有资格进行解组。 此外,您还可以注册自定义
转换器,以确保只有支持的类才能取消封送。你可能会
想要添加一个 |
请注意,XStream 是一个 XML 序列化库,而不是数据绑定库。 因此,它的命名空间支持有限。因此,它相当不适合使用 在 Web 服务中。 |
7. 附录
7.1. XML模式
附录的这一部分列出了用于数据访问的 XML 模式,包括以下内容:
7.1.1.tx
图式
这tx
标签处理在 Spring 的全面支持中配置所有这些 bean
用于交易。这些标记在标题为“事务管理”的章节中进行了介绍。
我们强烈建议您查看'spring-tx.xsd' 文件附带的
春季分配。此文件包含 Spring 事务的 XML Schema
配置,并涵盖了tx 命名空间,包括
属性默认值和类似信息。此文件是内联记录的,因此,
为了遵守 DRY(不要
重复自己)原则。 |
为了完整起见,使用tx
schema,你需要有
Spring XML 配置文件顶部的以下前导码。中的文本
以下代码段引用正确的架构,以便tx
Namespace
可供您:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" (1)
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd (2)
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
</beans>
1 | 声明tx Namespace。 |
2 | 指定位置(与其他架构位置)。 |
通常,当您使用tx 命名空间,您还在使用
元素来自aop 命名空间(因为 Spring 中的声明式事务支持
使用 AOP 实现)。前面的 XML 代码片段包含所需的相关行
引用aop schema,以便aop 命名空间可用
给你。 |
7.1.2.jdbc
图式
要使用jdbc
schema,您需要在
Spring XML 配置文件的顶部。以下代码片段中的文本引用
正确的架构,以便jdbc
命名空间可供您使用:
<?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:jdbc="http://www.springframework.org/schema/jdbc" (1)
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> (2)
<!-- bean definitions here -->
</beans>
1 | 声明jdbc Namespace。 |
2 | 指定位置(与其他架构位置)。 |