Spring Boot集成
簡要描述:MyBatis-Plus與Spring Boot的深度集成,提供了自動配置、啟動器等特性,大大簡化了配置和使用。
核心概念:
- 自動配置:基于條件的自動配置機制
- 啟動器:簡化依賴管理的starter
- 配置屬性:通過application.yml進行配置
- 條件化配置:根據環境動態調整配置
SpringBoot自動配置
簡要描述:Spring Boot通過自動配置機制,自動裝配MyBatis-Plus相關的Bean,無需手動配置大量的XML或Java配置。
核心概念:
- @EnableAutoConfiguration:啟用自動配置
- @ConditionalOn*:條件化配置注解
- AutoConfiguration類:自動配置類
- spring.factories:自動配置發現機制
自動配置原理:
// MyBatis-Plus自動配置類
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class, MybatisPlusAutoConfiguration.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);private final MybatisPlusProperties properties;private final Interceptor[] interceptors;private final TypeHandler[] typeHandlers;private final LanguageDriver[] languageDrivers;private final ResourceLoader resourceLoader;private final DatabaseIdProvider databaseIdProvider;private final List<ConfigurationCustomizer> configurationCustomizers;private final List<MybatisPlusPropertiesCustomizer> mybatisPlusPropertiesCustomizers;private final ApplicationContext applicationContext;public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,ObjectProvider<TypeHandler[]> typeHandlersProvider,ObjectProvider<LanguageDriver[]> languageDriversProvider,ResourceLoader resourceLoader,ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider,ApplicationContext applicationContext) {this.properties = properties;this.interceptors = interceptorsProvider.getIfAvailable();this.typeHandlers = typeHandlersProvider.getIfAvailable();this.languageDrivers = languageDriversProvider.getIfAvailable();this.resourceLoader = resourceLoader;this.databaseIdProvider = databaseIdProvider.getIfAvailable();this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable();this.applicationContext = applicationContext;}@Bean@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBeanMybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));}applyConfiguration(factory);if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties());}if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);}if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider);}if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if (this.properties.getTypeAliasesSuperType() != null) {factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());}if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.typeHandlers)) {factory.setTypeHandlers(this.typeHandlers);}Resource[] mapperLocations = this.properties.resolveMapperLocations();if (!ObjectUtils.isEmpty(mapperLocations)) {factory.setMapperLocations(mapperLocations);}// 修改源碼支持定制化 GlobalConfigGlobalConfig globalConfig = this.properties.getGlobalConfig();//注入填充器this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);//注入主鍵生成器this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));//注入sql注入器this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);//注入ID生成器this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);//設置 GlobalConfig 到 MybatisSqlSessionFactoryBeanfactory.setGlobalConfig(globalConfig);return factory.getObject();}private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {consumer.accept(this.applicationContext.getBean(clazz));}}private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {MybatisPlusConfiguration configuration = this.properties.getConfiguration();if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {configuration = new MybatisPlusConfiguration();}if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {for (ConfigurationCustomizer customizer : this.configurationCustomizers) {customizer.customize(configuration);}}factory.setConfiguration(configuration);}@Bean@ConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType = this.properties.getExecutorType();if (executorType != null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}@Overridepublic void afterPropertiesSet() {if (!CollectionUtils.isEmpty(this.mybatisPlusPropertiesCustomizers)) {this.mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(this.properties));}checkConfigFileExists();}private void checkConfigFileExists() {if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());Assert.state(resource.exists(),"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");}}
}
自定義自動配置:
// 自定義MyBatis-Plus配置
@Configuration
@AutoConfigureAfter(MybatisPlusAutoConfiguration.class)
public class CustomMybatisPlusAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分頁插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(500L);paginationInnerInterceptor.setOverflow(false);interceptor.addInnerInterceptor(paginationInnerInterceptor);// 樂觀鎖插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());// 防全表更新與刪除插件interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return interceptor;}@Bean@ConditionalOnMissingBeanpublic MetaObjectHandler metaObjectHandler() {return new CustomMetaObjectHandler();}@Bean@ConditionalOnMissingBeanpublic ISqlInjector sqlInjector() {return new CustomSqlInjector();}@Bean@ConditionalOnMissingBeanpublic IdentifierGenerator identifierGenerator() {return new CustomIdentifierGenerator();}
}// 自定義填充器
public class CustomMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser());this.strictInsertFill(metaObject, "updateBy", String.class, getCurrentUser());this.strictInsertFill(metaObject, "deleted", Integer.class, 0);}@Overridepublic void updateFill(MetaObject metaObject) {this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUser());}private String getCurrentUser() {// 從SecurityContext或其他地方獲取當前用戶return "system";}
}
配置文件詳解
簡要描述:Spring Boot通過application.yml或application.properties文件提供了豐富的配置選項,可以靈活配置MyBatis-Plus的各種特性。
核心配置項:
- 數據源配置:數據庫連接相關配置
- MyBatis配置:MyBatis核心配置
- MyBatis-Plus配置:MyBatis-Plus特有配置
- 日志配置:SQL日志輸出配置
完整配置示例:
# application.yml
spring:# 數據源配置datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: password# 連接池配置(使用Druid)druid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: SELECT 1test-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: truemax-pool-prepared-statement-per-connection-size: 20# 配置監控統計攔截的filtersfilters: stat,wall,slf4j# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000# 配置web監控web-stat-filter:enabled: trueurl-pattern: /*exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"stat-view-servlet:enabled: trueurl-pattern: /druid/*reset-enable: falselogin-username: adminlogin-password: admin# MyBatis-Plus配置
mybatis-plus:# 配置文件位置config-location: classpath:mybatis-config.xml# Mapper XML文件位置mapper-locations: classpath*:mapper/**/*Mapper.xml# 實體類包路徑type-aliases-package: com.example.entity# 類型處理器包路徑type-handlers-package: com.example.typehandler# 執行器類型executor-type: simple# 配置屬性configuration-properties:key1: value1key2: value2# MyBatis原生配置configuration:# 開啟駝峰命名轉換map-underscore-to-camel-case: true# 開啟緩存cache-enabled: true# 設置懶加載lazy-loading-enabled: true# 設置積極懶加載aggressive-lazy-loading: false# 允許多結果集multiple-result-sets-enabled: true# 允許使用列標簽use-column-label: true# 允許使用自定義緩存use-generated-keys: false# 給予被嵌套的resultMap以字段-屬性的映射支持auto-mapping-behavior: partial# 對于未知的SQL查詢,允許返回不同的結果集以達到通用的效果auto-mapping-unknown-column-behavior: warning# 配置默認的執行器default-executor-type: simple# 對于批量更新操作緩存SQL以提高性能default-statement-timeout: 25# 設置超時時間default-fetch-size: 100# 允許在嵌套語句中使用分頁safe-row-bounds-enabled: false# 允許在嵌套語句中使用分頁safe-result-handler-enabled: true# 是否開啟自動駝峰命名規則映射map-underscore-to-camel-case: true# 本地緩存機制local-cache-scope: session# 數據庫超廠商標識jdbc-type-for-null: other# 指定當結果集中值為null的時候如何處理call-setters-on-nulls: false# 指定MyBatis增加到日志名稱的前綴log-prefix: mybatis-plus# 指定MyBatis所用日志的具體實現log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl# 使用實際參數名稱use-actual-param-name: true# 返回map時true:當查詢返回的所有列都為空時,MyBatis返回null false:當查詢返回的所有列都為空時,MyBatis返回一個空的Mapreturn-instance-for-empty-row: false# 指定VFS的實現vfs-impl: org.mybatis.spring.boot.autoconfigure.SpringBootVFS# 指定默認的類型別名超類default-scripting-language-driver: org.apache.ibatis.scripting.xmltags.XMLLanguageDriver# 全局配置global-config:# 是否控制臺 print mybatis-plus 的 LOGObanner: true# 是否初始化 SqlRunnerenable-sql-runner: false# 數據庫配置db-config:# 主鍵類型(AUTO:數據庫自增 NONE:無狀態 INPUT:自行輸入 ASSIGN_ID:分配ID ASSIGN_UUID:分配UUID)id-type: ASSIGN_ID# 表名前綴table-prefix: t_# 字段名前綴column-prefix: # 表名是否使用駝峰轉下劃線命名table-underline: true# 字段是否使用駝峰轉下劃線命名column-underline: true# 大寫命名capital-mode: false# 表關鍵詞 keywordkey-generator: com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator# 邏輯刪除全局值(默認 1、表示已刪除)logic-delete-value: 1# 邏輯未刪除全局值(默認 0、表示未刪除)logic-not-delete-value: 0# 字段驗證策略insert-strategy: not_nullupdate-strategy: not_nullwhere-strategy: not_null# 日志配置
logging:level:# MyBatis日志com.example.mapper: debug# 根日志級別root: info# SQL日志org.springframework.jdbc: debugpattern:console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"file:name: logs/application.logmax-size: 10MBmax-history: 30
事務管理集成
簡要描述:Spring Boot與MyBatis-Plus的事務管理集成,支持聲明式事務、編程式事務等多種事務管理方式。
核心概念:
- 聲明式事務:通過@Transactional注解管理事務
- 編程式事務:通過TransactionTemplate管理事務
- 事務傳播:事務的傳播行為
- 事務隔離:事務的隔離級別
事務配置:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {return new TransactionTemplate(transactionManager);}
}
聲明式事務使用:
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserRoleMapper userRoleMapper;// 默認事務配置public void saveUser(User user) {userMapper.insert(user);}// 只讀事務@Transactional(readOnly = true)public User findById(Long id) {return userMapper.selectById(id);}// 指定傳播行為@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveUserWithNewTransaction(User user) {userMapper.insert(user);}// 指定隔離級別@Transactional(isolation = Isolation.READ_COMMITTED)public void updateUser(User user) {userMapper.updateById(user);}// 復雜事務場景@Transactional(rollbackFor = Exception.class)public void saveUserWithRoles(User user, List<Long> roleIds) {// 保存用戶userMapper.insert(user);// 保存用戶角色關系for (Long roleId : roleIds) {UserRole userRole = new UserRole();userRole.setUserId(user.getId());userRole.setRoleId(roleId);userRoleMapper.insert(userRole);}// 模擬異常,測試事務回滾if (user.getUsername().equals("error")) {throw new RuntimeException("模擬異常");}}
}
編程式事務使用:
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate TransactionTemplate transactionTemplate;public void createOrder(Order order) {transactionTemplate.execute(status -> {try {// 業務邏輯orderMapper.insert(order);// 其他操作processOrderItems(order);return null;} catch (Exception e) {// 手動回滾status.setRollbackOnly();throw new RuntimeException("訂單創建失敗", e);}});}private void processOrderItems(Order order) {// 處理訂單項}
}
測試環境配置
簡要描述:為MyBatis-Plus配置測試環境,包括單元測試、集成測試等不同層次的測試配置。
核心概念:
- @SpringBootTest:Spring Boot測試注解
- @MybatisTest:MyBatis專用測試注解
- TestContainers:容器化測試
- H2數據庫:內存數據庫測試
測試依賴配置:
<dependencies><!-- Spring Boot Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- MyBatis-Plus Test --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter-test</artifactId><version>3.5.3.1</version><scope>test</scope></dependency><!-- H2數據庫 --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>test</scope></dependency><!-- TestContainers --><dependency><groupId>org.testcontainers</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency><dependency><groupId>org.testcontainers</groupId><artifactId>mysql</artifactId><scope>test</scope></dependency>
</dependencies>
測試配置文件:
# application-test.yml
spring:datasource:driver-class-name: org.h2.Driverurl: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_LOWER=TRUEusername: sapassword: h2:console:enabled: truepath: /h2-consolesql:init:schema-locations: classpath:schema.sqldata-locations: classpath:data.sqlmode: alwaysmybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: autologic-delete-value: 1logic-not-delete-value: 0logging:level:com.example.mapper: debug
單元測試示例:
// Mapper層測試
@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserMapperTest {@Autowiredprivate TestEntityManager entityManager;@Autowiredprivate UserMapper userMapper;@Testvoid testInsert() {// 準備測試數據User user = new User();user.setUsername("test");user.setEmail("test@example.com");// 執行測試int result = userMapper.insert(user);// 驗證結果assertThat(result).isEqualTo(1);assertThat(user.getId()).isNotNull();}@Testvoid testSelectById() {// 準備測試數據User user = new User();user.setUsername("test");user.setEmail("test@example.com");entityManager.persistAndFlush(user);// 執行測試User found = userMapper.selectById(user.getId());// 驗證結果assertThat(found).isNotNull();assertThat(found.getUsername()).isEqualTo("test");}@Testvoid testSelectByCondition() {// 準備測試數據User user1 = new User();user1.setUsername("test1");user1.setEmail("test1@example.com");entityManager.persistAndFlush(user1);User user2 = new User();user2.setUsername("test2");user2.setEmail("test2@example.com");entityManager.persistAndFlush(user2);// 執行測試QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.like("username", "test");List<User> users = userMapper.selectList(wrapper);// 驗證結果assertThat(users).hasSize(2);}
}
Service層測試:
@SpringBootTest
@ActiveProfiles("test")
@Transactional
@Rollback
class UserServiceTest {@Autowiredprivate UserService userService;@Testvoid testSaveUser() {// 準備測試數據User user = new User();user.setUsername("test");user.setEmail("test@example.com");// 執行測試userService.saveUser(user);// 驗證結果assertThat(user.getId()).isNotNull();assertThat(user.getCreateTime()).isNotNull();}@Testvoid testSaveUserWithRoles() {// 準備測試數據User user = new User();user.setUsername("test");user.setEmail("test@example.com");List<Long> roleIds = Arrays.asList(1L, 2L);// 執行測試userService.saveUserWithRoles(user, roleIds);// 驗證結果assertThat(user.getId()).isNotNull();// 驗證角色關系QueryWrapper<UserRole> wrapper = new QueryWrapper<>();wrapper.eq("user_id", user.getId());List<UserRole> userRoles = userRoleMapper.selectList(wrapper);assertThat(userRoles).hasSize(2);}@Testvoid testTransactionRollback() {// 準備測試數據User user = new User();user.setUsername("error"); // 觸發異常user.setEmail("error@example.com");List<Long> roleIds = Arrays.asList(1L, 2L);// 執行測試并驗證異常assertThrows(RuntimeException.class, () -> {userService.saveUserWithRoles(user, roleIds);});// 驗證事務回滾QueryWrapper<User> userWrapper = new QueryWrapper<>();userWrapper.eq("username", "error");List<User> users = userMapper.selectList(userWrapper);assertThat(users).isEmpty();}
}
TestContainers集成測試:
@SpringBootTest
@Testcontainers
class UserServiceIntegrationTest {@Containerstatic MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0").withDatabaseName("testdb").withUsername("test").withPassword("test");@DynamicPropertySourcestatic void configureProperties(DynamicPropertyRegistry registry) {registry.add("spring.datasource.url", mysql::getJdbcUrl);registry.add("spring.datasource.username", mysql::getUsername);registry.add("spring.datasource.password", mysql::getPassword);}@Autowiredprivate UserService userService;@Testvoid testUserOperations() {// 測試用戶操作User user = new User();user.setUsername("integration-test");user.setEmail("integration@example.com");userService.saveUser(user);User found = userService.findById(user.getId());assertThat(found).isNotNull();assertThat(found.getUsername()).isEqualTo("integration-test");}
}
性能優化與監控
簡要描述:MyBatis-Plus的性能優化涉及SQL優化、緩存策略、連接池配置、監控診斷等多個方面,通過合理的配置和使用可以顯著提升應用性能。
核心概念:
- SQL性能分析:分析SQL執行效率
- 慢查詢優化:識別和優化慢查詢
- 緩存策略:合理使用緩存提升性能
- 連接池優化:優化數據庫連接池配置
- 監控診斷:實時監控和問題診斷
SQL性能分析
簡要描述:通過各種工具和插件分析SQL執行性能,識別性能瓶頸。
核心概念:
- 執行計劃分析:分析SQL執行計劃
- 性能監控插件:MyBatis-Plus性能監控插件
- SQL統計:SQL執行統計信息
- 性能指標:關鍵性能指標監控
性能監控插件配置:
// 性能分析插件
@Configuration
public class PerformanceConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// SQL性能規范插件interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());// 分頁插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(1000L);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}@Bean@Profile("dev")public PerformanceInterceptor performanceInterceptor() {PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();// 設置SQL執行最大時長,超過自動停止運行,有助于發現問題performanceInterceptor.setMaxTime(1000);// 設置SQL格式化,默認falseperformanceInterceptor.setFormat(true);return performanceInterceptor;}
}// 自定義性能監控插件
@Component
public class CustomPerformanceInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(CustomPerformanceInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {long startTime = System.currentTimeMillis();try {Object result = invocation.proceed();long endTime = System.currentTimeMillis();long executeTime = endTime - startTime;// 記錄執行時間if (executeTime > 100) { // 超過100ms的SQL記錄警告MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];logger.warn("慢SQL檢測 - 執行時間: {}ms, SQL ID: {}, 參數: {}", executeTime, mappedStatement.getId(), parameter);}return result;} catch (Exception e) {long endTime = System.currentTimeMillis();long executeTime = endTime - startTime;logger.error("SQL執行異常 - 執行時間: {}ms, 異常信息: {}", executeTime, e.getMessage());throw e;}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 設置屬性}
}
執行計劃分析工具:
// 執行計劃分析工具
@Component
public class ExecutionPlanAnalyzer {@Autowiredprivate SqlSessionFactory sqlSessionFactory;public List<ExecutionPlan> analyzeExecutionPlan(String sql, Object... params) {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 執行EXPLAIN分析String explainSql = "EXPLAIN " + sql;List<Map<String, Object>> results = sqlSession.selectList("analyzeExecutionPlan", Map.of("sql", explainSql, "params", params));return results.stream().map(this::convertToExecutionPlan).collect(Collectors.toList());}}private ExecutionPlan convertToExecutionPlan(Map<String, Object> result) {ExecutionPlan plan = new ExecutionPlan();plan.setId((Integer) result.get("id"));plan.setSelectType((String) result.get("select_type"));plan.setTable((String) result.get("table"));plan.setType((String) result.get("type"));plan.setPossibleKeys((String) result.get("possible_keys"));plan.setKey((String) result.get("key"));plan.setKeyLen((String) result.get("key_len"));plan.setRef((String) result.get("ref"));plan.setRows((Long) result.get("rows"));plan.setExtra((String) result.get("Extra"));return plan;}public boolean hasPerformanceIssues(List<ExecutionPlan> plans) {for (ExecutionPlan plan : plans) {// 檢查是否有性能問題if ("ALL".equals(plan.getType()) || // 全表掃描plan.getRows() > 10000 || // 掃描行數過多plan.getExtra().contains("Using filesort") || // 文件排序plan.getExtra().contains("Using temporary")) { // 使用臨時表return true;}}return false;}
}
慢查詢優化
簡要描述:識別、分析和優化慢查詢,提升數據庫查詢性能。
核心概念:
- 慢查詢日志:記錄執行時間超過閾值的SQL
- 索引優化:合理創建和使用索引
- 查詢重寫:優化SQL語句結構
- 分頁優化:優化大數據量分頁查詢
慢查詢監控配置:
# application.yml
spring:datasource:druid:# 慢SQL記錄filter:stat:enabled: trueslow-sql-millis: 1000log-slow-sql: true# 監控配置stat-view-servlet:enabled: trueurl-pattern: /druid/*reset-enable: falseweb-stat-filter:enabled: trueurl-pattern: /*exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"# MyBatis-Plus慢查詢配置
mybatis-plus:configuration:# 開啟SQL日志log-impl: org.apache.ibatis.logging.slf4j.Slf4jImplglobal-config:# 性能分析插件enable-sql-runner: truelogging:level:# 開啟SQL日志com.example.mapper: debug# Druid慢SQL日志druid.sql.Statement: debug
慢查詢優化策略:
// 慢查詢優化服務
@Service
public class SlowQueryOptimizationService {@Autowiredprivate UserMapper userMapper;// 優化前:全表掃描public List<User> findUsersByNameBad(String name) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.like("username", "%" + name + "%"); // 前綴模糊查詢,無法使用索引return userMapper.selectList(wrapper);}// 優化后:使用索引public List<User> findUsersByNameGood(String name) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.likeRight("username", name); // 右模糊查詢,可以使用索引return userMapper.selectList(wrapper);}// 優化前:N+1查詢問題public List<UserWithRoles> findUsersWithRolesBad() {List<User> users = userMapper.selectList(null);return users.stream().map(user -> {List<Role> roles = roleMapper.selectByUserId(user.getId()); // N+1查詢return new UserWithRoles(user, roles);}).collect(Collectors.toList());}// 優化后:批量查詢public List<UserWithRoles> findUsersWithRolesGood() {List<User> users = userMapper.selectList(null);if (users.isEmpty()) {return Collections.emptyList();}List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());// 批量查詢角色List<UserRole> userRoles = userRoleMapper.selectList(new QueryWrapper<UserRole>().in("user_id", userIds));// 構建用戶角色映射Map<Long, List<Role>> userRoleMap = userRoles.stream().collect(Collectors.groupingBy(UserRole::getUserId,Collectors.mapping(ur -> roleMapper.selectById(ur.getRoleId()),Collectors.toList())));return users.stream().map(user -> new UserWithRoles(user, userRoleMap.getOrDefault(user.getId(), Collections.emptyList()))).collect(Collectors.toList());}// 分頁查詢優化public IPage<User> findUsersWithPagination(int current, int size, String keyword) {Page<User> page = new Page<>(current, size);QueryWrapper<User> wrapper = new QueryWrapper<>();if (StringUtils.hasText(keyword)) {wrapper.and(w -> w.like("username", keyword).or().like("email", keyword));}// 優化:只查詢必要字段wrapper.select("id", "username", "email", "create_time");return userMapper.selectPage(page, wrapper);}// 大數據量分頁優化(游標分頁)public List<User> findUsersWithCursorPagination(Long lastId, int size) {QueryWrapper<User> wrapper = new QueryWrapper<>();if (lastId != null) {wrapper.gt("id", lastId);}wrapper.orderByAsc("id");wrapper.last("LIMIT " + size);return userMapper.selectList(wrapper);}
}
索引優化建議:
-- 創建合適的索引
-- 1. 單列索引
CREATE INDEX idx_user_username ON user(username);
CREATE INDEX idx_user_email ON user(email);
CREATE INDEX idx_user_create_time ON user(create_time);-- 2. 復合索引(注意字段順序)
CREATE INDEX idx_user_status_create_time ON user(status, create_time);
CREATE INDEX idx_user_dept_status ON user(dept_id, status);-- 3. 覆蓋索引(包含查詢所需的所有字段)
CREATE INDEX idx_user_cover ON user(status, username, email, create_time);-- 4. 前綴索引(對于長字符串字段)
CREATE INDEX idx_user_description ON user(description(50));-- 5. 函數索引(MySQL 8.0+)
CREATE INDEX idx_user_upper_username ON user((UPPER(username)));
緩存優化策略
簡要描述:合理使用MyBatis一級緩存、二級緩存以及外部緩存系統,提升查詢性能。
核心概念:
- 一級緩存:SqlSession級別的緩存
- 二級緩存:Mapper級別的緩存
- 外部緩存:Redis等外部緩存系統
- 緩存策略:緩存的使用策略和失效機制
MyBatis緩存配置:
# application.yml
mybatis-plus:configuration:# 開啟二級緩存cache-enabled: true# 本地緩存作用域local-cache-scope: session# 懶加載配置lazy-loading-enabled: trueaggressive-lazy-loading: false
Redis緩存集成:
// Redis緩存配置
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 設置序列化器Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);serializer.setObjectMapper(mapper);template.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(30)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).disableCachingNullValues();return RedisCacheManager.builder(factory).cacheDefaults(config).build();}
}
Spring Cache注解使用:
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 緩存查詢結果@Cacheable(value = "users", key = "#id")public User findById(Long id) {return userMapper.selectById(id);}// 緩存查詢結果(條件緩存)@Cacheable(value = "users", key = "#username", condition = "#username.length() > 3")public User findByUsername(String username) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("username", username);return userMapper.selectOne(wrapper);}// 更新時清除緩存@CacheEvict(value = "users", key = "#user.id")public void updateUser(User user) {userMapper.updateById(user);}// 刪除時清除緩存@CacheEvict(value = "users", key = "#id")public void deleteUser(Long id) {userMapper.deleteById(id);}// 清除所有緩存@CacheEvict(value = "users", allEntries = true)public void clearAllCache() {// 清除所有用戶緩存}// 更新緩存@CachePut(value = "users", key = "#user.id")public User saveUser(User user) {userMapper.insert(user);return user;}
}
連接池優化
簡要描述:優化數據庫連接池配置,提升數據庫連接效率和應用性能。
核心概念:
- 連接池大小:合理設置連接池大小
- 連接超時:設置合適的連接超時時間
- 連接驗證:連接有效性驗證
- 連接監控:連接池狀態監控
Druid連接池優化配置:
# application.yml
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:# 初始連接數initial-size: 10# 最小空閑連接數min-idle: 10# 最大活躍連接數max-active: 100# 獲取連接等待超時時間max-wait: 60000# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒time-between-eviction-runs-millis: 60000# 配置一個連接在池中最小生存的時間,單位是毫秒min-evictable-idle-time-millis: 300000# 配置一個連接在池中最大生存的時間,單位是毫秒max-evictable-idle-time-millis: 900000# 用來檢測連接是否有效的sql,要求是一個查詢語句validation-query: SELECT 1# 建議配置為true,不影響性能,并且保證安全性test-while-idle: true# 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能test-on-borrow: false# 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能test-on-return: false# 是否緩存preparedStatement,也就是PSCachepool-prepared-statements: true# 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發修改為truemax-pool-prepared-statement-per-connection-size: 20# 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用于防火墻filters: stat,wall,slf4j# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000# 合并多個DruidDataSource的監控數據use-global-data-source-stat: true# 配置web監控web-stat-filter:enabled: trueurl-pattern: /*exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"session-stat-enable: falsesession-stat-max-count: 1000principal-session-name: adminprincipal-cookie-name: adminprofile-enable: true# 配置監控頁面stat-view-servlet:enabled: trueurl-pattern: /druid/*# IP白名單(沒有配置或者為空,則允許所有訪問)allow: 127.0.0.1,192.168.163.1# IP黑名單 (存在共同時,deny優先于allow)deny: 192.168.1.73# 禁用HTML頁面上的"Reset All"功能reset-enable: false# 登錄名login-username: admin# 登錄密碼login-password: 123456
HikariCP連接池優化配置:
# application.yml
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcehikari:# 連接池名稱pool-name: HikariCP# 最小空閑連接數minimum-idle: 10# 最大連接池大小maximum-pool-size: 100# 自動提交auto-commit: true# 空閑連接存活最大時間,默認600000(10分鐘)idle-timeout: 600000# 連接池最大生命周期,0表示無限生命周期,默認1800000即30分鐘max-lifetime: 1800000# 連接超時時間,默認30000即30秒connection-timeout: 30000# 測試連接是否可用的查詢語句connection-test-query: SELECT 1# 連接初始化SQLconnection-init-sql: SET NAMES utf8mb4# 數據庫連接超時時間,默認30秒,即30000validation-timeout: 5000# 空閑連接檢測周期,默認30000毫秒keepalive-time: 30000# 是否允許連接泄露檢測leak-detection-threshold: 60000
連接池監控:
// 連接池監控服務
@Service
public class DataSourceMonitorService {@Autowiredprivate DataSource dataSource;public DataSourceStats getDataSourceStats() {if (dataSource instanceof DruidDataSource) {return getDruidStats((DruidDataSource) dataSource);} else if (dataSource instanceof HikariDataSource) {return getHikariStats((HikariDataSource) dataSource);}return new DataSourceStats();}private DataSourceStats getDruidStats(DruidDataSource druidDataSource) {DataSourceStats stats = new DataSourceStats();stats.setActiveCount(druidDataSource.getActiveCount());stats.setPoolingCount(druidDataSource.getPoolingCount());stats.setMaxActive(druidDataSource.getMaxActive());stats.setCreateCount(druidDataSource.getCreateCount());stats.setDestroyCount(druidDataSource.getDestroyCount());stats.setConnectCount(druidDataSource.getConnectCount());stats.setCloseCount(druidDataSource.getCloseCount());return stats;}private DataSourceStats getHikariStats(HikariDataSource hikariDataSource) {DataSourceStats stats = new DataSourceStats();HikariPoolMXBean poolBean = hikariDataSource.getHikariPoolMXBean();stats.setActiveCount(poolBean.getActiveConnections());stats.setPoolingCount(poolBean.getIdleConnections());stats.setMaxActive(hikariDataSource.getMaximumPoolSize());stats.setTotalConnections(poolBean.getTotalConnections());return stats;}@Scheduled(fixedRate = 30000) // 每30秒監控一次public void monitorDataSource() {DataSourceStats stats = getDataSourceStats();// 記錄監控日志logger.info("數據源監控 - 活躍連接: {}, 空閑連接: {}, 最大連接: {}", stats.getActiveCount(), stats.getPoolingCount(), stats.getMaxActive());// 檢查連接池健康狀況if (stats.getActiveCount() > stats.getMaxActive() * 0.8) {logger.warn("連接池使用率過高: {}%", (double) stats.getActiveCount() / stats.getMaxActive() * 100);}}
}
監控與診斷
簡要描述:通過各種監控工具和診斷手段,實時監控MyBatis-Plus應用的性能狀況。
核心概念:
- 性能指標監控:關鍵性能指標的實時監控
- 健康檢查:應用健康狀況檢查
- 鏈路追蹤:分布式鏈路追蹤
- 告警機制:異常情況告警
Actuator監控配置:
# application.yml
management:endpoints:web:exposure:include: "*"endpoint:health:show-details: alwaysmetrics:enabled: truemetrics:export:prometheus:enabled: truedistribution:percentiles-histogram:http.server.requests: truepercentiles:http.server.requests: 0.5, 0.9, 0.95, 0.99
自定義健康檢查:
// 數據庫健康檢查
@Component
public class DatabaseHealthIndicator implements HealthIndicator {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Overridepublic Health health() {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 執行簡單查詢測試數據庫連接sqlSession.selectOne("SELECT 1");return Health.up().withDetail("database", "Available").withDetail("validationQuery", "SELECT 1").build();} catch (Exception e) {return Health.down().withDetail("database", "Unavailable").withDetail("error", e.getMessage()).build();}}
}// MyBatis-Plus健康檢查
@Component
public class MybatisPlusHealthIndicator implements HealthIndicator {@Autowiredprivate UserMapper userMapper;@Overridepublic Health health() {try {// 測試基本CRUD操作long count = userMapper.selectCount(null);return Health.up().withDetail("mybatis-plus", "Available").withDetail("userCount", count).build();} catch (Exception e) {return Health.down().withDetail("mybatis-plus", "Unavailable").withDetail("error", e.getMessage()).build();}}
}
性能指標收集:
// 自定義性能指標
@Component
public class MybatisPlusMetrics {private final MeterRegistry meterRegistry;private final Counter sqlExecutionCounter;private final Timer sqlExecutionTimer;private final AtomicLong slowQueryCount = new AtomicLong(0);public MybatisPlusMetrics(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;this.sqlExecutionCounter = Counter.builder("mybatis.sql.executions").description("Total SQL executions").register(meterRegistry);this.sqlExecutionTimer = Timer.builder("mybatis.sql.duration").description("SQL execution duration").register(meterRegistry);Gauge.builder("mybatis.sql.slow.count").description("Number of slow queries").register(meterRegistry, this, MybatisPlusMetrics::getSlowQueryCount);}public void recordSqlExecution(String sqlId, long duration) {sqlExecutionCounter.increment(Tags.of("sql.id", sqlId,"status", "success"));sqlExecutionTimer.record(duration, TimeUnit.MILLISECONDS,Tags.of("sql.id", sqlId));}public void recordSqlError(String sqlId, String errorType) {sqlExecutionCounter.increment(Tags.of("sql.id", sqlId,"status", "error","error.type", errorType));}private double getSlowQueryCount() {return slowQueryCount.get();}public void incrementSlowQueryCount() {slowQueryCount.incrementAndGet();}
}