最近做的项目有特别留意到spring的 @Transactional,于是,在网上查找一番。
本文将深入研究Spring的事务管理。主要介绍@Transactional
在底层是如何工作的。之后的文章将介绍:
很重要的一点是JPA本身并不提供任何类型的声明式事务管理。如果在依赖注入容器之外使用JPA,事务处理必须由开发人员编程实现。
1
2
3
4
5
6
7
8
9
10
11
12
|
UserTransaction utx = entityManager.getTransaction();
try
{ utx.begin();
businessLogic(); utx.commit();
}
catch (Exception ex) {
utx.rollback();
throw
ex; } |
这种方式的事务管理使事务范围可以在代码中很清晰地表达出来,但它有以下缺点:
使用Spring @Transactional
,上面的代码就简化为:
1
2
3
4
|
@Transactional public
void businessLogic() { ... use entity manager inside a transaction ... } |
代码更加简洁,可读性更好,也是目前Spring中事务处理的推荐方式。
通过使用@Transactional
,事务传播等很多重要方面可以自动处理。这种情况下如果businessLogic()
调用了其他事务方法,该方法将根据选项确定如何加入正在运行事务。
这个强大机制的一个潜在缺点是它隐藏了底层的运行,当它不能正常工作时很难调试。
@Transactional
含义关于@Transactional
,关键点之一是要考虑两个独立的概念,它们都有各自的范围和生命周期:
@Transactional
本身定义了单个事务的范围。这个事务在persistence context的范围内。
JPA中的持久化上下文是EntityManager
,内部实现使用了Hibernate Session
(使用Hibernate作为持久化provider)。
持久化上下文仅仅是一个同步对象,它记录了有限集合的Java对象的状态,并且保证这些对象的变化最终持久化到数据库。
这是与单个事务非常不同的概念。一个Entity Manager可以跨越多个事务使用,而且的确是这样使用的。
最常见的情况是应用使用Open Session In View模式处理懒初始化异常时,之前的文章介绍过这种做法的优势和劣势。
这种情况下视图层运行的多个查询处于独立的事务中,而不是单事务的业务逻辑,但这些查询由相同的entity manager管理。
另一种情况是开发人员将持久化上下文标记为PersistenceContextType.EXTENDED
,这表示它能够响应多个请求。
这由应用开发者来选择,但是JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每个事务都有自己的实体管理器)模式。entity manager注入的常用方法是:
1
2
|
@PersistenceContext private
EntityManager em; |
这里默认为“Entity Manager per transaction”模式。这种模式下如果在@Transactional
方法内部使用该Entity Manager,那么该方法将在单一事务中运行。
随之而来的问题就是@PersistenceContext
如何仅在容器启动时注入entity manager,假定entity manager生命周期很短暂,而且每次请求需要多个entity manager。
答案是它不能:EntityManager
是一个接口,注入到spring bean中的不是entity manager本身,而是在运行时代理具体entity manager的context aware proxy(上下文感知代理)。
通常用于代理的具体类为SharedEntityManagerInvocationHandler
,借助调试器可以确认这一点。
实现了EntityManager
接口的持久化上下文代理并不是声明式事务管理的唯一部分,事实上包含三个组成部分:
看一下这三部分以及它们之间的相互作用。
事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor
。
事务的切面有两个主要职责:
在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。
事务管理器需要解决下面两个问题:
这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:
REQUIRES_NEW
总要开始新事务)如果事务管理器确定要创建新事务,那么将:
使用ThreadLocal变量将entity manager和数据库连接都绑定到当前线程。
事务运行时他们存储在线程中,当它们不再被使用时,事务管理器决定是否将他们清除。
程序的任何部分如果需要当前的entity manager和数据库连接都可以从线程中获取。
EntityManager proxy(前面已经介绍过)就是谜题的最后一部分。当业务方法调用entityManager.persist()
时,这不是由entity manager直接调用的。
而是业务方法调用代理,代理从线程获取当前的entity manager,前面介绍过事务管理器将entity manager绑定到线程。
了解了@Transactional
机制的各个部分,我们来看一下实现它的常用Spring配置。
如何将三个部分组合起来使事务注解可以正确地发挥作用呢?首先定义entity manager工厂。
这样就可以通过持久化上下文注解注入Entity Manager proxy。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Configuration public
class EntityManagerFactoriesConfiguration { @Autowired private
DataSource dataSource; @Bean (name =
"entityManagerFactory" ) public
LocalContainerEntityManagerFactoryBean emf() { LocalContainerEntityManagerFactoryBean emf = ... emf.setDataSource(dataSource); emf.setPackagesToScan( new
String[] { "your.package" }); emf.setJpaVendorAdapter( new
HibernateJpaVendorAdapter()); return
emf; } } |
下一步实现配置事务管理器和在@Transactional
注解的类中应用事务的切面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Configuration @EnableTransactionManagement public
class TransactionManagersConfig { @Autowired EntityManagerFactory emf; @Autowired private
DataSource dataSource; @Bean (name =
"transactionManager" ) public
PlatformTransactionManager transactionManager() { JpaTransactionManager tm =
new
JpaTransactionManager(); tm.setEntityManagerFactory(emf); tm.setDataSource(dataSource); return
tm; } } |
注解@EnableTransactionManagement
通知Spring,@Transactional
注解的类被事务的切面包围。这样@Transactional
就可以使用了。
Spring声明式事务管理机制非常强大,但它可能被误用或者容易发生配置错误。
当这个机制不能正常工作或者未达到预期运行结果等问题出现时,理解它的内部工作情况是很有帮助的。
需要记住的最重要的一点是,要考虑到两个概念:事务和持久化上下文,每个都有自己不可读的明显的生命周期。
原文链接:
javacodegeeks 翻译: ImportNew.com
- hejiani
译文链接: http://www.importnew.com/12300.html
这是一篇翻译的文章,挺好的啊,觉得好请顶一下!
关于Spring框架的 @Transactional工作原理介绍
原文地址:http://blog.csdn.net/yang_best/article/details/44201507