概况
spring对多线程无法进行事务控制,是因为多线程底层连接数据库的时候,是使用的线程变量(TheadLocal),线程之间事务隔离,每个线程有自己的连接,事务肯定不是同一个。
思路解析
- spring自动提交事务修改为手动提交事务
 - 子线程执行完了再去通知主线程检查执行结果
 - 主线程去控制子线程是否提交或者回滚
解决办法
原理:使用两个CountDownLatch实现子线程的二阶段提交
注:对CountDownLatch不熟悉的可以看:CountDownLatch解析
步骤: 
- 主线程将任务分发给子线程,然后使用childMonitor.await();阻塞主线程,等待所有子线程处理向数据库中插入的业务,并使用BlockingDeque
存储线程的返回结果。  - 使用childMonitor.countDown()释放子线程锁定,同时使用mainMonitor.await();阻塞子线程,将程序的控制权交还给主线程。
 - 主线程检查子线程执行任务的结果,若有失败结果出现,主线程标记状态告知子线程回滚,然后使用mainMonitor.countDown();将程序控制权再次交给子线程,子线程检测回滚标志,判断是否回滚。
代码实现
思路说到这里,代码实现已经很简单了,代码如下,暂未考虑超时以及撑爆线程池的问题,后续有空再补充。ThreadPoolExecutor.java
线程池工具类主要在之前的线程池工具类的基础上,冲在了一个可以支持多线程情况下事务的execute方法。主要初始化了两个重要的门闩以及结果集回滚等,这里由主线程控制,唤醒(暂且叫唤醒吧,和唤醒效果差不多,实际上是计数器来实现的)子线程或者被子线程唤醒。 ```java import cn.hutool.core.thread.ThreadFactoryBuilder; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.datasource.DataSourceTransactionManager; 
import java.lang.reflect.Constructor; import java.util.Queue; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit;
/**
- 线程池工具类 *
 @author anin */ @Slf4j public class ThreadPoolExecutor
{ private static java.util.concurrent.ThreadPoolExecutor executor;
private ThreadPoolExecutor() {
final int cpuNum = Runtime.getRuntime().availableProcessors();// 线程池中所保存的核心线程数final int corePoolSize = cpuNum + 1;// 池中允许的最大线程数final int maxPoolSize = corePoolSize * 2;// 线程池中的空闲线程所能持续的最长时间final long keepAliveTime = 0L;// 时间单位final TimeUnit unit = TimeUnit.MILLISECONDS;// 线程队列final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(corePoolSize * 64);// 线程工厂final ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("test-").build();executor = new java.util.concurrent.ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, namedThreadFactory, new java.util.concurrent.ThreadPoolExecutor.AbortPolicy());log.info(" >>>>>>>>>>>>>>> 线程池开始初始化 <<<<<<<<<<<<<<< ");log.info(" >>>>>>>>>>>>>>> 核心线程数: {} <<<<<<<<<<<<<<<", corePoolSize);log.info(" >>>>>>>>>>>>>>> 最大线程数: {} <<<<<<<<<<<<<<<", maxPoolSize);log.info(" >>>>>>>>>>>>>>> 线程队列长度: {} <<<<<<<<<<<<<<<", (corePoolSize * 8));log.info(" >>>>>>>>>>>>>>> 线程池结束初始化 <<<<<<<<<<<<<<< ");Runtime.getRuntime().addShutdownHook(new Thread(() -> {log.info("线程池释放成功");executor.shutdownNow();}));
}
private static class ThreadPoolExecutorHolder {
private static final ThreadPoolExecutor<?> THREAD_POOL_EXECUTOR = new ThreadPoolExecutor<>();
}
public static ThreadPoolExecutor<?> getThreadPoolExecutor() {
return ThreadPoolExecutor.ThreadPoolExecutorHolder.THREAD_POOL_EXECUTOR;
}
/**
- 执行线程 *
 @param runnable 任务 */ public void execute(Runnable runnable) { executor.execute(runnable); }
/**
- 多线程任务 *
 @param transactionManager 数据库事务管理 */ @SneakyThrows public void execute(DataSourceTransactionManager transactionManager, TaskData taskData) { //监控子线程的任务执行 CountDownLatch childMonitor = new CountDownLatch(taskData.getTaskCount()); //监控主线程,是否需要回滚 CountDownLatch mainMonitor = new CountDownLatch(1); //存储任务的返回结果,返回true表示不需要回滚,反之,则回滚 BlockingDeque
results = new LinkedBlockingDeque<>(executor.getCorePoolSize()); RollBack rollback = new RollBack(false); Queue
> queue = list2Queue(taskData); while (queue.peek() != null) {
TaskData.TaskDataInfo<T> taskDataInfo = queue.poll();Constructor<?> constructor = taskDataInfo.getClazz().getConstructor(CountDownLatch.class, CountDownLatch.class, BlockingDeque.class, RollBack.class, DataSourceTransactionManager.class, TaskData.TaskDataInfo.class);AbstractThreadTask<T> task = (AbstractThreadTask<T>) constructor.newInstance(childMonitor, mainMonitor, results, rollback, transactionManager, taskDataInfo);// TODO 这里需要处理任务提交数量 防止线程池被撑爆executor.execute(task);
}
// 阻塞主线程,等待所有子线程处理向数据库中插入的业务。 // TODO 需要考虑超时 childMonitor.await(); log.info(“ >>>>>>>>>>>>>>> 主线程开始执行任务”);
// 根据返回结果来确定是否回滚 for (int i = 0; i < taskData.getTaskCount(); i++) {
boolean result = results.take();if (!result) {log.info(" >>>>>>>>>>>>>>> 主线程检测到有任务执行失败,通知子线程进行回滚");//有线程执行异常,需要回滚子线程rollback.setNeedRollBack(true);break;}
} // 唤醒子线程,子线程检测回滚标志,判断是否回滚。 mainMonitor.countDown(); }
private Queue
> list2Queue(TaskData taskData) { Queue > queue = new LinkedBlockingQueue<>(taskData.getTaskCount()); queue.addAll(taskData.getData()); return queue; } /**
- 提交任务 *
 - @param runnable 任务
 @return Future */ public Future
submit(Runnable runnable, T result) { return executor.submit(runnable, result); } /**
- 提交任务 *
 - @param callable 任务
 - @return Future
*/
public Future
submit(Callable callable) { return executor.submit(callable); }  
}
<a name="Lx5FT"></a>#### AbstractThreadTask.java子线程任务执行类是一个抽象方法,主要使用了模板方法模式,定义了子线程执行任务的流程以及在执行完毕子线程任务之后阻塞自己,并且唤醒主线程去校验自己的结果。```javaimport lombok.extern.slf4j.Slf4j;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.TransactionDefinition;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.support.DefaultTransactionDefinition;import java.util.concurrent.BlockingDeque;import java.util.concurrent.CountDownLatch;/**** 子线程任务执行类** @author anin*/@Slf4jpublic abstract class AbstractThreadTask<T> implements Runnable {/*** 监控子任务的执行*/private CountDownLatch childMonitor;/*** 监控主线程*/private CountDownLatch mainMonitor;/*** 存储线程的返回结果*/private BlockingDeque<Boolean> resultList;/*** 回滚类*/private RollBack rollback;private TaskData.TaskDataInfo<T> taskDataInfo;protected DataSourceTransactionManager transactionManager;protected TransactionStatus status;public AbstractThreadTask(CountDownLatch childCountDown, CountDownLatch mainCountDown, BlockingDeque<Boolean> result, RollBack rollback, DataSourceTransactionManager transactionManager, TaskData.TaskDataInfo<T> taskDataInfo) {this.childMonitor = childCountDown;this.mainMonitor = mainCountDown;this.resultList = result;this.rollback = rollback;this.transactionManager = transactionManager;this.taskDataInfo = taskDataInfo;initParam();}/*** 事务回滚*/private void rollBack() {log.info(" >>>>>>>>>>>>>>> 子线程:{} 开始回滚事务", Thread.currentThread().getName());transactionManager.rollback(status);}/*** 事务提交*/private void submit() {log.info(" >>>>>>>>>>>>>>> 子线程:{} 开始提交事务", Thread.currentThread().getName());transactionManager.commit(status);}protected TaskData.TaskDataInfo<T> getDataInfo() {return taskDataInfo;}/*** 初始化方法:作用是把线程池工具任务执行类所需的外部资源通过 ThreadTask.class的构造方法中 Map<String,Obejct> params参数进行初始化传递进来*/public abstract void initParam();/*** 执行任务,返回false表示任务执行错误,需要回滚*/public abstract boolean processTask();@Overridepublic void run() {log.info(" >>>>>>>>>>>>>>> 子线程:{} 开始执行任务", Thread.currentThread().getName());DefaultTransactionDefinition def = new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);status = transactionManager.getTransaction(def);Boolean result = processTask();// 向队列中添加处理结果resultList.add(result);// 告诉主线程我执行完了childMonitor.countDown();try {// 等待主线程的判断逻辑执行完,执行下面的是否回滚逻辑mainMonitor.await();} catch (Exception e) {log.error(e.getMessage());}log.info(" >>>>>>>>>>>>>>> 子线程:{} 执行剩下的任务", Thread.currentThread().getName());// 判断是否回滚if (rollback.isNeedRollBack()) {rollBack();} else {submit();}}}
RollBack.java
定义了一个是否需要回滚的标识
import lombok.Data;/*** 回滚标记类* @author anin*/@Datapublic class RollBack {/*** 是否需要回滚*/private boolean needRollBack;public RollBack(boolean needRollBack) {this.needRollBack = needRollBack;}}
TaskData.java
考虑到实际业务场景下,多线程执行的总是不通的业务逻辑,所以封装了这个任务参数。
import lombok.Data;import java.util.List;/**** 任务参数** @author anin*/@Datapublic class TaskData<T> {/*** 实现类:数据*/private List<TaskDataInfo<T>> data;public Integer getTaskCount() {return data.size();}@Datapublic static class TaskDataInfo<T> {/*** 任务名称*/private String name;/*** 任务实现服务*/private TaskService taskService;/*** task class*/private Class<?> clazz;/*** 任务数据*/private T data;}}
TaskService.java
任务执行接口,需要实现该方法。
/**** 任务执行接口** @author anin*/public interface TaskService {/*** 业务执行*/<T> Boolean execute(T param);}
UserServiceImpl.java
一个示例实现方法。
import com.example.threadinsertdemo.executor.TaskData;import com.example.threadinsertdemo.executor.TaskService;import com.example.threadinsertdemo.executor.ThreadPoolExecutor;import com.example.threadinsertdemo.mapper.UserMapper;import com.example.threadinsertdemo.pojo.User;import com.example.threadinsertdemo.pojo.UserData;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;/**** 任务执行接口** @author anin*/@Slf4j@Servicepublic class UserServiceImpl implements TaskService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;public void insert2(UserData userData){TaskData<User> taskData = new TaskData<>();List<TaskData.TaskDataInfo<User>> users = new ArrayList<>();TaskData.TaskDataInfo<User> taskDataInfo1 = new TaskData.TaskDataInfo<>();taskDataInfo1.setName("user1");taskDataInfo1.setTaskService(this);taskDataInfo1.setClazz(UserTask1.class);taskDataInfo1.setData(userData.getUser1());TaskData.TaskDataInfo<User> taskDataInfo2 = new TaskData.TaskDataInfo<>();taskDataInfo2.setName("user2");taskDataInfo2.setTaskService(this);taskDataInfo2.setClazz(UserTask2.class);taskDataInfo2.setData(userData.getUser2());TaskData.TaskDataInfo<User> taskDataInfo3 = new TaskData.TaskDataInfo<>();taskDataInfo3.setName("user3");taskDataInfo3.setTaskService(this);taskDataInfo3.setClazz(UserTask3.class);taskDataInfo3.setData(userData.getUser3());users.add(taskDataInfo1);users.add(taskDataInfo2);users.add(taskDataInfo3);taskData.setData(users);//调用多线程工具方法ThreadPoolExecutor.getThreadPoolExecutor().execute(dataSourceTransactionManager,taskData);}@Overridepublic Boolean execute(Object param) {return userMapper.insert((User) param) == 1;}}
UserTask1.java
UserTask1、UserTask2、UserTask3类一致
import com.example.threadinsertdemo.executor.RollBack;import com.example.threadinsertdemo.executor.TaskData;import com.example.threadinsertdemo.executor.TaskService;import com.example.threadinsertdemo.executor.AbstractThreadTask;import com.example.threadinsertdemo.pojo.User;import lombok.extern.slf4j.Slf4j;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import java.util.concurrent.BlockingDeque;import java.util.concurrent.CountDownLatch;/**** 测试Task类** @author anin*/@Slf4jpublic class UserTask1 extends AbstractThreadTask<User> {/*** 分批处理的数据*/private User data;/*** 可能需要注入的某些服务*/private TaskService taskService;public UserTask1(CountDownLatch childCountDown, CountDownLatch mainCountDown, BlockingDeque<Boolean> result, RollBack rollback, DataSourceTransactionManager transactionManager, TaskData.TaskDataInfo<User> taskDataInfo) {super(childCountDown, mainCountDown, result, rollback, transactionManager, taskDataInfo);}@Overridepublic void initParam() {TaskData.TaskDataInfo<User> dataInfo = getDataInfo();this.data = dataInfo.getData();this.taskService = dataInfo.getTaskService();}/*** 执行任务** @return 返回false表示任务执行错误,需要回滚*/@Overridepublic boolean processTask() {log.info(" >>>>>>>>>>>>>>> 执行task1多线程任务逻辑");try {taskService.execute(data);return true;} catch (Exception e) {return false;}}}
正常提交测试
@RestControllerpublic class WebController {@Autowiredprivate UserServiceImpl userService;@GetMapping("/test2")public String test2(){User user1 = new User();user1.setId(1);user1.setName("wangwu1");User user2 = new User();user2.setId(2);user2.setName("wangwu2");User user3 = new User();user3.setId(3);user3.setName("wangwu3");UserData userData = new UserData();userData.setUser1(user1);userData.setUser2(user2);userData.setUser3(user3);userService.insert2(userData);return "suc";}}
终端输出
2021-06-01 15:15:53.764 INFO 17604 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'2021-06-01 15:15:53.764 INFO 17604 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'2021-06-01 15:15:53.765 INFO 17604 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms2021-06-01 15:15:53.810 INFO 17604 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 线程池开始初始化 <<<<<<<<<<<<<<<2021-06-01 15:15:53.811 INFO 17604 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 核心线程数: 9 <<<<<<<<<<<<<<<2021-06-01 15:15:53.818 INFO 17604 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 最大线程数: 18 <<<<<<<<<<<<<<<2021-06-01 15:15:53.818 INFO 17604 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 线程队列长度: 72 <<<<<<<<<<<<<<<2021-06-01 15:15:53.818 INFO 17604 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 线程池结束初始化 <<<<<<<<<<<<<<<2021-06-01 15:15:53.824 INFO 17604 --- [ test-2] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-2 开始执行任务2021-06-01 15:15:53.824 INFO 17604 --- [ test-1] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-1 开始执行任务2021-06-01 15:15:53.824 INFO 17604 --- [ test-0] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-0 开始执行任务2021-06-01 15:15:53.845 INFO 17604 --- [ test-0] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...2021-06-01 15:15:54.624 INFO 17604 --- [ test-0] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.2021-06-01 15:15:54.635 INFO 17604 --- [ test-0] c.e.threadinsertdemo.service.UserTask1 : >>>>>>>>>>>>>>> 执行task1多线程任务逻辑2021-06-01 15:15:54.667 DEBUG 17604 --- [ test-0] c.e.t.mapper.UserMapper.insert : ==> Preparing: insert into`user` (`name`) values (?)2021-06-01 15:15:54.675 INFO 17604 --- [ test-2] c.e.threadinsertdemo.service.UserTask3 : >>>>>>>>>>>>>>> 执行task3多线程任务逻辑2021-06-01 15:15:54.675 DEBUG 17604 --- [ test-2] c.e.t.mapper.UserMapper.insert : ==> Preparing: insert into`user` (`name`) values (?)2021-06-01 15:15:54.697 INFO 17604 --- [ test-1] c.e.threadinsertdemo.service.UserTask2 : >>>>>>>>>>>>>>> 执行task2多线程任务逻辑2021-06-01 15:15:54.697 DEBUG 17604 --- [ test-1] c.e.t.mapper.UserMapper.insert : ==> Preparing: insert into`user` (`name`) values (?)2021-06-01 15:15:54.697 DEBUG 17604 --- [ test-0] c.e.t.mapper.UserMapper.insert : ==> Parameters: wangwu1(String)2021-06-01 15:15:54.697 DEBUG 17604 --- [ test-2] c.e.t.mapper.UserMapper.insert : ==> Parameters: wangwu3(String)2021-06-01 15:15:54.697 DEBUG 17604 --- [ test-1] c.e.t.mapper.UserMapper.insert : ==> Parameters: wangwu2(String)2021-06-01 15:15:54.699 DEBUG 17604 --- [ test-0] c.e.t.mapper.UserMapper.insert : <== Updates: 12021-06-01 15:15:54.699 DEBUG 17604 --- [ test-1] c.e.t.mapper.UserMapper.insert : <== Updates: 12021-06-01 15:15:54.701 DEBUG 17604 --- [ test-2] c.e.t.mapper.UserMapper.insert : <== Updates: 12021-06-01 15:15:54.701 INFO 17604 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 主线程开始执行任务2021-06-01 15:15:54.701 INFO 17604 --- [ test-1] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-1 执行剩下的任务2021-06-01 15:15:54.701 INFO 17604 --- [ test-2] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-2 执行剩下的任务2021-06-01 15:15:54.701 INFO 17604 --- [ test-0] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-0 执行剩下的任务2021-06-01 15:15:54.701 INFO 17604 --- [ test-1] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-1 开始提交事务2021-06-01 15:15:54.701 INFO 17604 --- [ test-2] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-2 开始提交事务2021-06-01 15:15:54.701 INFO 17604 --- [ test-0] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-0 开始提交事务
查看数据库
<br />成功插入数据。
异常回滚测试
将任意一个task的processTask方法返回改为false,如下
/*** 执行任务** @return 返回false表示任务执行错误,需要回滚*/@Overridepublic boolean processTask() {log.info(" >>>>>>>>>>>>>>> 执行task3多线程任务逻辑");return false;}
终端输出
2021-06-01 15:31:14.410 INFO 18044 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'2021-06-01 15:31:14.410 INFO 18044 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'2021-06-01 15:31:14.411 INFO 18044 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms2021-06-01 15:31:14.431 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 线程池开始初始化 <<<<<<<<<<<<<<<2021-06-01 15:31:14.431 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 核心线程数: 9 <<<<<<<<<<<<<<<2021-06-01 15:31:14.432 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 最大线程数: 18 <<<<<<<<<<<<<<<2021-06-01 15:31:14.432 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 线程队列长度: 72 <<<<<<<<<<<<<<<2021-06-01 15:31:14.432 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 线程池结束初始化 <<<<<<<<<<<<<<<2021-06-01 15:31:14.434 INFO 18044 --- [ test-0] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-0 开始执行任务2021-06-01 15:31:14.434 INFO 18044 --- [ test-1] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-1 开始执行任务2021-06-01 15:31:14.434 INFO 18044 --- [ test-2] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-2 开始执行任务2021-06-01 15:31:14.438 INFO 18044 --- [ test-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...2021-06-01 15:31:14.701 INFO 18044 --- [ test-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.2021-06-01 15:31:14.712 INFO 18044 --- [ test-2] c.e.threadinsertdemo.service.UserTask3 : >>>>>>>>>>>>>>> 执行task3多线程任务逻辑2021-06-01 15:31:14.728 INFO 18044 --- [ test-1] c.e.threadinsertdemo.service.UserTask2 : >>>>>>>>>>>>>>> 执行task2多线程任务逻辑2021-06-01 15:31:14.748 DEBUG 18044 --- [ test-1] c.e.t.mapper.UserMapper.insert : ==> Preparing: insert into`user` (`name`) values (?)2021-06-01 15:31:14.761 INFO 18044 --- [ test-0] c.e.threadinsertdemo.service.UserTask1 : >>>>>>>>>>>>>>> 执行task1多线程任务逻辑2021-06-01 15:31:14.761 DEBUG 18044 --- [ test-0] c.e.t.mapper.UserMapper.insert : ==> Preparing: insert into`user` (`name`) values (?)2021-06-01 15:31:14.772 DEBUG 18044 --- [ test-1] c.e.t.mapper.UserMapper.insert : ==> Parameters: wangwu2(String)2021-06-01 15:31:14.772 DEBUG 18044 --- [ test-0] c.e.t.mapper.UserMapper.insert : ==> Parameters: wangwu1(String)2021-06-01 15:31:14.774 DEBUG 18044 --- [ test-1] c.e.t.mapper.UserMapper.insert : <== Updates: 12021-06-01 15:31:14.775 DEBUG 18044 --- [ test-0] c.e.t.mapper.UserMapper.insert : <== Updates: 12021-06-01 15:31:14.775 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 主线程开始执行任务2021-06-01 15:31:14.775 INFO 18044 --- [nio-8080-exec-1] c.e.t.executor.ThreadPoolExecutor : >>>>>>>>>>>>>>> 主线程检测到有任务执行失败,通知子线程进行回滚2021-06-01 15:31:14.775 INFO 18044 --- [ test-0] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-0 执行剩下的任务2021-06-01 15:31:14.775 INFO 18044 --- [ test-1] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-1 执行剩下的任务2021-06-01 15:31:14.775 INFO 18044 --- [ test-2] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-2 执行剩下的任务2021-06-01 15:31:14.776 INFO 18044 --- [ test-0] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-0 开始回滚事务2021-06-01 15:31:14.776 INFO 18044 --- [ test-1] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-1 开始回滚事务2021-06-01 15:31:14.776 INFO 18044 --- [ test-2] c.e.t.executor.AbstractThreadTask : >>>>>>>>>>>>>>> 子线程:test-2 开始回滚事务
查看数据库
<br />插入数据成功回滚。
