一、Mybatis-Plus集成
- 新增依赖到父级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>
- 替换原来的 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
- 删除或者修改MyBatisConfig.java 配置 ,新增MybatisPlusConfig配置
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {public static final ThreadLocal<String> TABLE_NAME = new ThreadLocal<>();@Beanpublic 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();// 设置数据库类型为mysqlpaginationInnerInterceptor.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
- 仅供参考的分表功能 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*/@Overridepublic 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();}}}}
- 使用方式在需要增删改查的地方调用方法即可
/*** 添加任务(使用动态表名)*/@Transactional@Overridepublic 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();}}
二、集成单元测试
- 使用依赖,在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>-->
- 简简单单才是最好的,不然容易出现错误,一直以为引入的是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);}
}
三、问题解决
- 在 JUnit 5 中,不需要 @RunWith(SpringRunner.class),直接使用 @SpringBootTest 即可
- 在 JUnit 4 中,通常需要显式指定 @RunWith(SpringRunner.class),使用RunWith才能实例化到spring容器中
- JUnit 4如何没有引入 @RunWith,就会出现 NullPointerExecption,是因为 Spring 的依赖注入没有正确完成,或者相关的 Bean 没有被正确加载。
- 如果添加完成还是没有完成,还是服务注入失败问题,就需要使用 @ComponentScan 显式指定扫描包
// 正确配置启动类
(scanBasePackages = "com.ruoyi")
("com.ruoyi.**.mapper")
- 动态表名导致自动填充失效,实体类字段未正确配置自动填充策略
public void addTaskWithDynamicTable(实体类 task) {// 先设置动态表名MybatisPlusUtils.setDynamicTableName("表名");// 再手动设置时间(双重保障)task.setCreateTime(new Date());task.setUpdateTime(new Date());mapper类.insert(task);
}#避免措施:实体类字段使用正确注解
(fill = FieldFill.INSERT)
private Date createTime;
- 分页插件冲突问题,PageHelper 与 MyBatis-Plus 分页不兼容
原因:同时存在两套分页机制,最好是不要改动原来的依赖,然后引入新的mybatis-plus即可