一、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即可