全部笔记All notes

SpringBoot 整合 MyBatis-Plus 和 Druid 完整指南

阅读 6m 44s6m 44s read

概述

MyBatis-Plus 是 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,提供了更强大的 CRUD 操作和代码生成功能。Druid 是阿里巴巴开源的数据库连接池,具有优秀的监控和扩展功能。

技术栈介绍

  • MyBatis-Plus: MyBatis 增强工具,提供 CRUD 接口和代码生成器
  • Druid: 高性能数据库连接池,具备监控和防火墙功能
  • SpringBoot: 提供自动配置和快速集成

核心优势

技术主要优势应用场景
MyBatis-Plus零配置、强大的 CRUD、代码生成器简化数据库操作,提高开发效率
Druid监控统计、SQL 防火墙、高性能生产环境数据库连接管理

💡 提示: MyBatis-Plus 和 Druid 的结合可以极大提升开发效率和应用性能。

环境准备

系统要求

  • JDK 8+
  • Maven 3.6+ 或 Gradle 6+
  • MySQL 5.7+ 或其他关系型数据库
  • SpringBoot 2.0+

Maven 依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>springboot-mybatis-plus-druid</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.4</mybatis-plus.version>
        <druid.version>1.2.20</druid.version>
    </properties>
    
    <dependencies>
        <!-- SpringBoot Web Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- MyBatis-Plus SpringBoot Starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        
        <!-- Druid SpringBoot Starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        
        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- SpringBoot 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- 代码生成器依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.4</version>
            <scope>test</scope>
        </dependency>
        
        <!-- 模板引擎(代码生成器需要) -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

整合 MyBatis-Plus

1. 基础配置

application.yml 配置

# 服务器配置
server:
  port: 8080

# Spring 配置
spring:
  application:
    name: springboot-mybatis-plus-druid

# MyBatis-Plus 配置
mybatis-plus:
  # 配置扫描通用枚举,支持统计等操作
  type-enums-package: com.example.enums
  # 全局配置
  global-config:
    # 数据库配置
    db-config:
      # 设置所有表的通用前缀名称
      table-prefix: tbl_
      # 主键策略(AUTO: 数据库自增,ASSIGN_ID: 雪花算法)
      id-type: ASSIGN_ID
      # 逻辑删除字段名
      logic-delete-field: deleted
      # 逻辑删除值(删除)
      logic-delete-value: 1
      # 逻辑删除值(未删除)
      logic-not-delete-value: 0
      # 字段验证策略
      insert-strategy: NOT_NULL
      update-strategy: NOT_NULL
      select-strategy: NOT_NULL
  # 配置文件
  configuration:
    # 开启驼峰命名转换
    map-underscore-to-camel-case: true
    # 开启缓存
    cache-enabled: true
    # 开启延迟加载
    lazy-loading-enabled: true
    # 开启二级缓存
    local-cache-scope: STATEMENT
    # 日志实现
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # Mapper XML 文件位置
  mapper-locations: classpath*:/mapper/**/*.xml
  # 实体类别名包路径
  type-aliases-package: com.example.entity

# 分页插件配置
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql

# 日志配置
logging:
  level:
    com.example.mapper: debug
    com.alibaba.druid: info
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n'

2. 实体类定义

使用 MyBatis-Plus 注解来简化配置:

package com.example.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tbl_book")
public class Book {
    
    /**
     * 主键ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    /**
     * 书籍类型
     */
    @TableField("type")
    private String type;
    
    /**
     * 书籍名称
     */
    @TableField("name")
    private String name;
    
    /**
     * 书籍描述
     */
    @TableField("description")
    private String description;
    
    /**
     * 作者
     */
    @TableField("author")
    private String author;
    
    /**
     * 价格
     */
    @TableField("price")
    private Double price;
    
    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    /**
     * 更新时间
     */
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    /**
     * 逻辑删除标识
     */
    @TableLogic
    @TableField("deleted")
    private Integer deleted;
    
    /**
     * 版本号(乐观锁)
     */
    @Version
    @TableField("version")
    private Integer version;
}

3. Mapper 接口定义

继承 BaseMapper 获得基础 CRUD 功能:

package com.example.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.entity.Book;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface BookMapper extends BaseMapper<Book> {
    
    /**
     * 根据类型查询书籍列表
     */
    @Select("SELECT * FROM tbl_book WHERE type = #{type} AND deleted = 0")
    List<Book> findByType(@Param("type") String type);
    
    /**
     * 分页查询书籍
     */
    @Select("SELECT * FROM tbl_book WHERE deleted = 0 ORDER BY create_time DESC")
    IPage<Book> selectBookPage(Page<Book> page);
    
    /**
     * 根据价格区间查询
     */
    @Select("SELECT * FROM tbl_book WHERE price BETWEEN #{minPrice} AND #{maxPrice} AND deleted = 0")
    List<Book> findByPriceRange(@Param("minPrice") Double minPrice, @Param("maxPrice") Double maxPrice);
    
    /**
     * 统计各类型书籍数量
     */
    @Select("SELECT type, COUNT(*) as count FROM tbl_book WHERE deleted = 0 GROUP BY type")
    List<Object> countByType();
    
    /**
     * 自定义复杂查询
     */
    List<Book> findByCustomCondition(@Param("type") String type, 
                                    @Param("author") String author, 
                                    @Param("minPrice") Double minPrice,
                                    @Param("maxPrice") Double maxPrice);
}

4. Service 层实现

使用 MyBatis-Plus 提供的 IService 接口:

package com.example.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.entity.Book;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import java.util.List;

public interface BookService extends IService<Book> {
    
    /**
     * 根据类型查询书籍
     */
    List<Book> findByType(String type);
    
    /**
     * 分页查询书籍
     */
    IPage<Book> selectBookPage(Page<Book> page);
    
    /**
     * 根据价格区间查询
     */
    List<Book> findByPriceRange(Double minPrice, Double maxPrice);
    
    /**
     * 批量保存书籍
     */
    boolean saveBatchBooks(List<Book> books);
    
    /**
     * 根据ID逻辑删除
     */
    boolean logicDeleteById(Long id);
}

Service 实现类:

package com.example.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.Book;
import com.example.mapper.BookMapper;
import com.example.service.BookService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {
    
    @Override
    public List<Book> findByType(String type) {
        return baseMapper.findByType(type);
    }
    
    @Override
    public IPage<Book> selectBookPage(Page<Book> page) {
        return baseMapper.selectBookPage(page);
    }
    
    @Override
    public List<Book> findByPriceRange(Double minPrice, Double maxPrice) {
        return baseMapper.findByPriceRange(minPrice, maxPrice);
    }
    
    @Override
    public boolean saveBatchBooks(List<Book> books) {
        return this.saveBatch(books);
    }
    
    @Override
    public boolean logicDeleteById(Long id) {
        return this.removeById(id);
    }
    
    /**
     * 使用 QueryWrapper 进行条件查询
     */
    public List<Book> findByCondition(String type, String author, Double minPrice) {
        QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(type != null, "type", type)
                   .like(author != null, "author", author)
                   .ge(minPrice != null, "price", minPrice)
                   .orderByDesc("create_time");
        return this.list(queryWrapper);
    }
}

整合 Druid 连接池

1. Druid 配置

application.yml 中的 Druid 配置

spring:
  datasource:
    # Druid 数据源配置
    druid:
      # 数据库连接信息
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springboot_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&allowPublicKeyRetrieval=true
      username: root
      password: password123
      
      # 连接池配置
      # 初始化连接数
      initial-size: 5
      # 最小连接数
      min-idle: 5
      # 最大连接数
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位毫秒
      min-evictable-idle-time-millis: 300000
      # 配置一个连接在池中最大生存的时间,单位毫秒
      max-evictable-idle-time-millis: 900000
      
      # 验证连接有效性
      validation-query: SELECT 1 FROM DUAL
      # 申请连接时执行validationQuery检测连接是否有效
      test-while-idle: true
      # 申请连接时执行validationQuery检测连接是否有效
      test-on-borrow: false
      # 归还连接时执行validationQuery检测连接是否有效
      test-on-return: false
      
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计
      # stat: 监控统计
      # log4j: 日志
      # wall: 防火墙(防止SQL注入)
      filters: stat,wall,log4j2
      
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      
      # 配置监控服务器
      stat-view-servlet:
        enabled: true
        # 访问路径
        url-pattern: /druid/*
        # IP白名单(没有配置或者为空,则允许所有访问)
        allow: 127.0.0.1,192.168.1.0/24
        # IP黑名单(存在共同时,deny优先于allow)
        deny: 192.168.1.73
        # 禁用HTML页面上的"Reset All"功能
        reset-enable: false
        # 登录名
        login-username: admin
        # 登录密码
        login-password: 123456
        
      # 配置Web应用监控
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        
      # 配置Spring监控
      aop-patterns: com.example.service.*

2. Druid 配置类

创建自定义配置类来进一步定制 Druid:

package com.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {
    
    /**
     * 配置Druid数据源
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    
    /**
     * 配置Druid监控页面
     */
    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(
            new StatViewServlet(), "/druid/*"
        );
        
        Map<String, String> initParams = new HashMap<>();
        // 访问用户名和密码
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "123456");
        // 默认允许所有访问
        initParams.put("allow", "");
        // 拒绝访问的IP
        initParams.put("deny", "192.168.1.73");
        
        bean.setInitParameters(initParams);
        return bean;
    }
    
    /**
     * 配置web监控filter
     */
    @Bean
    public FilterRegistrationBean<WebStatFilter> webStatFilter() {
        FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());
        
        Map<String, String> initParams = new HashMap<>();
        // 不拦截的请求
        initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
        
        bean.setInitParameters(initParams);
        // 拦截所有请求
        bean.setUrlPatterns(Arrays.asList("/*"));
        
        return bean;
    }
}

完整集成示例

1. MyBatis-Plus 配置类

package com.example.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;

@Configuration
public class MybatisPlusConfig {
    
    /**
     * 配置分页插件和乐观锁插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
    
    /**
     * 自动填充处理器
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MetaObjectHandler() {
            @Override
            public void insertFill(MetaObject metaObject) {
                this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
                this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
            }
            
            @Override
            public void updateFill(MetaObject metaObject) {
                this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
            }
        };
    }
}

2. 控制器实现

package com.example.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.entity.Book;
import com.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/books")
public class BookController {
    
    @Autowired
    private BookService bookService;
    
    /**
     * 分页查询书籍
     */
    @GetMapping("/page")
    public ResponseEntity<IPage<Book>> getBookPage(
            @RequestParam(defaultValue = "1") Long current,
            @RequestParam(defaultValue = "10") Long size) {
        Page<Book> page = new Page<>(current, size);
        IPage<Book> bookPage = bookService.selectBookPage(page);
        return ResponseEntity.ok(bookPage);
    }
    
    /**
     * 根据ID查询书籍
     */
    @GetMapping("/{id}")
    public ResponseEntity<Book> getBook(@PathVariable Long id) {
        Book book = bookService.getById(id);
        return book != null ? ResponseEntity.ok(book) : ResponseEntity.notFound().build();
    }
    
    /**
     * 根据类型查询书籍
     */
    @GetMapping("/type/{type}")
    public List<Book> getBooksByType(@PathVariable String type) {
        return bookService.findByType(type);
    }
    
    /**
     * 根据价格区间查询
     */
    @GetMapping("/price")
    public List<Book> getBooksByPriceRange(
            @RequestParam Double minPrice,
            @RequestParam Double maxPrice) {
        return bookService.findByPriceRange(minPrice, maxPrice);
    }
    
    /**
     * 创建书籍
     */
    @PostMapping
    public ResponseEntity<String> createBook(@RequestBody Book book) {
        boolean result = bookService.save(book);
        return result ? ResponseEntity.ok("创建成功") : ResponseEntity.badRequest().body("创建失败");
    }
    
    /**
     * 批量创建书籍
     */
    @PostMapping("/batch")
    public ResponseEntity<String> createBooks(@RequestBody List<Book> books) {
        boolean result = bookService.saveBatchBooks(books);
        return result ? ResponseEntity.ok("批量创建成功") : ResponseEntity.badRequest().body("批量创建失败");
    }
    
    /**
     * 更新书籍
     */
    @PutMapping("/{id}")
    public ResponseEntity<String> updateBook(@PathVariable Long id, @RequestBody Book book) {
        book.setId(id);
        boolean result = bookService.updateById(book);
        return result ? ResponseEntity.ok("更新成功") : ResponseEntity.badRequest().body("更新失败");
    }
    
    /**
     * 删除书籍(逻辑删除)
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteBook(@PathVariable Long id) {
        boolean result = bookService.logicDeleteById(id);
        return result ? ResponseEntity.ok("删除成功") : ResponseEntity.badRequest().body("删除失败");
    }
    
    /**
     * 获取所有书籍
     */
    @GetMapping
    public List<Book> getAllBooks() {
        return bookService.list();
    }
    
    /**
     * 统计书籍总数
     */
    @GetMapping("/count")
    public Long getBookCount() {
        return bookService.count();
    }
}

高级配置

1. 代码生成器

创建代码生成器来自动生成实体类、Mapper、Service 等:

package com.example.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;

import java.sql.Types;
import java.util.Collections;

public class CodeGenerator {
    
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/springboot_demo", "root", "password123")
                .globalConfig(builder -> {
                    builder.author("系统管理员") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://"); // 指定输出目录
                })
                .dataSourceConfig(builder -> 
                        builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                            int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                            if (typeCode == Types.SMALLINT) {
                                // 自定义类型转换
                                return DbColumnType.INTEGER;
                            }
                            return typeRegistry.getColumnType(metaInfo);
                        })
                )
                .packageConfig(builder -> {
                    builder.parent("com.example") // 设置父包名
                            .moduleName("demo") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://mapper")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("tbl_book") // 设置需要生成的表名
                            .addTablePrefix("tbl_") // 设置过滤表前缀
                            .entityBuilder()
                            .enableLombok() // 开启 lombok 模型
                            .enableTableFieldAnnotation() // 开启生成实体时生成字段注解
                            .controllerBuilder()
                            .enableRestStyle() // 开启生成@RestController 控制器
                            .mapperBuilder()
                            .enableMapperAnnotation() // 开启 @Mapper 注解
                            .serviceBuilder()
                            .formatServiceFileName("%sService") // 格式化 service 接口文件名称
                            .formatServiceImplFileName("%sServiceImpl"); // 格式化 service 实现类文件名称
                })
                .templateEngine(new VelocityTemplateEngine()) // 使用Velocity引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

2. 多数据源配置

@Configuration
public class MybatisPlusMultiDataSourceConfig {
    
    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    
    @Bean(name = "dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("primary", primaryDataSource());
        dataSourceMap.put("secondary", secondaryDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
        return dynamicDataSource;
    }
}

监控与管理

1. Druid 监控面板

访问监控面板:

监控面板提供以下功能:

  • 数据源监控: 连接池状态、活跃连接数等
  • SQL监控: SQL执行统计、慢SQL分析
  • URI监控: Web URI访问统计
  • Session监控: 用户Session统计
  • JSON API: 监控数据的JSON接口

2. SQL 监控配置

spring:
  datasource:
    druid:
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          slow-sql-millis: 1000
          log-slow-sql: true
          # 合并SQL
          merge-sql: true
        wall:
          enabled: true
          config:
            # 是否允许执行SELECT语句
            select-allow: true
            # 是否允许执行DELETE语句
            delete-allow: true
            # 是否允许执行UPDATE语句
            update-allow: true

3. 自定义监控指标

@Component
public class DatabaseMetrics {
    
    @Autowired
    private DataSource dataSource;
    
    @EventListener
    public void handleContextRefresh(ContextRefreshedEvent event) {
        if (dataSource instanceof DruidDataSource) {
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;
            
            // 注册自定义指标
            Gauge.builder("datasource.active.connections")
                 .description("Number of active connections")
                 .register(Metrics.globalRegistry, druidDataSource, DruidDataSource::getActiveCount);
                 
            Gauge.builder("datasource.max.connections")
                 .description("Maximum number of connections")
                 .register(Metrics.globalRegistry, druidDataSource, DruidDataSource::getMaxActive);
        }
    }
}

性能优化

1. 连接池优化

spring:
  datasource:
    druid:
      # 根据应用实际情况调整连接池大小
      initial-size: 10
      min-idle: 10
      max-active: 50
      
      # 连接超时设置
      max-wait: 30000
      
      # 连接回收设置
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      
      # 预编译语句缓存
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 50

2. MyBatis-Plus 优化

@Configuration
public class MybatisPlusOptimizeConfig {
    
    /**
     * SQL 性能分析插件(开发环境使用)
     */
    @Bean
    @Profile({"dev", "test"})
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        // 设置SQL执行最大时长,超过则停止运行
        performanceInterceptor.setMaxTime(1000);
        // 设置SQL格式化
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }
    
    /**
     * 数据权限插件
     */
    @Bean
    public DataPermissionInterceptor dataPermissionInterceptor() {
        return new DataPermissionInterceptor();
    }
}

测试验证

1. 单元测试

@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class BookServiceTest {
    
    @Autowired
    private BookService bookService;
    
    @Test
    void testSaveAndQuery() {
        // 创建测试数据
        Book book = new Book()
                .setType("技术")
                .setName("MyBatis-Plus实战")
                .setDescription("MyBatis-Plus开发指南")
                .setAuthor("张三")
                .setPrice(88.00);
        
        // 保存测试
        boolean saveResult = bookService.save(book);
        assertTrue(saveResult);
        assertNotNull(book.getId());
        
        // 查询测试
        Book savedBook = bookService.getById(book.getId());
        assertNotNull(savedBook);
        assertEquals("技术", savedBook.getType());
        
        // 更新测试
        savedBook.setPrice(99.00);
        boolean updateResult = bookService.updateById(savedBook);
        assertTrue(updateResult);
        
        // 删除测试(逻辑删除)
        boolean deleteResult = bookService.removeById(savedBook.getId());
        assertTrue(deleteResult);
        
        // 验证逻辑删除
        Book deletedBook = bookService.getById(savedBook.getId());
        assertNull(deletedBook);
    }
    
    @Test
    void testPageQuery() {
        // 分页查询测试
        Page<Book> page = new Page<>(1, 5);
        IPage<Book> result = bookService.selectBookPage(page);
        
        assertNotNull(result);
        assertTrue(result.getTotal() >= 0);
        assertTrue(result.getRecords().size() <= 5);
    }
    
    @Test
    void testBatchOperation() {
        // 批量操作测试
        List<Book> books = Arrays.asList(
            new Book().setType("文学").setName("红楼梦").setAuthor("曹雪芹").setPrice(56.00),
            new Book().setType("文学").setName("西游记").setAuthor("吴承恩").setPrice(48.00),
            new Book().setType("文学").setName("水浒传").setAuthor("施耐庵").setPrice(52.00)
        );
        
        boolean batchResult = bookService.saveBatch(books);
        assertTrue(batchResult);
        
        // 验证批量保存结果
        List<Book> savedBooks = bookService.findByType("文学");
        assertTrue(savedBooks.size() >= 3);
    }
}

2. 集成测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BookControllerIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Test
    void testBookCrud() {
        // 创建书籍
        Book book = new Book()
                .setType("技术")
                .setName("Spring Boot实战")
                .setAuthor("李四")
                .setPrice(78.00);
        
        ResponseEntity<String> createResponse = restTemplate.postForEntity("/api/books", book, String.class);
        assertEquals(HttpStatus.OK, createResponse.getStatusCode());
        
        // 查询书籍列表
        ResponseEntity<Book[]> listResponse = restTemplate.getForEntity("/api/books", Book[].class);
        assertEquals(HttpStatus.OK, listResponse.getStatusCode());
        assertTrue(listResponse.getBody().length > 0);
    }
}

常见问题

问题1:Druid 监控页面无法访问

现象: 访问 /druid/ 返回 404 原因: Druid 监控未正确配置 解决方案:

spring:
  datasource:
    druid:
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*

问题2:MyBatis-Plus 表前缀不生效

现象: SQL 中没有表前缀 原因: 配置路径错误 解决方案:

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_

问题3:分页查询不生效

现象: 分页参数不起作用 原因: 未配置分页插件 解决方案:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

问题4:逻辑删除不生效

现象: 删除操作执行物理删除 原因: 逻辑删除字段未正确配置 解决方案:

@TableLogic
@TableField("deleted")
private Integer deleted;

问题5:连接池连接泄露

现象: 连接数持续增长 原因: 连接未正确释放 解决方案:

spring:
  datasource:
    druid:
      # 开启连接泄露检测
      remove-abandoned: true
      remove-abandoned-timeout: 1800

⚠️ 注意: 在生产环境中,请根据实际情况调整连接池参数,并定期监控数据库连接状态。

相关文章

SpringBoot 系列

数据库相关

Java 基础

  • [Java EE 企业级开发](../JAVA EE/)

问题排查

运维部署

监控运维

上一章 / 下一章