java事务不⽣效场景_Spring事务失效场景原理及解决⽅案1.事务失效-⾃⾝调⽤(通过REQUIRES、REQUIRES_NEW传播属性):⾃⾝调⽤即调该类⾃⼰的⽅法。
同类OrderServiceImpl 中 doSomeThing()⽅法 不存在事务,该⽅法去调⽤本类中的存在事务注解的 insertAndUpdateOrderInfo() ⽅法。但是insertAndUpdateOrderInfo() 其实是⽆法保证预想的事务性。
⽰列验证:
OrderServiceImpl.insertAndUpdateOrderInfo⽅法中upateData(updateParam) 发⽣异常时,insertData(insertParam) 未发⽣回滚
说明:⾃⾝调⽤时候,⽆论是以下哪种传播属性均是⽆效的,因为⾃⾝调⽤时的⼦⽅法压根就不会被AOP 代理拦截到以下的这两种⽅式均经过验证,⽆法保证⼦⽅法事务的有效性
@Transactional(propagation = Propagation.REQUIRES)
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Controller
@RequestMapping("/trans")
public class TransactionalController {
@Autowired
OrderService orderService;
@RequestMapping("/test.do")
@ResponseBody
public void getIndex(HttpServletRequest request, HttpServletResponse response, Model model) {
orderService.doSomeThing();
}
}
@Service
public interface OrderService {
/
*
*添加订单和修改其他订单信息
* */
public void doSomeThing();
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
TransBusiness transBusiness;
@Override
public void doSomeThing() {
insertAndUpdateOrderInfo();
}
@Transactional(propagation = Propagation.REQUIRED)
public void insertAndUpdateOrderInfo(){
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String updateTime = dateFormat.format(date);
//步骤1:插⼊订单记录信息
String[] insertParam = {"555555555", "977723233", updateTime, updateTime};
transBusiness.insertData(insertParam);
//步骤2:修改订单记录信息
String[] updateParam = {"1111111111", updateTime, "1"};
transBusiness.upateData(updateParam);
}
}
@Service
public class TransBusiness {
@Autowired
JdbcTemplate dalClient;
public void insertData(String[] param) {
Map resultMap = new HashMap<>();
String sql = "INSERT INTO test_order (`order_no`, `cust_no`,create_time,update_time) VALUES (?, ?,?,?)"; int i = dalClient.update(sql, param);
System.out.println("TransBusiness>>>insertData" + i);
resultMap.put("插⼊的记录数", i);
}
public void upateData(String[] param) {
Map resultMap = new HashMap<>();
String sql = "update test_order set order_no =?,update_time=? ? where id= ?";
int i = dalClient.update(sql, param);
System.out.println("TransBusiness>>>upateData" + i);
resultMap.put("修改的记录数", i);
}
}
2.1⾃⾝调⽤事务失效解决⽅法1—在⽗⽅法中添加事务
通过doSomeThing()⽅法中添加事务性,可以解决1中事务⾃⾝调⽤失效的问题。
⽰列验证:
OrderServiceImpl.insertAndUpdateOrderInfo⽅法中当步骤1执⾏完成后,数据库中并不会存在该订单记录。当执⾏步骤2时发⽣了异常,整个事务发⽣了回滚。说明才⽅法解决了1⾃⾝调⽤事务失效的问题。
说明:此处的@Transactional等同于 @Transactional(propagation = Propagation.REQUIRED) 表⽰⽀持当前事务,如果没有事务就新建⼀个事务,这是常见的选择,也是spring默认的事务传播
@Override
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void doSomeThing1() {
insertAndUpdateOrderInfo();
}
public void insertAndUpdateOrderInfo(){
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String updateTime = dateFormat.format(date);
//步骤1:插⼊订单记录信息
String[] insertParam = {"8888888888", "977723233", updateTime, updateTime};
transBusiness.insertData(insertParam);
/
/步骤2:修改订单记录信息
String[] updateParam = {"1111111112", updateTime, "1"};
transBusiness.upateData(updateParam);
}
2.2⾃⾝调⽤事务失效解决⽅法2—将事务⽅法拆分到另外⼀个类中
@Service
public class TransBusiness {
@Autowired
JdbcTemplate dalClient;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void insertAndUpdateOrderInfo(){
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
下载mysql为什么下载不了String updateTime = dateFormat.format(date);
//步骤1:插⼊订单记录信息
String[] insertParam = {"8888888888", "977723233", updateTime, updateTime};
insertData(insertParam);
//步骤2:修改订单记录信息
String[] updateParam = {"1111111112", updateTime, "1"};
upateData(updateParam);
}
}
3.SQL规范于1992年提出了数据库事务隔离级别,以此⽤来保证并发操作数据的正确性及⼀致性。Mysql的事务隔离级别由低往⾼可分为以下⼏类:
1) READ UNCOMMITTED(读取未提交的数据)
这是最不安全的⼀种级别,查询语句在⽆锁的情况下运⾏,就读取到别的未提交的数据,造成脏读,如果未提交的那个事务数据全部回滚了,⽽之前读取了这个事务的数据即是脏数据,这种数据不⼀致性读造成的危害是可想⽽知的。
2) READ COMMITTED(读取已提交的数据)
⼀个事务只能读取数据库中已经提交过的数据,解决了脏读问题,但不能重复读,即⼀个事务内的两次查询返回的数据是不⼀样的。如第⼀次查询⾦额是100,第⼆次去查询可能就是50了,这就是不可重复读取。
3) REPEATABLE READ(可重复读取数据,这也是Mysql默认的隔离级别)
⼀个事务内的两次⽆锁查询返回的数据都是⼀样的,但别的事务的新增数据也能读取到。⽐如另⼀个事务插⼊了⼀条数据并提交,这个事务第⼆次去读取的时候发现多了⼀条之前查询数据列表⾥⾯不存在的数据,这时候就是传说的中幻读了。这个级别避免了不可重复读取,但不能避免幻读的问题。
4) SERIALIZABLE(可串⾏化读)
这是效率最低最耗费资源的⼀个事务级别,和可重复读类似,但在⾃动提交模式关闭情况下可串⾏化读会给每个查询加上共享锁和排他锁,意味着所有的读操作之间不阻塞,但读操作会阻塞别的事务的写操作,写操作也阻塞读操作。
4.spring事务管理其实是对数据库事务进⾏了封装⽽已,并提了5种事务隔离级别和7种事务传播机制。
4.1声明式事务(declarative transaction management)是Spring提供的对程序事务管理的⽅式之⼀。Spring使⽤AOP来完成声明式的事务管理,因⽽声明式事务是以⽅法为单位,Spring的事务属性⾃然就在于描述事务应⽤⾄⽅法上的策略,在Spring中事务属性有以下参数:
readOnly属性的详细理解:
1)readonly并不是所有数据库都⽀持的,不同的数据库下会有不同的结果。
2)设置了readonly后,connection都会被赋予readonly,效果取决于数据库的实现。
a. 在oracle下测试,发现不⽀持readOnly,也就是不论Connection⾥的readOnly属性是true还是false均不影响SQL的增删改查;
b. 在mysql下测试,发现⽀持readOnly,设置为true时,只能查询,若增删改会发⽣如下异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
sql.ateSQLException(SQLError.java:910)
sql.ute(PreparedStatement.java:792)
3)在ORM中,设置了readonly会赋予⼀些额外的优化,例如在Hibernate中,会被禁⽌flush等。
4.2 spring 的 5种事务隔离级别
1) ISOLATION_DEFAULT (使⽤后端数据库默认的隔离级别)
以下四个与JDBC的隔离级别相对应:
2) ISOLATION_READ_UNCOMMITTED (允许读取尚未提交的更改,可能导致脏读、幻影读或不可重复读)
3) ISOLATION_READ_COMMITTED (允许从已经提交的并发事务读取,可防⽌脏读,但幻影读和不可重复读仍可能会发⽣)
4) ISOLATION_REPEATABLE_READ (对相同字段的多次读取的结果是⼀致的,除⾮数据被当前事务本⾝改变。可防⽌脏读和不可重复读,但幻影读仍可能发⽣)
5) ISOLATION_SERIALIZABLE (完全服从ACID的隔离级别,确保不发⽣脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的)
4.3 spring的7种事务传播机制:
1) REQUIRED(需要事务): 业务⽅法需要在⼀个事务中运⾏,如果⽅法运⾏时,已处在⼀个事务中,那么就加⼊该事务,否则⾃⼰创建⼀个新的事务.这是spring默认的传播⾏为;
2) NOT_SUPPORTED(不⽀持事务): 声明⽅法需要事务,如果⽅法没有关联到⼀个事务,容器不会为
它开启事务.如果⽅法在⼀个事务中被调⽤,该事务会被挂起,在⽅法调⽤结束后,原先的事务便会恢复执⾏;
3) REQUIREDS_NEW(需要新事务):业务⽅法总是会为⾃⼰发起⼀个新的事务,如果⽅法已运⾏在⼀个事务中,则原有事务被挂起,新的事务被创建,直到⽅法结束,新事务才结束,原先的事务才会恢复执⾏;
备注:新建的事务如果没有进⾏异常捕获,发⽣异常那么原事务⽅法也会发⽣回滚。(该结论经过⾃测验证)
4) MANDATORY(强制性事务):只能在⼀个已存在事务中执⾏。业务⽅法不能发起⾃⼰的事务,如果业务⽅法在没有事务的环境下调⽤,就抛异常
5) NEVER(不能存在事务):声明⽅法绝对不能在事务范围内执⾏,如果⽅法在某个事务范围内执⾏,容器就抛异常.只有没关联到事务,才正常执⾏.
6) SUPPORTS(⽀持事务):如果业务⽅法在某个事务范围内被调⽤,则⽅法成为该事务的⼀部分,如果业务⽅法在事务范围外被调⽤,则⽅法在没有事务的环境下执⾏.
7) NESTED(嵌套事务):如果⼀个活动的事务存在,则运⾏在⼀个嵌套的事务中.如果没有活动的事务,
则按REQUIRED属性执⾏.它使⽤了⼀个单独的事务,这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效.
思考:Nested和RequiresNew的区别:
a. RequiresNew每次都创建新的独⽴的物理事务,⽽Nested只有⼀个物理事务;
b. Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,⽽ RequiresNew由于都是全新的事务,所以之间是⽆关联的;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论