1遇到问题: spring 事务中保存了对象后 启动一个线程获取对象无效
public void postDispatchDoc(DispatchDoc entity) { try { dispatchMapper.save(entity);// 保存对象 new Thread(new SendMsgThread(userIds)).start(); //如果流程已经完成则发送短信 } catch (Exception e) { e.printStackTrace(); } }
public void run() { try { DispatchDoc disPatch = dispatchMapper.queryById(entityId); //查询对象 sendMsg(dispatch); //发送短信 } catch (Exception e) { e.printStackTrace(); } }
写web项目的时候,我们一般在service层中启用事务。在一个事务当中,只有方法执行完毕才会提交结果到数据库。而在service中方法中调用另个一个线程,另外一个线程不在当前事务中,有肯能在事务还没有提交前就已经执行。
mysql默认的事务隔离级别是 repeatable-read 对照下面可以查看出,mysql不可能dirty read & nonRepeatable Read 有可能 phantom Read
那么具体是什么意思呢?
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
看了解释时候还是不懂通俗的说mysql默认就是:事务A操作了数据库的同时,B也操作,若B未提交。A读取不到。B提交后,A才能获取。除非A统计数据时B在此期间插入一条数据。如果考虑这种情况需要将数据库设置成串行化设置方式如下:
set session transaction isolation level serializable;
查看当前数据库的隔离级别:
现在总数弄明白了,mysql的事务原理。当启动一个线程后另一个线程读取不到未提交的数据。
2解决:那么怎么才能在service层中当mapper对象保存完数据后立即发送短信或则邮件功能呢?
*一般边缘的操作,通常会设置成为异步的,以提升性能,比如发送MQ,业务系统负责事务成功后消息发送成功,然后接收系统负责保证通知成功完成。以上的业务即使能获取到业务,也要保证提交成功否则回退后发短信无效。
如何在spring事务提交之后进行异步操作,这些异步操作必须得在该事务成功提交后才执行,回滚则不执行
使用TransactionSynchronizationManager在事务提交之后操作
//该方法就可以实现在事务提交之后进行操作 public void insert(TechBook techBook){ bookMapper.insert(techBook); // send after tx commit but is async TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { System.out.println("send email after transaction commit..."); } } ); ThreadLocalRandom random = ThreadLocalRandom.current(); if(random.nextInt() % 2 ==0){ throw new RuntimeException("test email transaction"); } System.out.println("service end"); }