在SSM项目中,经常在业务层的类或者方法上看到@Transactional注解,只是知道这个注解的作用是进行事务管理,但是具体有哪些属性,在什么情况下进行回滚,确是不那么清楚。所以在网上看了一些视频和博客,初步理解了Spring的事务管理。这里记录的主要是一些概念,如果想要能快速学会使用和理解事务管理,建议去相关视频。我看的是https://www.imooc.com/learn/478。
一、要理解Spring的事务管理,首先要了解什么是事务,事务有哪些特性,不遵守这些特性会出现什么问题。只有了解这些,才会理解事务管理要做什么。
1.事务:逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
2.事务的特性
(1)原子性:事务是一个不可分割的工作单位,事务中的操作在提交后要么全部成功,要么全部失败。
(2)一致性:事务提交前后的数据的完整性必须保持一致。
(3)隔离性:多个事务之间不会产生相互干扰,事务间的数据要相互隔离。
(4)持久性:事务一单被提交,它对数据库的改变将是永久的,即使数据库发生故障也不应对其产生影响。
3.如果事务不遵守上述的特性,则会出现脏读、不可重复读和幻读的问题
(1)脏读:一个事务读取一个另一个事务改写但是还未提交的数据。如果这些数据回滚,则第一个事务读取到的数据将是无效的。
(2)不可重复读:在同一个事务中,多次读取同一个数据的结果有所不同。
(3)幻读:在一个事务读取数据后,另一个事务插入一些记录。当再次查询时,第一个事务将会发现原来没有的记录。
二、了解了这些基础概念之后,再去了解Spring为事务管理提供的接口。
1.PlatformTransactionManager:平台事务管理器接口。Spring为不同的持久化框架提供了不同的实现,如为Spring JDBC或MyBatis提供了DataSourceTransactionManager,
为Hibernate提供了HibernateTransactionManager。事务管理器中定义了一些具体的事务操作,如事务提交、事务回滚。
2.TransactionDefinition:事务定义信息。如事务隔离级别、传播行为、超时和只读。
(1)事务隔离级别
a)READ_UNCOMMITTED:允许读取另一个事务修改了但是还未提交的数据,会出现脏读、不可重复读和幻读的情况
b)READ_COMMITTED:允许在另一个事务提交后读取数据,可防止脏读,但是会出现不可重复读和幻读。
c)REPEATABLE_READ:对相同的数据的读取是一致的,除非事务本身修改了数据。可防止脏读和不可重复读,会出现幻读。
d)SERIALIZABLE:该隔离级别是完全遵守ACID隔离级别的,能够确保不会发生脏读、不可重复读和幻读。但是由于读取相同数据的事务是完全串行读取的,所以执行速度非常慢。
e)DEAFULT:Spring默认使用该隔离级别。表示使用后端数据库的默认隔离级别,MySQL的默认隔级别是REPEATABLE_READ,ORACLE的默认隔离级别是READ_COMMITTED。
(2)传播行为,用于解决业务层中多个方法调用的问题
a)PROPAGATION_REQUIRED:支持当前事务。如果当前事务存在,将另一个事务添加到当前事务中;如果当前事务不存在,则创建一个新的事务,并将第一个包含进去。
b)PROPAGATION_SUPPORTS:支持当前事务。如果当前事务不存在,则以非事务方式执行。
c)PROPAGATION_MANADATORY:支持当前事务。如果当前事务不存在,抛出异常。
d)PROPAGATION_REQUIRES_NEW:新建事务,挂起当前事务,新建事务完成后再执行当前事务。
e)PROPAGATION_NOT_SUPPORTS:以非事务方式执行。如果存在当前事务,则挂起当前事务。另一个方法执行完成后再执行当前事务。
f)PROPAGATION_NEVER:以非事务方式运行。如果存在当前事务,则抛出异常。
g)PROPAGATION_NESTED:若当前事务存在,则将第二个事务嵌套到当前事务中。
(3)超时:默认为-1。设置超时时间后事务还未完成,则回滚事务。
(4)只读:指定事务为只读事务,默认false。如果设置为true,事务中存在新增、删除和修改数据库则会抛出异常并回滚。
3.TransactionStatus:事务具体运行状态。
三、配置事务管理
1.编程式事务管理:通过在方法中编写事务管理程序来实现事务管理,这种方式对业务层代码有侵入,很少使用。
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
}
});
2.声明式事务管理:
(1)使用代理实现事务管理,需要为每一个类添加一个代理类,当需要事务管理的类增多时,添加代理类很麻烦。所以这个很少使用。
<!--注入代理--> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!--注入目标对象--> <property name="target" ref="declarativeAccountService" /> <!--注入事物管理器--> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> </props> </property> </bean>
@Resource(name="accountServiceProxy") private DeclarativeAccountService declarativeAccountService;
(2)使用AspectJ实现事务管理,需要配置增强通知和切面。需要事务管理的包或类很清晰。
<!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <!--配置切入点--> <aop:pointcut id="aspectJPoint" expression="execution(* imooc.spring.transaction.manage.service.AspectJAccountService+.*(..))" /> <!--配置切面--> <aop:advisor advice-ref="txAdvice" pointcut-ref="aspectJPoint" /> </aop:config>
(3)基于注解的事务管理:需要的步骤有1.配置事务管理器,可以在xml中配置,同时也可以使用注解配置 2.在类或方法上标注@Transactional注解。
<tx:annotation-driven transaction-manager="transactionManager" />
四、使用@Transactional注解需要注意的几点
1.在类和方法上都可以标注@Transactional注解,同时在类或方法上标有@Transactional注解时,方法上的事务管理属性胡会覆盖类上的属性。
2.@Transactional注解只有标注在public方法上时才会生效,标注在其它方法上不会报错,但是不能生效。
3.默认情况下,事务中如果抛出未检查异常(RuntimeException或其子类)和ERROR时,事务将回滚;除此之外的异常将不会回滚。
在阿里的p3c代码规范中要求在标注事务管理时添加rollbackFor属性,即@Transactional(rollbackFor=Exception.class)。
上述概念都是在看完博客和视频后的一些记录,如果有错误的地方欢迎指正。如果有兴趣的,可以查阅相关博客和视频链接如下: