SpringBoot3(若依框架)集成Mybatis-Plus和单元测试功能,以及问题解决

发布于:2025-07-26 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、Mybatis-Plus集成

  1. 新增依赖到父级pom.xml,原先的mybatis依赖可以不动
    在这里插入图片描述
    需要注意 mybatis-plus与mybatis版本之间的冲突,不要轻易改动依赖,不然分页也容易出现问题
    在这里插入图片描述
分类顶级pom.xml下面,如果没有引入还是出现报错,在common的模块下面再引入一份下面的依赖
 <!-- springboot3 / mybatis-plus 配置 -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.16</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>3.5.10</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-jsqlparser</artifactId>
                <version>3.5.10</version>
            </dependency>
  1. 替换原来的 MyBatis 配置,修改application.yml文件,修改mybatis配置为mybatis-plus
# MyBatis Plus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.ruoyi.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml
  1. 删除或者修改MyBatisConfig.java 配置 ,新增MybatisPlusConfig配置
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {
    public static final ThreadLocal<String> TABLE_NAME = new ThreadLocal<>();

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 1. 动态表名配置 (兼容 MyBatis-Plus 3.5.5)
        DynamicTableNameInnerInterceptor dynamicTableNameInterceptor = new DynamicTableNameInnerInterceptor();

        // 创建表名处理器映射 (3.5.5 版本使用 setTableNameHandler 方法)
        Map<String, TableNameHandler> tableNameHandlerMap = new HashMap<>();

        BaseTableNameEnum.getAllBaseTableName().forEach(table ->
                tableNameHandlerMap.put(table, (sql, oldTable) ->
                        Optional.ofNullable(TABLE_NAME.get()).orElse(oldTable)
                ));

        // 3.5.5 版本设置表名处理器的方式
        dynamicTableNameInterceptor.setTableNameHandler(
                (sql, tableName) -> {
                    TableNameHandler handler = tableNameHandlerMap.get(tableName);
                    return handler != null ? handler.dynamicTableName(sql, tableName) : tableName;
                }
        );

        interceptor.addInnerInterceptor(dynamicTableNameInterceptor);

        // 2. 分页插件配置
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        // 3. 添加自定义的动态创建表拦截器
        interceptor.addInnerInterceptor(new DynamicCreateTableInterceptor());
        // 4. 攻击 SQL 阻断插件
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

        return interceptor;
    }

    /**
     * 分页插件,自动识别数据库类型
     */
    public PaginationInnerInterceptor paginationInnerInterceptor()
    {
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // 设置数据库类型为mysql
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        return paginationInnerInterceptor;
    }

    /**
     * 乐观锁插件
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor()
    {
        return new OptimisticLockerInnerInterceptor();
    }

    /**
     * 如果是对全表的删除或更新操作,就会终止该操作
     */
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor()
    {
        return new BlockAttackInnerInterceptor();
    }
}

可以不用参考我的,因为我新增了拦截分表功能,官网有配置参考,可以参考官网
https://doc.ruoyi.vip/ruoyi/document/cjjc.html#%E9%9B%86%E6%88%90mybatis-plus%E5%AE%9E%E7%8E%B0mybatis%E5%A2%9E%E5%BC%BA

  1. 仅供参考的分表功能 MybatisPlusUtils 和 DynamicCreateTableInterceptor
/**
 * MybatisPlus工具类
 *
 */
public class MybatisPlusUtils {
    /**
     * 获取动态表名
     *
     * @return
     */
    public static String getDynamicTableName() {
        return MybatisPlusConfig.TABLE_NAME.get();
    }

    /**
     * 设置动态表名
     */
    public static void setDynamicTableName(String tableName) {
        MybatisPlusConfig.TABLE_NAME.set(tableName);
    }

    /**
     * 清空当前线程设置的动态表名
     *
     * @return
     * @author wk
     * @date 2022/2/9 10:37
     */
    public static void emptyDynamicTableName() {
        MybatisPlusConfig.TABLE_NAME.remove();
    }

    /**
     * 获取基础表
     *
     * @return
     */
    public static String getBaseTableName(String dynamicTableName) {
        return BaseTableNameEnum.getBaseTableName(dynamicTableName);
    }
}

/**
 * 动态创建表拦截器
 *
 */
@Slf4j
public class DynamicCreateTableInterceptor implements InnerInterceptor {

    /**
     * 动态创建表
     *
     * @param sh
     * @param connection
     * @param transactionTimeout
     */
    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        String dynamicTableName = MybatisPlusUtils.getDynamicTableName();
        if (StringUtils.isBlank(dynamicTableName)) {
            return;
        }
        String baseTableName = MybatisPlusUtils.getBaseTableName(dynamicTableName);
        if (StringUtils.isNotBlank(baseTableName)&&!baseTableName.contains("null")) {
            try {
                String dataBase = connection.getCatalog();
                String sql = "SELECT count(1) FROM information_schema.tables WHERE table_schema=? AND table_name = ?";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setString(1, dataBase);
                preparedStatement.setString(2, dynamicTableName);
                ResultSet resultSet = preparedStatement.executeQuery();
                if (resultSet.next()) {
                    //获取表是否存在
                    int count = resultSet.getInt(1);
                    close(preparedStatement, resultSet);
                    //如果表不存在
                    if (count == 0) {
                        sql = "SHOW CREATE TABLE " + baseTableName;
                        //获取创建表语句
                        preparedStatement = connection.prepareStatement(sql);
                        resultSet = preparedStatement.executeQuery();
                        if (resultSet.next()) {
                            String createTableSql = resultSet.getString(2);
                            close(preparedStatement, resultSet);
                            //创建表
                            sql = createTableSql.replaceFirst(baseTableName, dynamicTableName);
                            preparedStatement = connection.prepareStatement(sql);
                            preparedStatement.executeUpdate();
                            close(preparedStatement, resultSet);
                            log.info("【动态创建表成功】表名:{}", dynamicTableName);
                        } else {
                            close(preparedStatement, resultSet);
                        }
                    }
                } else {
                    close(preparedStatement, resultSet);
                }
            } catch (Exception e) {
                log.info(String.format("【动态创建表失败】表名: %s", dynamicTableName), e);
            }
        }

    }

    /**
     * 关闭资源
     *
     * @param preparedStatement
     * @param resultSet
     */
    private void close(PreparedStatement preparedStatement, ResultSet resultSet) {
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

  1. 使用方式在需要增删改查的地方调用方法即可
 /**
     * 添加任务(使用动态表名)
     */
    @Transactional
    @Override
    public void addTaskWithDynamicTable(实体类 task) {
        task.setCreateTime(DateUtils.getNowDate());
        try {
            // 设置动态表名(按月份分表)
            String monthSuffix = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy"));
            String dynamicTable = "表名_" + monthSuffix;
            MybatisPlusUtils.setDynamicTableName(dynamicTable);

            // 插入数据(会自动使用动态表名)
            int result = 当前实现类的方法.insert(task);
            log.info("【动态表名插入】表名: {}, 结果: {}", dynamicTable, result);
        } finally {
            MybatisPlusUtils.emptyDynamicTableName();
        }
    }

二、集成单元测试

  1. 使用依赖,在ruoyi-admin的pom.xml下添加依赖
 <!-- 单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

不需要指定版本,自动依赖,下面是错误案例,在其他模块引入了单元测试版本不一样,造成混乱

       <!-- 单元测试-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-test</artifactId>-->
<!--            <scope>test</scope>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.junit.jupiter</groupId>-->
<!--            <artifactId>junit-jupiter</artifactId>-->
<!--            <scope>test</scope>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework</groupId>-->
<!--            <artifactId>spring-test</artifactId>-->
<!--            <scope>test</scope>-->
<!--        </dependency>-->

<!--        <dependency>-->
<!--            <groupId>junit</groupId>-->
<!--            <artifactId>junit</artifactId>-->
<!--            <version>4.13.2</version>-->
<!--        </dependency>-->

  1. 简简单单才是最好的,不然容易出现错误,一直以为引入的是JUnit 5结果是JUnit 4导致运行失败
    ,在ruoyi-admin的src下创建test模块(与main同级),导入依赖后更新maven,确保依赖加入
/**
 1. @description 测试MybatisPlus和分表功能
 */

//JUnit 4
//@SpringBootTest(classes = Application.class,
//        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//@RunWith(SpringRunner.class)
//JUnit 5

("MybatisPlus测试")
public class MybatisPlusTest {

    //测试分页功能
    
    private service方法 taskMapper;

    
    private ISysUserService sysUserService;

    
    private RuoYiConfig ruoYiConfig;
    
    public void testContextLoad() {
        assertNotNull("taskService 注入失败", taskMapper);
        assertNotNull("sysUserService 注入失败", sysUserService);
        log.info("服务注入测试通过:"+ruoYiConfig.getName());
    }

    /**
     * 添加任务(使用动态表名)
     */
    ("添加任务(使用动态表名)")
    
    public void addTaskWithDynamicTable() {
        try {

            实体类 task = new  实体类();
            task.settName("张三");
            task.setAge("18");
            task.setPatientSex(1);
            task.setMobile("13888888888");
            task.setIdCard("420000000000000000");
            // 插入数据(会自动使用动态表名)
           taskMapper.insertHosCollectTaskInfo(task);
        } finally {
//            MybatisPlusUtils.emptyDynamicTableName();
        }
    }
    ("测试查询功能")
    
    public void testContextLoads() {
        log.info("Spring上下文加载成功,taskMapper已注入");
    }
    
    ("测试查询功能")
    
    public void testSelect() {
        log.info("测试查询功能");
        SysUser sysUser = sysUserService.selectUserById(1L);
        log.info("查询结果:{}", sysUser);
    }
}

三、问题解决

  1. 在 JUnit 5 中,不需要 @RunWith(SpringRunner.class),直接使用 @SpringBootTest 即可
  2. 在 JUnit 4 中,通常需要显式指定 @RunWith(SpringRunner.class),使用RunWith才能实例化到spring容器中
  3. JUnit 4如何没有引入 @RunWith,就会出现 NullPointerExecption,是因为 Spring 的依赖注入没有正确完成,或者相关的 Bean 没有被正确加载。
  4. 如果添加完成还是没有完成,还是服务注入失败问题,就需要使用 @ComponentScan 显式指定扫描包
// 正确配置启动类
(scanBasePackages = "com.ruoyi")
("com.ruoyi.**.mapper")
  1. 动态表名导致自动填充失效,实体类字段未正确配置自动填充策略
public void addTaskWithDynamicTable(实体类 task) {
    // 先设置动态表名
    MybatisPlusUtils.setDynamicTableName("表名");
    
    // 再手动设置时间(双重保障)
    task.setCreateTime(new Date());
    task.setUpdateTime(new Date());
    
    mapper类.insert(task);
}


#避免措施:实体类字段使用正确注解
(fill = FieldFill.INSERT) 
private Date createTime;
  1. 分页插件冲突问题,PageHelper 与 MyBatis-Plus 分页不兼容
    原因:同时存在两套分页机制,最好是不要改动原来的依赖,然后引入新的mybatis-plus即可

网站公告

今日签到

点亮在社区的每一天
去签到