在这篇文章中,我们将深入了解spring的事务管理。@Transactional实际上是怎么工作的
JPA和事务管理
注意到JPA自己并不提供任何声明的类型管理是很重要的,当在一个依赖注入容器的外部使用JPA的时候,事务需要开发人员使用代码来处理。
UserTransaction utx = entityManager.getTransaction();
try {
utx.begin();
businessLogic();
utx.commit();
} catch(Exception ex) {
utx.rollback();
throw ex;
}
这种管理事务的方式在代码中非常的清晰,但是有下边这几个缺点
1、容易代码重复并且容易出错
2、出错会有很高的代价
3、出错之后很难调试和复制
4、降低代码的可读性
5、这个方法调用另一个事务的时候会发生什么呢?
使用spring@Transactional
使用spring的@Transactional,上边的代码可以简化为下边这些
@Transactional
public void businessLogic() {
... use entity manager inside a transaction ...
}
这更方便并且有更高的可读性,在spring中这是当前推荐的处理方式。通过使用
@
Transactional,很多重要的切面比如事务的传播被自动的处理,在这种情况下,如果另一个事务方法被businessLogic()调用,那个方法有加入正在进行的事务的选择性。这个强大的机制的一个潜在的缺点是它隐藏了运行,这使得调试非常的困难。
@Transactional意味着什么
关于@Transactional的一个关键点是需要考虑两个独立的概念,每一个都有它独立的生命周期
1、持久化上下文
2、数据库事务
这个Transactional标签自己定义了单个数据库事务的范围,数据库事务在持久化上下文的内部发生。在JPA中持久化上下文是EntityManager,使用hibernate的session作为内部的实现(当使用hibernate作为持久化的提供者的时候)。持久化上下文仅仅是一个同步器对象,追踪有限Java对象集合的状态,确保在这些对象上的改变最终都会持久到数据库中去。这个概念和数据库的事务有很大的不同。一个实体管理器可以在好几个数据库事务中使用,实际上也是这样的。
什么时候EntityManager 跨越多个数据库事务
最常见的例子是当应用程序使用open session处理懒加载的异常,可以查看先前的博客。在这个例子中查询在不同的数据库中的视图层运行,而不是在业务逻辑中的一个,但是是通过同一个实体管理器处理的。另一个例子是当持久化上下文被开发者标注为PersistenceContextType.EXTENDED,意味着它可以在多个请求中存活。
什么定义了EntityManager和事务的关系
这实际上是开发者的一个选项,大多数情况下使用JPA的实体管理器的时候采用“实体管理器一个应用事务”的模式,下边是注入一个实体管理器的最常用的方式。
@PersistenceContext
private EntityManager em;
这里我们默认的是在上边说的那种模式中,在这种模式中,如果我们在
Transactional方法的内部使用实体管理器,那么这个方法将会在单个的数据库事务里运行。
@PersistenceContext 怎么运行的
一个问题是PersistenceContext怎么在容器启动的时候仅仅注入一次,考虑到实体管理器具有很短的生命周期并且有多个请求。答案是不可以,EntityManager是一个接口,并且在springbean中注入的不是实体管理器自己,而是一个上下文的代理,在运行时代理了一个具体的管理器。通常用来代理的具体的类是SharedEntityManagerInvocationHandler,这可以通过调试得到确认。
那么@Transactional又是怎么工作的呢
持久化上下文的代理实习了entityManager不是唯一的需要声明的组件,实际上需要三个不同的组件
1、entityManager代理自己
2、事务切面
3、事务管理器
我们每一个都看一下看看他们是怎么工作的
事务切面
事务切面是一种环绕型的切面,在业务方法之前和之后被调用,实现切面的具体类是TransactionInterceptor。事务切面有两个主要的责任:
1、在开始之前,切面提供切入点来决定业务方法是在当前的数据库事务中运行还是需要开启一个新的事务
2、在结束之后,切面需要决定事务应该被提交、回滚还是继续运行
在开始的时候事务切面本身并不提供任何逻辑决定,开启一个新的事务的决定由事务管理器来做出
事务管理器
事务管理器需要回答两个问题
1、是否应该一个新的实体管理器需要被创建
2、是否需要开启一个新的数据库事务
这些应该在事务管理器“before”逻辑被调用之前决定,事务管理器需要根据以下的情形进行决定
1、是否当前有正在运行的事务
2、方法的传播属性是否已经创建(例如:REQUIRES_NEW 会开启一个新的事务)
如果一个事务管理器需要决定创建一个新的事务,那么它将做以下的工作。
1、创建一个新的实体管理器
2、对当前的线程隐藏实体管理器
3、获取数据库连接池的链接
4、对当前的线程隐藏链接
实体管理器和链接都要使用ThreadLocal变量绑定到当前的线程。当事务运行的时候他们存储在当前的线程中,事务管理器决定在不使用他们时候清除的时机。程序的任何部分需要当前实体管理器或者链接的时候可以在线程中获取他们,实体管理器代理正是做这件事的程序组件。
entityManager代理
实体管理器代理(我们之前介绍过的)是最后的一个疑惑,当业务方法调用entityManager.persist(),这个调用并不会直接请求实体管理器。业务方法会调用代理,在线程中获取当前的当前的实体管理器,这个实体管理器是事务管理器放进去的。知道了@Transactional运行机制了,那么我们来看一下通常的spring配置来让他运行成功。
把他们放在一起
我们看看怎么把三个组件正确的组合在一起让他们运行,我们首先定义一个实体工厂。
@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;
}
}
其次再配置一个事务管理器来应用事务切面
@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的声明事务管理机制非常强大,但是很容易错误的使用和配置错误
理解内部工作原理对异常情况的解决非常有帮助
需要记住的最重要的事情是数据库事务和持久化上下文,每一个都不具有明显的生命周期
原文地址:http://bjhades.org/how-does-spring-transactional-really-work/