如果使用的Spring进行测试,则需要在xml中开启基于注解的事务:
<tx:annotation-driven></tx:annotation-driven>
<!–配置事务管理器–>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
根据在同类中调用和不同类之间调用进行分类实验:
同类中调用
数据库初始数据:
我们定义了两个方法,都是根据用户名修改密码的方法,调用testA()方法。
1、A方法中无事务,B方法中有事务
@Service("TransactionService")
public class TransactionServiceImpl implements TransactionService {
//注入dao层对象
@Autowired
private IUserDao iUserDao;
@Override
public void testA() {
int num=iUserDao.updatePWDByUserName("hu", "3333");
testB("ding","3333");
}
@Transactional
@Override
public int testB(String username, String newPassword) {
int num=iUserDao.updatePWDByUserName(username, newPassword);
int i=1/0;
return 0;
}
}
运行日志信息:
从日志信息中我们可以发现并没有出现事务回滚情况,再来看数据库中数据。
也就是说,在同一个类中的不同方法调用中,A方法调用B方法,A中无事务,B中有事务,此时事务不会生效。
Spring采用动态代理(AOP)实现对bean的管理和切片,它为每个class都会生成一个代理对象。
只有在代理对象之间调用时,可以触发切面逻辑。而同一个class中,方法A调用方法B,且方法A无事务,
调用的是原对象的方法,而不是通过代理对象,因此Spring无法切换到这次调用,也就无法通过注解保证事务性了。
注意:下面每一步测试之前,我都将数据库中的密码恢复为1111的状态。
2、A方法中有事务,B方法中无事务
@Service("TransactionService")
public class TransactionServiceImpl implements TransactionService {
//注入dao层对象
@Autowired
private IUserDao iUserDao;
@Transactional
@Override
public void testA() {
int num=iUserDao.updatePWDByUserName("hu", "3333");
testB("ding","3333");
}
@Override
public int testB(String username, String newPassword) {
int num=iUserDao.updatePWDByUserName(username, newPassword);
int i=1/0;
return 0;
}
}
此时我们发现,在同一个类中的不同方法调用中,A方法调用B方法,A中有事务,B中无事务,事务A生效,
对于B中抛出的异常事务也能进行回滚操作。
3、A方法中有事务,B方法中有事务
@Service("TransactionService")
public class TransactionServiceImpl implements TransactionService {
//注入dao层对象
@Autowired
private IUserDao iUserDao;
@Transactional
@Override
public void testA() {
int num=iUserDao.updatePWDByUserName("hu", "3333");
testB("ding","3333");
}
@Transactional
@Override
public int testB(String username, String newPassword) {
int num=iUserDao.updatePWDByUserName(username, newPassword);
int i=1/0;
return 0;
}
}
此时我们发现,在同一个类中的不同方法调用中,A方法调用B方法,A中有事务,B中有事务,此时事务B能进行回滚操作。但是也是利用的事务A进行的回滚操作,看Rolling back处使用的回滚代理就知道了。
总结:在一个类的内部,事务之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务。
不同类之间的调用。
首先将两个方法放入到不同类中:
方法B:
@Service("TransactionService2")
public class TransactionService2Impl implements TransactionService2 {
@Autowired
private IUserDao iUserDao;
@Override
public int testB(String username, String newPassword) {
int num=iUserDao.updatePWDByUserName(username, newPassword);
int i=1/0;
return 0;
}
}
方法A
@Service("TransactionService")
public class TransactionServiceImpl implements TransactionService {
@Autowired
private IUserDao iUserDao;
@Autowired
private TransactionService2 transactionService2;
@Override
public void testA() {
int num=iUserDao.updatePWDByUserName("hu", "3333");
transactionService2.testB("ding","3333");
}
}
1、A方法中无事务,B方法中有事务
可以看到,此时方法B中的事务触发了回滚操作,当然由于方法A中并没有事务,因此,方法A中的操作并没有进行回滚
。此时对于需要保证事务的方法B来说,在A中调用了B,通过代理对象调用的,因此Spring切换到这次调用
,也就通过注解保证事务性了。
2、A方法中有事务,B方法中无事务
可以看到,方法A中有事务,方法B中无事务,方法B执行出现了异常,由于方法A中有事务,因此进行了回滚操作。
3、A方法中有事务,B方法中有事务
方法A和方法B中都有事务,此时进行了事务回滚操作。
总结:不同类之间的方法调用,如类A的方法a()调用类B的方法b(),这种情况事务是正常起作用的。只要方法a()或b()配置了事务,运行中就会开启事务,产生代理。
如有错误,希望各大佬及时指正。
最后介绍Spring中事务的隔离级别和传播行为:事务是逻辑处理原子性的保证手段,通过使用事务控制,可以极大的避免出现逻辑处理失败导致的脏数据等问题。
事务最重要的两个特性,是事务的传播行为和数据隔离级别。传播行为定义的是事务的控制范围,事务隔离级别定义的是事务在数据库读写方面的控制范围。
事务的7种传播行为:
PROPAGATION_REQUIRED:默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。(如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。)
PROPAGATION_SUPPORTS:从字面意思就知道,supports,支持,该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少。(如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。)
PROPAGATION_MANDATORY: 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。(如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。)
PROPAGATION_NEVER:该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。(总是非事务地执行,如果存在一个活动事务,则抛出异常)
PROPAGATION_NESTED:字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。(如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED属性执行。)
PROPAGATION_REQUIRES_NEW:从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。(总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。)
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。 怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
PROPAGATION_NOT_SUPPORTED:这个也可以从字面得知,not supported ,不支持,当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。(总是非事务地执行,并挂起任何存在的事务。)
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就可以了。
嵌套事务:嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point。看几个问题就明了了:
如果子事务回滚,会发生什么?
父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
如果父事务回滚,会发生什么?
父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。那么:
事务的提交,是什么情况?
是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。
五个隔离级别:
ISOLATION_DEFAULT:使用底层数据存储的默认隔离级别。所有其他级别都对应JDBC隔离级别。(默认)
ISOLATION_READ_UNCOMMITTED:读未提交(表示脏读、不可重复读和幻读可能发生)
ISOLATION_READ_COMMITTED:读已提交(表示禁止脏读;不可重复读和可能发生幻读。)
ISOLATION_REPEATABLE_READ:可重复读(表示防止脏读和不可重复读;可能发生幻读。)
ISOLATION_SERIALIZABLE:序列化(表示脏读、不可重复读和幻读不会发生。)
————————————————
版权声明:本文为CSDN博主「童话ing」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dl962454/article/details/118275335