一、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<>();
@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
- 仅供参考的分表功能 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();
}
}
}
}
- 使用方式在需要增删改查的地方调用方法即可
/**
* 添加任务(使用动态表名)
*/
@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();
}
}
二、集成单元测试
- 使用依赖,在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即可