浏览代码

更新日志.

nieqiurong 1 年之前
父节点
当前提交
a8f2f47fe7

+ 9 - 5
changelog-temp.md

@@ -5,8 +5,12 @@
 * fix: 修复@OrderBy搭配@TableId排序字段错误(不兼容改动,com.baomidou.mybatisplus.core.metadata.TableInfo.orderByFields调整了类型).
 * fix: 修复Service中根据主键逻辑删除时类型不匹配导致的错误.
 * fix: 修复分页插件Count与自定义ResultHandler冲突.
-* feat: 新增自增主键字段是否允许插入控制,可使用方法注入覆盖Insert(boolean ignoreAutoIncrementColumn)或Insert(String name, boolean ignoreAutoIncrementColumn)控制自增主键是否支持自定义写入行为.
+* fix: 修复字段填充处理器可能会出现重入问题
+* feat: 新增自增主键字段是否允许插入控制,可使用方法注入覆盖Insert(boolean ignoreAutoIncrementColumn)或Insert(String name, boolean ignoreAutoIncrementColumn)控制自增主键是否支持写入行为.
 * feat: ActiveRecord模式下deleteById(逻辑删除)方法支持自动填充功能.
+* feat: 内置泛型提取,支持非Spring体系项目使用.
+* feat: BaseMapper新增update(wrapper)更新方法.
+* feat: BaseMapper新增流式查询方法对大数据查询支持.
 * feat: 代码生成器元数据信息公开tableName与columnName字段访问.
 * feat: 新增mybatis-plus-spring-boot3-starter与mybatis-plus-spring-boot3-starter-test支持SpringBoot3.
 * feat: 支持插件缺省注入,当无MybatisPlusInterceptor注入时,支持com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor自动注入.
@@ -15,9 +19,9 @@
 * feat: 升级kotlin-gradle-plugin至1.9.20-Beta.
 * feat: 升级SpringBoot2.x版本至2.7.15.
 * feat: 升级lombok至1.18.30.
-* feat: BaseMapper新增流式查询方法对大数据查询支持.
 * opt: mybatis-plus-extension中mybatis-spring依赖修改为可选依赖(不兼容改动,如果项目在非spring或非springBoot下使用到了请手动添加依赖).
 * opt: spring-boot-starter减少无用的配置提示(不兼容改动,调整了com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties.configuration类型).
-*
-
-
+* opt: 字段填充处理器提取去除固定参数提取,支持更宽松的mapper方法参数提取填充处理,
+* opt: 去除com.baomidou.mybatisplus.core.toolkit.ReflectionKit.setAccessible方法调用,防止高版本Jdk移除
+* opt: 调整selectOne方法(配合流式处理,最多提取两行数据,日志不再打印总记录数).
+* opt: 优化selectObjs方法返回值,减少类型强制转换.

+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-rc-1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-rc-3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 10 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/batch/MybatisBatch.java

@@ -35,6 +35,7 @@ import java.util.function.Function;
 /**
  * <li>事务需要自行控制</li>
  * <li>批次数据尽量自行切割处理</li>
+ * <li>关于事务必须执行到flushStatements才具有意义{@link org.apache.ibatis.executor.BatchExecutor#doFlushStatements(boolean)}</>
  * <li>返回值为批处理结果,如果对返回值比较关心的可接收判断处理</li>
  * <li>saveOrUpdate尽量少用把,保持批处理为简单的插入或更新</li>
  * <li>关于saveOrUpdate中的sqlSession,如果执行了select操作的话,BatchExecutor都会触发一次flushStatements,为了保证结果集,故使用包装了部分sqlSession查询操作</li>
@@ -141,6 +142,10 @@ public class MybatisBatch<T> {
 
     /**
      * 批量保存或更新
+     * 这里需要注意一下,如果在insertPredicate里判断调用其他sqlSession(类似mapper.xxx)时,要注意一级缓存问题或数据感知问题(因为当前会话数据还未提交)
+     * 举个例子(事务开启状态下):
+     * 如果当前批次里面执行两个主键相同的数据,当调用mapper.selectById时,如果数据库未有这条记录,在同个sqlSession下,由于一级缓存的问题,下次再查就还是null,导致插入主键冲突,
+     * 但使用 {@link BatchSqlSession}时,由于每次select操作都会触发一次flushStatements,就会执行更新操作
      *
      * @param insertMethod    插入方法
      * @param insertPredicate 插入条件 (当条件满足时执行插入方法,否则执行更新方法)
@@ -153,6 +158,10 @@ public class MybatisBatch<T> {
 
     /**
      * 批量保存或更新
+     * 这里需要注意一下,如果在insertPredicate里判断调用其他sqlSession(类似mapper.xxx)时,要注意一级缓存问题或数据感知问题(因为当前会话数据还未提交)
+     * 举个例子(事务开启状态下):
+     * 如果当前批次里面执行两个主键相同的数据,当调用mapper.selectById时,如果数据库未有这条记录,在同个sqlSession下,由于一级缓存的问题,下次再查就还是null,导致插入主键冲突,
+     * 但使用 {@link BatchSqlSession}时,由于每次select操作都会触发一次flushStatements,就会执行更新操作
      *
      * @param autoCommit      是否自动提交(这里生效的前提依赖于事务管理器 {@link org.apache.ibatis.transaction.Transaction})
      * @param insertMethod    插入方法
@@ -173,7 +182,7 @@ public class MybatisBatch<T> {
             }
             resultList.addAll(sqlSession.flushStatements());
             resultList.addAll(session.getResultBatchList());
-            if(!autoCommit) {
+            if (!autoCommit) {
                 sqlSession.commit();
             }
             return resultList;

+ 8 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/MybatisBatchUtils.java

@@ -116,6 +116,10 @@ public class MybatisBatchUtils {
 
     /**
      * 批量保存或更新
+     * 这里需要注意一下,如果在insertPredicate里判断调用其他sqlSession(类似mapper.xxx)时,要注意一级缓存问题或数据感知问题(因为当前会话数据还未提交)
+     * 举个例子(事务开启状态下):
+     * 如果当前批次里面执行两个主键相同的数据,当调用mapper.selectById时,如果数据库未有这条记录,在同个sqlSession下,由于一级缓存的问题,下次再查就还是null,导致插入主键冲突,
+     * 但使用 {@link BatchSqlSession}时,由于每次select操作都会触发一次flushStatements,就会执行更新操作
      *
      * @param sqlSessionFactory sqlSessionFactory {@link SqlSessionFactory}
      * @param dataList          数据集列表
@@ -131,6 +135,10 @@ public class MybatisBatchUtils {
 
     /**
      * 批量保存或更新
+     * 这里需要注意一下,如果在insertPredicate里判断调用其他sqlSession(类似mapper.xxx)时,要注意一级缓存问题或数据感知问题(因为当前会话数据还未提交)
+     * 举个例子(事务开启状态下):
+     * 如果当前批次里面执行两个主键相同的数据,当调用mapper.selectById时,如果数据库未有这条记录,在同个sqlSession下,由于一级缓存的问题,下次再查就还是null,导致插入主键冲突,
+     * 但使用 {@link BatchSqlSession}时,由于每次select操作都会触发一次flushStatements,就会执行更新操作
      *
      * @param sqlSessionFactory sqlSessionFactory {@link SqlSessionFactory}
      * @param dataList          数据集列表

+ 2 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/ReflectionKit.java

@@ -69,7 +69,9 @@ public final class ReflectionKit {
      * @param entity    实体
      * @param fieldName 字段名称
      * @return 属性值
+     * @deprecated 3.5.4
      */
+    @Deprecated
     public static Object getFieldValue(Object entity, String fieldName) {
         Class<?> cls = entity.getClass();
         Map<String, Field> fieldMaps = getFieldMap(cls);

+ 3 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/SetAccessibleAction.java

@@ -20,7 +20,10 @@ import java.security.PrivilegedAction;
 
 /**
  * Create by hcl at 2021/5/14
+ *
+ * @deprecated 3.5.4
  */
+@Deprecated
 public class SetAccessibleAction<T extends AccessibleObject> implements PrivilegedAction<T> {
     private final T obj;
 

+ 88 - 44
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/MybatisTest.java

@@ -22,6 +22,7 @@ import com.baomidou.mybatisplus.core.toolkit.MybatisBatchUtils;
 import com.baomidou.mybatisplus.test.h2.entity.H2User;
 import com.baomidou.mybatisplus.test.h2.enums.AgeEnum;
 import com.baomidou.mybatisplus.test.h2.mapper.H2UserMapper;
+import org.apache.ibatis.exceptions.PersistenceException;
 import org.apache.ibatis.io.Resources;
 import org.apache.ibatis.jdbc.ScriptRunner;
 import org.apache.ibatis.session.Configuration;
@@ -39,7 +40,6 @@ import java.io.IOException;
 import java.io.Reader;
 import java.sql.Connection;
 import java.sql.SQLException;
-import java.util.Arrays;
 import java.util.List;
 
 
@@ -73,56 +73,100 @@ class MybatisTest {
 
 
     @Test
-    void test(){
-        SqlSession sqlSession = sqlSessionFactory.openSession(true);
-        H2UserMapper mapper = sqlSession.getMapper(H2UserMapper.class);
-        Assertions.assertEquals(mapper.myInsertWithNameVersion("test", 2), 1);
-        Assertions.assertEquals(mapper.insert(new H2User("test")), 1);
-        Assertions.assertEquals(mapper.selectCount(new QueryWrapper<H2User>().lambda().eq(H2User::getName, "test")), 2);
-        Assertions.assertEquals(mapper.delete(new QueryWrapper<H2User>().lambda().eq(H2User::getName, "test")), 2);
-        H2User h2User = new H2User(66L, "66666", AgeEnum.THREE, 666);
-        Assertions.assertEquals(mapper.insert(h2User), 1);
-        h2User.setName("7777777777");
-        H2User user = mapper.selectById(66L);
-        Assertions.assertNotNull(user);
-        Assertions.assertEquals(user.getAge(), AgeEnum.THREE);
-        Assertions.assertNotNull(user.getTestType());
-        Assertions.assertEquals(mapper.updateById(new H2User(66L, "777777")), 1);
-        Assertions.assertEquals(mapper.deleteById(66L), 1);
-        Assertions.assertNull(mapper.selectById(66L));
-        sqlSession.close();
-
-        System.out.println("------------------批量测试开始-----------------------------");
-        sqlSession = sqlSessionFactory.openSession();
-        mapper = sqlSession.getMapper(H2UserMapper.class);
-        List<H2User> userList = Arrays.asList(new H2User(1000L, "测试"), new H2User(1001L, "测试"));
-        try {
-            MybatisBatchUtils.execute(sqlSessionFactory, userList, H2UserMapper.class.getName() + ".insert", parameter -> {
-                if (parameter.getTestId() == 1001L) {
-                    throw new RuntimeException("报错了");
-                }
-                return parameter;
-            });
-        } catch (Exception exception) {
+    void test() {
+        try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
+            H2UserMapper mapper = sqlSession.getMapper(H2UserMapper.class);
+            Assertions.assertEquals(mapper.myInsertWithNameVersion("test", 2), 1);
+            Assertions.assertEquals(mapper.insert(new H2User("test")), 1);
+            Assertions.assertEquals(mapper.selectCount(new QueryWrapper<H2User>().lambda().eq(H2User::getName, "test")), 2);
+            Assertions.assertEquals(mapper.delete(new QueryWrapper<H2User>().lambda().eq(H2User::getName, "test")), 2);
+            H2User h2User = new H2User(66L, "66666", AgeEnum.THREE, 666);
+            Assertions.assertEquals(mapper.insert(h2User), 1);
+            h2User.setName("7777777777");
+            H2User user = mapper.selectById(66L);
+            Assertions.assertNotNull(user);
+            Assertions.assertEquals(user.getAge(), AgeEnum.THREE);
+            Assertions.assertNotNull(user.getTestType());
+            Assertions.assertEquals(mapper.updateById(new H2User(66L, "777777")), 1);
+            Assertions.assertEquals(mapper.deleteById(66L), 1);
+            Assertions.assertNull(mapper.selectById(66L));
+        }
+    }
+
+    @Test
+    void testBatchAutoCommitFalse() {
+        var userList = List.of(new H2User(2000L, "测试"), new H2User(2001L, "测试"));
+        MybatisBatchUtils.execute(sqlSessionFactory, userList, H2UserMapper.class.getName() + ".insert");
+        try (var sqlSession = sqlSessionFactory.openSession()) {
+            var mapper = sqlSession.getMapper(H2UserMapper.class);
             for (H2User u : userList) {
-                Assertions.assertNull(mapper.selectById(u.getTestId()));
+                Assertions.assertNotNull(mapper.selectById(u.getTestId()));
             }
         }
+    }
 
-        userList = Arrays.asList(new H2User(2000L, "测试"), new H2User(2001L, "测试"));
-        MybatisBatchUtils.execute(sqlSessionFactory, userList, H2UserMapper.class.getName() + ".insert");
-        for (H2User u : userList) {
-            Assertions.assertNotNull(mapper.selectById(u.getTestId()));
+    @Test
+    void testBatchAutoCommitFalseOnException1() {
+        List<H2User> userList = List.of(new H2User(1000L, "测试"), new H2User(1000L, "测试"));
+        Assertions.assertThrowsExactly(PersistenceException.class, () -> MybatisBatchUtils.execute(sqlSessionFactory, userList, H2UserMapper.class.getName() + ".insert"));
+        try (var sqlSession = sqlSessionFactory.openSession()) {
+            var mapper = sqlSession.getMapper(H2UserMapper.class);
+            Assertions.assertNull(mapper.selectById(1000L));
         }
-        // 自动提交事务
-        userList = Arrays.asList(new H2User(3000L,"测试"),new H2User(3001L,"测试"));
-        sqlSession = sqlSessionFactory.openSession();
-        mapper = sqlSession.getMapper(H2UserMapper.class);
+    }
+
+    @Test
+    void testBatchAutoCommitFalseOnException2() {
+        List<H2User> userList = List.of(new H2User(1010L, "测试"), new H2User(1011L, "测试"));
+        Assertions.assertThrowsExactly(RuntimeException.class, () -> MybatisBatchUtils.execute(sqlSessionFactory, userList, H2UserMapper.class.getName() + ".insert", parameter -> {
+            if (parameter.getTestId() == 1011L) {
+                throw new RuntimeException("出异常了");
+            }
+            return parameter;
+        }));
+        try (var sqlSession = sqlSessionFactory.openSession()) {
+            var mapper = sqlSession.getMapper(H2UserMapper.class);
+            Assertions.assertNull(mapper.selectById(1010L));
+        }
+    }
+
+
+    @Test
+    void testBatchAutoCommitTrue() {
+        var userList = List.of(new H2User(3000L, "测试"), new H2User(3001L, "测试"));
         MybatisBatchUtils.execute(sqlSessionFactory, userList, true, H2UserMapper.class.getName() + ".insert");
-        for (H2User u : userList) {
-            Assertions.assertNotNull(mapper.selectById(u.getTestId()));
+        try (var sqlSession = sqlSessionFactory.openSession()) {
+            var mapper = sqlSession.getMapper(H2UserMapper.class);
+            for (H2User u : userList) {
+                Assertions.assertNotNull(mapper.selectById(u.getTestId()));
+            }
+        }
+    }
+
+    @Test
+    void testBatchAutoCommitTrueOnException1() {
+        var userList = List.of(new H2User(4000L, "测试"), new H2User(4000L, "测试"));
+        Assertions.assertThrowsExactly(PersistenceException.class, () -> MybatisBatchUtils.execute(sqlSessionFactory, userList, true, H2UserMapper.class.getName() + ".insert"));
+        try (var sqlSession = sqlSessionFactory.openSession()) {
+            var mapper = sqlSession.getMapper(H2UserMapper.class);
+            Assertions.assertNotNull(mapper.selectById(4000L));
+        }
+    }
+
+    @Test
+    void testBatchAutoCommitTrueOnException2() {
+        var userList = List.of(new H2User(4010L, "测试"), new H2User(4011L, "测试"));
+        Assertions.assertThrowsExactly(RuntimeException.class, () -> MybatisBatchUtils.execute(sqlSessionFactory, userList, true, H2UserMapper.class.getName() + ".insert", parameter -> {
+            if (parameter.getTestId() == 4011L) {
+                throw new RuntimeException("出异常了");
+            }
+            return parameter;
+        }));
+        try (var sqlSession = sqlSessionFactory.openSession()) {
+            var mapper = sqlSession.getMapper(H2UserMapper.class);
+            Assertions.assertNull(mapper.selectById(4010L));
+            Assertions.assertNull(mapper.selectById(4011L));
         }
-        System.out.println("------------------批量测试结束-----------------------------");
     }
 
 }

+ 34 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserMapperTest.java

@@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.core.toolkit.MybatisBatchUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -28,6 +29,7 @@ import com.baomidou.mybatisplus.test.h2.entity.H2User;
 import com.baomidou.mybatisplus.test.h2.entity.SuperEntity;
 import com.baomidou.mybatisplus.test.h2.enums.AgeEnum;
 import com.baomidou.mybatisplus.test.h2.mapper.H2UserMapper;
+import org.apache.ibatis.exceptions.PersistenceException;
 import org.apache.ibatis.executor.BatchResult;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.junit.jupiter.api.*;
@@ -255,6 +257,38 @@ class H2UserMapperTest extends BaseTest {
         }
     }
 
+    @Test
+    void testSaveOrUpdateBatch3() {
+        var id = IdWorker.getId();
+        var h2UserList = List.of(new H2User(id, "testSaveOrUpdateBatch3"), new H2User(id, "testSaveOrUpdateBatch3-1"));
+        var mapperMethod = new MybatisBatch.Method<H2User>(H2UserMapper.class);
+        // 由于没有共享一个sqlSession,第二条记录selectById的时候第一个sqlSession的数据还没提交,会执行插入导致主键冲突.
+        Assertions.assertThrowsExactly(PersistenceException.class, () -> {
+            MybatisBatchUtils.saveOrUpdate(sqlSessionFactory, h2UserList,
+                mapperMethod.insert(),
+                ((sqlSession, h2User) -> userMapper.selectById(h2User.getTestId()) == null),
+                mapperMethod.updateById());
+        });
+
+    }
+
+    @Test
+    void testSaveOrUpdateBatch4() {
+        var id = IdWorker.getId();
+        var h2UserList = List.of(new H2User(id, "testSaveOrUpdateBatch4"), new H2User(id, "testSaveOrUpdateBatch4-1"));
+        var mapperMethod = new MybatisBatch.Method<H2User>(H2UserMapper.class);
+        // 共享一个sqlSession,每次selectById都会刷新一下,第二条记录为update.
+        var batchResults = MybatisBatchUtils.saveOrUpdate(sqlSessionFactory, h2UserList,
+            mapperMethod.insert(),
+            ((sqlSession, h2User) -> sqlSession.selectList(mapperMethod.get("selectById").getStatementId(), h2User.getTestId()).isEmpty()),
+            mapperMethod.updateById());
+        var updateCounts = batchResults.get(0).getUpdateCounts();
+        for (int updateCount : updateCounts) {
+            Assertions.assertEquals(1, updateCount);
+        }
+        Assertions.assertEquals(userMapper.selectById(id).getName(), "testSaveOrUpdateBatch4-1");
+    }
+
 
     @Test
     @Order(1)

+ 17 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserTest.java

@@ -22,6 +22,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
@@ -36,6 +37,7 @@ import com.baomidou.mybatisplus.test.h2.mapper.H2StudentMapper;
 import com.baomidou.mybatisplus.test.h2.service.IH2UserService;
 import net.sf.jsqlparser.parser.CCJSqlParserUtil;
 import net.sf.jsqlparser.statement.select.Select;
+import org.apache.ibatis.exceptions.PersistenceException;
 import org.apache.ibatis.exceptions.TooManyResultsException;
 import org.apache.ibatis.plugin.Interceptor;
 import org.apache.ibatis.session.Configuration;
@@ -927,4 +929,19 @@ class H2UserTest extends BaseTest {
         Assertions.assertNotNull(userService.getBaseMapper().selectOne(Wrappers.emptyWrapper(), false));
     }
 
+    @Test
+    void testSaveOrUpdateTransactional1() {
+        var id = IdWorker.getId();
+        var userList = List.of(new H2User(id, "test-1"), new H2User(IdWorker.getId(), "test-2"), new H2User(id, "test-3"));
+        Assertions.assertThrowsExactly(PersistenceException.class, () -> userService.testSaveOrUpdateTransactional1(userList));
+    }
+
+    @Test
+    void testSaveOrUpdateTransactional2() {
+        var id = IdWorker.getId();
+        var userList = List.of(new H2User(id, "test-1"), new H2User(IdWorker.getId(), "test-2"), new H2User(id, "test-3"));
+        userService.testSaveOrUpdateTransactional2(userList);
+        Assertions.assertEquals(userService.getById(id).getName(), "test-3");
+    }
+
 }

+ 5 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/service/IH2UserService.java

@@ -60,4 +60,9 @@ public interface IH2UserService extends IService<H2User> {
     void testSaveBatchNoTransactional2();
 
     List<H2User> testCustomSqlSegment(Wrapper wrapper);
+
+    void testSaveOrUpdateTransactional1(List<H2User> users);
+
+    void testSaveOrUpdateTransactional2(List<H2User> users);
+
 }

+ 17 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/service/impl/H2UserServiceImpl.java

@@ -15,10 +15,12 @@
  */
 package com.baomidou.mybatisplus.test.h2.service.impl;
 
+import com.baomidou.mybatisplus.core.batch.MybatisBatch;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.core.toolkit.MybatisBatchUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.test.h2.entity.H2User;
@@ -140,4 +142,19 @@ public class H2UserServiceImpl extends ServiceImpl<H2UserMapper, H2User> impleme
     public List<H2User> testCustomSqlSegment(Wrapper wrapper) {
         return baseMapper.selectTestCustomSqlSegment(wrapper);
     }
+
+    @Override
+    @Transactional(rollbackFor = RuntimeException.class)
+    public void testSaveOrUpdateTransactional1(List<H2User> users) {
+        var method = new MybatisBatch.Method<H2User>(H2UserMapper.class);
+        MybatisBatchUtils.saveOrUpdate(sqlSessionFactory, users, method.insert(), (sqlSession, user) -> this.getById(user.getTestId()) == null, method.updateById());
+    }
+
+    @Override
+    @Transactional(rollbackFor = RuntimeException.class)
+    public void testSaveOrUpdateTransactional2(List<H2User> users) {
+        var method = new MybatisBatch.Method<H2User>(H2UserMapper.class);
+        MybatisBatchUtils.saveOrUpdate(sqlSessionFactory, users, method.insert(), (sqlSession, user) -> sqlSession.selectList(method.get("selectById").getStatementId(), user.getTestId()).isEmpty(), method.updateById());
+    }
+
 }

+ 1 - 1
settings.gradle

@@ -8,7 +8,7 @@ buildscript {
 
     dependencies {
         //noinspection DifferentKotlinGradleVersion
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20-Beta"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20-Beta2"
         classpath "gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1"
         classpath "io.freefair.gradle:lombok-plugin:6.6.3"
     }