多线程事务的前提:同一个连接,同一个事务
之前做过 JPA的多线程事务 ,现在做MyBatis的多线程事务。
场景:多线程插入数据,其中一个插入操作出现异常,所有操作都要回滚
一般事务使用@Transactional
来实现,但是多线程,使用@Transactional
就无效了,还是跟之前的JPA的多线程事务 一样的原因,主要他们不是同一个连接,不是同一个事务,所以一个线程出现异常,回滚不会影响到其它线程
使用@Transactional,但是多线程事务失效的代码
- mapper,插入代码
public interface ThreadMapper {
@Insert("insert into user(id,name,age) values(#{id},#{name},#{age})")
void insertUser(User user);
}
- service代码
@Service
public class ThreadService {
@Autowired
private ThreadMapper threadMapper;
//正常的线程
@Transactional
public void testTransactional(){
User user = new User();
user.setId("888");
user.setName("zhangsan");
user.setAge("888");
threadMapper.insertUser(user);
}
//故意模拟出现异常的操作线程
@Transactional
public void testTransactional1(){
User user = new User();
user.setId("666");
user.setName("lisi");
user.setAge("666");
threadMapper.insertUser(user);
System.out.println(1/0);
}
}
- 测试
import com.wzw.thread.ThreadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
@RequestMapping("/thread")
public class ThreadController {
@Autowired
private ThreadService threadService;
@RequestMapping("/test1")
@Transactional
public void threadTrans() throws InterruptedException {
//threadService.testTransactional();
ExecutorService service= Executors.newFixedThreadPool(10);
//保证子线程都执行完
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread thread=new Thread(()->{
try {
threadService.testTransactional();
} catch (Exception e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
});
Thread thread1=new Thread(()->{
try {
threadService.testTransactional1();
} catch (Exception e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
});
service.submit(thread);
service.submit(thread1);
countDownLatch.await();
System.out.println("主线程执行完成");
}
}
多线程,要保持事务一致,只要一个出错,所有线程都要回滚。
看结果,一个线程是正常插入zhangsan,一个线程是异常插入lisi,
可以看到,多线程是事务并没有生效,一个线程报错,另一个却正常插入了,这不是我们想要的结果
使用sqlSession手动提交事务
只改controller,先看正常的效果
- 无异常的情况
@RequestMapping("/test2")
public void test2() throws Exception {
SqlSession sqlSession = threadService.getSqlSession();
Connection connection = sqlSession.getConnection();
try {
//手动提交事务
connection.setAutoCommit(false);
//获取mapper
ThreadMapper mapper = sqlSession.getMapper(ThreadMapper.class);
ExecutorService service= Executors.newFixedThreadPool(10);
Future<Integer> future = service.submit(()->{
User user = new User();
user.setId("888");
user.setName("zhangsan");
user.setAge("888");
mapper.insertUser(user);
return 1;
});
Future<Integer> future1 = service.submit(()->{
User user = new User();
user.setId("666");
user.setName("lisi");
user.setAge("666");
mapper.insertUser(user);
//System.out.println(1/0);
return 1;
});
System.out.println(future.get());
System.out.println(future1.get());
connection.commit();
System.out.println("线程执行结束");
} catch (Exception e) {
//发生异常,手动回滚
connection.rollback();
System.err.println("发生异常");
}
}
两个线程都正常执行完成
正常插入
- 其中一个线程发生异常,所有线程回滚
只改这一点代码,将System.out.println(1/0);
打开,故意报错
Future<Integer> future1 = service.submit(()->{
User user = new User();
user.setId("666");
user.setName("lisi");
user.setAge("666");
mapper.insertUser(user);
System.out.println(1/0);
return 1;
});
无异常的线程正常结束,异常的线程抛出异常了
再查看两个线程的操作结果,都没有数据插入,正确回滚,多线程事务实现