spring 事物管理
1. 事务的特性 ACID
- 原子性:事务包含的所有操作要么全部成功,要么全部失败回滚
- 一致性:事务执行前和执行后都必须处于一致性状态,拿转账来说,A账户和B账户共有1000元,那么不论A和B如何转账,事务结束后,A账户钱+B账户钱=1000
- 隔离性:多个并发事务之间互相隔离
- 持久性:事务一旦提交成功,那么就必须持久化到数据库,即使数据库系统遇到问题,不能丢失提交后的数据
2.事务的隔离级别
• ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
• ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
• ③ Read committed (读已提交):可避免脏读的发生。
• ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
mysql默认的隔离级别是
Repeatable read
3.出现的问题
- 脏读: 读取一个未提交的事务中的数据
- 不可重复读:一个事务中多次查询,返回的结果不一致,这是因为在查询间隔中,有另一个事务修改并提交了数据
- 幻读: 幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
不可重复读重点是修改,而幻读重点是新增或删除。
4.spring 事务管理的核心接口
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上图所示,Spring并不直接管理事务,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,也就是将事务管理的职责委托给Hibernate或者JDBC等持久化机制所提供的相关平台框架的事务来实现。
5.事务的传播行为
- PROPAGATION_REQUIRED:没有事务创建一个事务,如果有事务,则使用该事务
- PROPAGATION_SUPPORTS:如果有事务,则使用该事务;如果没有事务,则以非事务的方式运行
- PROPAGATION_MANDATORY:如果有事务,则使用该事务;如果没有事务,则抛出异常
- PROPAGATION_REQUIRES_NEW :如果有事务,则将当前事务挂起,创建一个新的事物;如果没有事务,则创建一个新的事务
说明:内层事务失败,会导致外层事务回滚;
外层事务失败,不会导致内层事务回滚;
如果外层事务捕获了内层事务的异常,内层事务回滚,外层事务正常提交
- PROPAGATION_NOT_SUPPORTED :以非事务的方式运行,如果有事务,则将当前事务挂起
- PROPAGATION_NEVER :以非事务的方式运行,如果有事务,则抛出异常
- PROPAGATION_NESTED :如果有事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务
6.声明式事务配置(AOP):数据源,事务管理器,事务传播特性,切面
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/> <property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.210.61:1521:orcl"/> <property name="user" value="stlpd"/> <property name="password" value="stlpd"/> </bean> --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://192.168.210.61:3306/test"/> <property name="user" value="root"/> <property name="password" value="hefeiOrcl"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- spring 声明式事物配置 1. aop 切面管理 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRES_NEW"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <tx:method name="find*" propagation="REQUIRED" read-only="true"/> <tx:method name="select*" propagation="REQUIRED" read-only="true"/> <tx:method name="never*" propagation="NEVER" /> <tx:method name="mandatory*" propagation="MANDATORY" /> <tx:method name="requires_new*" propagation="REQUIRES_NEW" /> <tx:method name="notSupported*" propagation="NOT_SUPPORTED" /> <tx:method name="nested*" propagation="NESTED" /> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <!-- 即应该在哪些类的哪些方法上面进行事务切入--> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution( * com.test.transaction.*.*(..))" /> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="txAdvice" /> </aop:config> <bean id="userService" class="com.test.transaction.UserService"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> <property name="bsService" ref="bsService"></property> </bean> <bean id="bsService" class="com.test.transaction.BsService" > <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> </beans>
package com.test.transaction; import org.springframework.jdbc.core.support.JdbcDaoSupport; /** * 在同一个业务类里面,即使声明REQUIRES_NEW 也不会新启动一个事务,必须调用另一个类里面的方法 * * * 声明式事务是通过aop切面函数来处理,切面函数是通过代理,即A方法调用B方法是通过this.B调用,而不是通过切面函数 * ,如果通过applicationContext.getBean(A.class).B() 应该是可以的 * * @author: chenzb * @version: v1.0 * @description: * @date:2020年3月5日 */ public class UserService extends JdbcDaoSupport{ private BsService bsService; /** * read-only * * @param id * @return */ public void getUserById(){ String sql = "insert into ccc(id,name) values('8342','ccsz')"; this.getJdbcTemplate().execute(sql); } /** * 事务的原子性: 事务包含的全部操作,要么全部成功,要么全部失败,回滚所有的操作 * * */ public void update(){ String sql = "update ccc set name = 'wwwwwww' where id = '123' "; this.getJdbcTemplate().execute(sql); int i = 1/0; } public void start_required(){ String sql = "update ccc set name = 'hhhh' where id = '123' "; this.getJdbcTemplate().execute(sql); try { int i = 1/0; } catch (Exception e) { } } public void start_never(){ this.bsService.never(); } public void start_mandatory(){ this.bsService.mandatory(); } public void start_supports(){ this.bsService.supports(); } public void start_not_supported(){ this.bsService.notSupported(); } public void start_requires_new(){ String sql = "update ccc set name = 'sdfghppppp' where id = '123' "; this.getJdbcTemplate().execute(sql); try { this.bsService.requires_new(); } catch (Exception e) { } // int i = 1/0; } public void start_nested(){ String sql = "update ccc set name = 'GGGG111222' where id = '123' "; this.getJdbcTemplate().execute(sql); try { this.bsService.nested(); } catch (Exception e) { } // this.bsService.nested(); // int i = 1/0; } public BsService getBsService() { return bsService; } public void setBsService(BsService bsService) { this.bsService = bsService; } }
package com.test.transaction; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class BsService extends JdbcDaoSupport{ /** * 以非事务的方式运行,如果当前存在事务,则抛出异常 * 非事务:操作不具有原子性,即后面出现异常后,前面的操作不会回滚 * */ public void never(){ String sql = "update ccc set name = 'www.baidu.com1111' where id = '123'"; this.getJdbcTemplate().execute(sql); int i = 1/0; } /** * 支持当前事务,如果当前没有事务,则抛出异常 * * */ public void mandatory(){ } /** * * 支持当前事务,如果当前没有事务,则以非事务的方式运行 * */ public void supports(){ } /** * 新建事务,如果当前存在事务,则将当前事务挂起 * 外层事务失败,不会导致内层事务回滚 * 内层事务失败,会导致外层事务回滚 * 如果外层事务捕获了内层事务抛出的异常,在内层事务回滚,外层事务正常提交 * */ public void requires_new(){ // String sql = "update ccc set name = 'sdfgh' where id = '123' "; // this.getJdbcTemplate().execute(sql); int i = 1/0; } /** *以非事务的方式运行,如果当前存在事务,则将当前事务挂起 * * */ public void notSupported() { String sql = "update ccc set name = 'kkkooo111' where id = '123' "; this.getJdbcTemplate().execute(sql); int i = 1/0; } /** * 外层事务失败,会导致内层事务回滚 * 内层事务失败,不会导致外层事务回滚 * */ public void nested() { // String sql = "update ccc set name = 'sdfgh' where id = '123' "; // this.getJdbcTemplate().execute(sql); int i = 1/0; } }
package com.test.transaction; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/resource/application-tx1.xml"); UserService us = (UserService)context.getBean("userService"); //us.getUserById(); BsService bs = (BsService)context.getBean("bsService"); // bs.neverDo(); // us.start_required(); // us.start_never(); // us.update(); // bs.mandatory(); // us.start_mandatory(); // bs.supports(); // us.start_supports(); // bs.requires_new(); // us.start_requires_new(); // us.start_not_supported(); us.start_nested(); } }
7.spring 事务在同一个类中互相调用不生效的原因分析
spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。
而在同一个class中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象。所以Spring无法切到这次调用,也就无法通过注解保证事务性了。
也就是说,在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用。
参考文档:https://www.cnblogs.com/ynyhl/p/12066530.html
https://blog.csdn.net/qq_42914528/article/details/83743726