前言
现在,越来越多的数据系统开始支持事务了。事务依然成为数据系统不可分割的部分。那么,什么是事务呢?百科的定义是:
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
这里需要注意的是一个词是:单元。既然是个单元,就是一个不可分割的整体。要么全部作用到数据库,要么什么都没做。举个简单的例子,吃饭,现在有一大碗饭(数据),你打算都吃到肚子(数据库)里;但是呢,吃到一半的时候,开始拉肚子(抛异常),后续的饭不能继续吃了。如果没有把吃饭的过程事务化,那么,吃了多少饭,肚子里就有多少,剩下的饭还在外面;但是如果把吃饭的过程事务化,那么,无论吃了多少饭,只要没有全部吃完(抛了异常),必须要把原先吃进去的吐出来才行(回滚),如果全部吃完了,就都吃进去了,不用吐出来(提交)。另外一点要注意的是,一旦事物提交了之后,数据就已经更改了。
所以,事务是个要么0要么1的过程,没有中间状态。
Spring事务
那么,现在大大小小的数据系统那么多,Spring如何整合它们的?如何用一个一致性的概念去统一这些事务呢?
用面对对象的方式思考,肯定是要抽象的。Spring抽象出了一个事务管理器的接口org.springframework.transaction.PlatformTransactionManager,表示各个数据系统的事务管理:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
这个接口定义事务过程的三个必须的方法:
- 获取事务
- 提交事务
- 回滚事务
这个接口的实现有很多,比较常见的有:
- DataSourceTransactionManager(JDBC)
- HibernateTransactionManager
- JpaTransactionManager
- JtaTransactionManager
- MongoTransactionManager
- WebLogicJtaTransactionManager
- WebSphereUowTransactionManager
- RabbitTransactionManager(RabbitMQ)
- …
这些事务管理器可以分为两大类:
- 全局事务管理器
- 本地事务管理器
全局事务管理器可以管理多个数据系统的联合事务,而本地事务管理器只管理单个数据系统的事务。
原生事务管理器
直接使用PlatformTransactionManager时,一般使用下面的样板代码来开启某个数据系统的事务:
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 {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
事务管理样板
诚然,这种方法比较好理解Spring的事务抽象,但是实际使用的时候会有很多样板代码。为解决这个问题,Spring封装了一个样板类TransactionTemplate,这个样板类以一个事务管理器作为构造参数,实现了TransactionOperations接口:
public interface TransactionOperations {
@Nullable
<T> T execute(TransactionCallback<T> action) throws TransactionException;
default void executeWithoutResult(Consumer<TransactionStatus> action) throws TransactionException {
execute(status -> {
action.accept(status);
return null;
});
}
static TransactionOperations withoutTransaction() {
return WithoutTransactionOperations.INSTANCE;
}
}
这样,开始事务过程的代码就变成了下面这样:
有返回值
transactionTemplate.execute(new TransactionCallback() {
// the code in this method executes in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
无返回值
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
需要回滚
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
相比于直接使用PlatformTransactionManager,省了不少代码的书写量。
但是,Spring是基于POJO的编程风格。手动编写事务处理流程还不够Spring,因此想到了,能不能直接定义一个类或者方法,加个注解就是事务化的过程呢?答案是可以的。
声明式事务管理
如果说上面的代码叫做编程式的事务管理,那么采用注解的方法定义事务就叫做声明式的事务管理。
Spring提供了两个注解来定义事务:@EnableTransactionManagement和@Transactional。其中@EnableTransactionManagement(配合TransactionManagementConfigurer)用来定义程序要使用的事务管理器。当然,可以定义多个,比如定义一个JPA的,再定义一个Mongo的。下面演示了如何定义事务管理器:
@EnableTransactionManagement
@Configuration
public class TransactionConfig implements TransactionManagementConfigurer {
private final EntityManagerFactory entityManagerFactory;
private final MongoDbFactory mongoDbFactory;
public TransactionConfig(EntityManagerFactory entityManagerFactory, MongoDbFactory mongoDbFactory) {
this.entityManagerFactory = entityManagerFactory;
this.mongoDbFactory = mongoDbFactory;
}
@NonNull
@Override
public TransactionManager annotationDrivenTransactionManager() {
return transactionManager();
}
@Primary
@Bean
public PlatformTransactionManager transactionManager() { //jpa的事务管理器
return new JpaTransactionManager(entityManagerFactory);
}
@Bean
public PlatformTransactionManager mongoTM() { //mongo的事务管理器
return new MongoTransactionManager(mongoDbFactory);
}
}
而@Transactional是用来定义事务过程的。如果一个方法需要事务化(即要么成功提交,要么失败回滚),只需用@Transactional注释即可。下面例子展示了如何定义事务化过程:
@Transactional(rollbackFor = {IOException.class})
public void reset() throws IOException {
System.err.println("reset");
QuiltHello zlb = quiltHelloJpa.findByName("zlb");
if (zlb != null) {
zlb.setNumber(0);
zlb = quiltHelloJpa.save(zlb);
System.err.println(zlb);
throw new IOException("my error");
}
}
当方法执行时抛出了IOException,则会自动回滚,因为rollbackFor属性定义触发自动回滚的异常。
@Transactional注解也能注解类,表示该类的所有公开方法需要事务化。而注释方法则不限方法的修饰符(公开,保护,私有都可以)。
Spring声明式事务原理
那么,为什么定义一个带注解的方法就定义了事务呢?答案是AOP技术。
当程序定义了一个带@Transactional的方法或类时,框架实际上创建了一个事务代理类。在这个代理类中,框架自动补全了事务流程(开启事务,提交事务,回滚事务)相关的代码。而作为开发者,只需指定框架什么异常需要回滚,什么异常无需回滚即可。
下面是@Transactional注解的全部属性:
- value:等价于transactionManager
- transactionManager:执行事务的事务管理器(特别是定义了多个事务管理器,非默认的要显示指定)
- propagation:传播方式
- isolation:隔离级别
- timeout:事务超时时间
- readOnly:是否只读
- rollbackFor:触发自动回滚的异常类
- rollbackForClassName:触发自动回滚的异常类的名称
- noRollbackFor:不触发自动回滚的异常类
- noRollbackForClassName:不触发自动回滚的异常类的名称
可以看到,rollbackFor和noRollbackFor定义了什么异常能够触发和不触发自动回滚。这样,开发者节省了很多编写try-catch的时间,程序结构也一目了然。
默认情况下,Spring只能对那些被代理的带有@Transactional注解的方法事务化,而那些手动调用带有@Transactional注解的方法则不会事务化。这是由@EnableTransactionManagement的mode属性决定的。下面是@EnableTransactionManagement注解的所有属性:
- proxyTargetClass:mode为proxy时有作用,为true表示用CGLIB的类代理,为false表示用Java的接口代理;默认为false
- mode:事务建议被应用的方式,PROXY(JDK代理)或ASPECTJ(AspectJ织入),默认是PROXY
- order:方法有多个建议时事务建议被应用的顺序
如果mode为ASPECTJ,可以自定义事务方法,不限于被代理的方法,自调用的方法也可以,相对于PROXY(默认)要麻烦不少。