Skip to main content

MyBatis-Plus框架详解

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。它提供了强大的CRUD操作、代码生成器、条件构造器等功能,让开发者专注于业务逻辑而非重复的CRUD代码。

核心价值

MyBatis-Plus = MyBatis增强 + 零侵入 + 损耗小 + 强大CRUD + 支持Lambda

  • 🚀 开箱即用:只需简单配置,即可快速进行CRUD操作
  • 🎯 代码生成:内置代码生成器,快速生成Entity、Mapper、Service、Controller
  • 💡 条件构造器:强大的条件构造器,支持各种复杂查询
  • 🔧 内置分页:基于MyBatis物理分页,性能优秀
  • 🛡️ 防全表更新删除:内置防止全表更新删除插件

1. 核心特性与架构

1.1 框架优势

MyBatis-Plus相比原生MyBatis具有显著的优势,主要体现在代码量减少、开发效率提升、功能更加丰富、性能损耗极小和无侵入性等方面。

特性MyBatisMyBatis-Plus
通用CRUD❌ 需手动编写✅ 内置通用Mapper
分页支持❌ 需手动实现✅ 内置分页插件
条件构造器❌ 需手动拼接SQL✅ 强大的条件构造器
代码生成器❌ 需第三方插件✅ 内置代码生成器
逻辑删除❌ 需手动实现✅ 一键配置即用
乐观锁插件❌ 需手动实现✅ 内置乐观锁插件
性能分析❌ 需手动实现✅ 内置性能分析插件
多租户支持❌ 需手动实现✅ 内置多租户插件
动态表名❌ 需手动实现✅ 内置动态表名插件
SQL注入器❌ 不支持✅ 支持自定义方法注入

1.1.1 开发效率对比

在实际项目开发中,使用MyBatis-Plus可以极大地减少重复代码编写,下面是一个简单的开发效率对比:

使用MyBatis:

  1. 编写Entity类
  2. 编写Mapper接口
  3. 编写Mapper XML文件
  4. 编写各种CRUD SQL语句
  5. 实现分页逻辑

使用MyBatis-Plus:

  1. 编写Entity类
  2. 继承BaseMapper接口
  3. 直接使用内置CRUD方法

典型的项目中,使用MyBatis-Plus可以减少约60%-70%的代码量,特别是在基础CRUD操作方面。

1.2 核心组件

MyBatis-Plus的核心组件包括:

  • BaseMapper:提供基础的CRUD方法
  • IService/ServiceImpl:业务层封装,提供更多批量操作方法
  • 条件构造器:QueryWrapper、LambdaQueryWrapper等
  • 分页插件:物理分页支持
  • 代码生成器:快速生成各层代码
  • 插件体系:提供多种扩展插件

1.2.1 架构设计

MyBatis-Plus的架构设计遵循了"高内聚、低耦合"的原则,主要分为以下几层:

  1. 数据访问层:BaseMapper接口及其实现
  2. 业务服务层:IService接口及ServiceImpl实现
  3. 插件扩展层:各种功能插件(分页、乐观锁等)
  4. 工具支持层:代码生成器、条件构造器等

1.2.2 核心注解一览

MyBatis-Plus提供了丰富的注解,用于实体类与数据库表的映射和功能增强:

注解位置说明
@TableName表名注解,标识实体类对应的表
@TableId属性主键注解,标识主键字段
@TableField属性字段注解,标识非主键字段
@Version属性乐观锁注解,标识乐观锁字段
@TableLogic属性逻辑删除注解,标识逻辑删除字段
@OrderBy属性排序注解,标识排序字段
@InterceptorIgnore类/方法插件忽略注解,用于跳过特定插件

1.3 项目集成

1.3.1 Maven依赖

xml
1<!-- MyBatis-Plus依赖 -->
2<dependency>
3 <groupId>com.baomidou</groupId>
4 <artifactId>mybatis-plus-boot-starter</artifactId>
5 <version>3.5.3</version>
6</dependency>
7
8<!-- 代码生成器依赖 -->
9<dependency>
10 <groupId>com.baomidou</groupId>
11 <artifactId>mybatis-plus-generator</artifactId>
12 <version>3.5.3</version>
13</dependency>
14
15<!-- 模板引擎依赖 -->
16<dependency>
17 <groupId>org.freemarker</groupId>
18 <artifactId>freemarker</artifactId>
19 <version>2.3.31</version>
20</dependency>

1.3.2 基础配置

在Spring Boot项目中,MyBatis-Plus的基础配置示例:

yaml
1# application.yml
2mybatis-plus:
3 # 映射文件位置
4 mapper-locations: classpath*:/mapper/**/*.xml
5 # 实体扫描,多个package用逗号或者分号分隔
6 typeAliasesPackage: com.example.entity
7 # 全局配置
8 global-config:
9 # 数据库相关配置
10 db-config:
11 # 主键类型
12 id-type: auto
13 # 字段策略
14 field-strategy: not_null
15 # 表名前缀
16 table-prefix: t_
17 # 逻辑删除配置
18 logic-delete-field: deleted
19 logic-delete-value: 1
20 logic-not-delete-value: 0
21 # MyBatis原生配置
22 configuration:
23 # 驼峰命名转换
24 map-underscore-to-camel-case: true
25 # 二级缓存
26 cache-enabled: false
27 # 结果集自动映射
28 auto-mapping-behavior: full
29 # 日志实现
30 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1.4 Spring Boot集成详解

MyBatis-Plus提供了专门的Spring Boot Starter,通过自动配置简化了与Spring Boot的集成过程。本节将详细介绍如何在Spring Boot项目中集成和配置MyBatis-Plus。

1.4.1 自动配置原理

MyBatis-Plus的Spring Boot Starter基于Spring Boot的自动配置机制,通过MybatisPlusAutoConfiguration类自动完成以下配置:

  1. 数据源配置:自动识别并使用项目中配置的数据源
  2. SqlSessionFactory配置:自动创建并配置SqlSessionFactory
  3. MapperScan配置:自动扫描Mapper接口
  4. 插件配置:自动注册配置的插件
  5. 全局配置:应用在application.yml/properties中的配置

1.4.2 完整项目结构

一个典型的Spring Boot + MyBatis-Plus项目结构如下:

1com.example.project
2├── config
3│ └── MybatisPlusConfig.java # MyBatis-Plus配置类
4├── controller
5│ └── UserController.java # 控制器层
6├── entity
7│ └── User.java # 实体类
8├── mapper
9│ └── UserMapper.java # Mapper接口
10├── service
11│ ├── UserService.java # 服务接口
12│ └── impl
13│ └── UserServiceImpl.java # 服务实现类
14├── ProjectApplication.java # 启动类
15└── resources
16 ├── application.yml # 配置文件
17 └── mapper # XML文件目录
18 └── UserMapper.xml # 自定义SQL映射文件

1.4.3 Spring Boot集成步骤

步骤1:引入依赖

在pom.xml文件中添加MyBatis-Plus的Spring Boot Starter依赖:

xml
1<dependency>
2 <groupId>com.baomidou</groupId>
3 <artifactId>mybatis-plus-boot-starter</artifactId>
4 <version>3.5.3</version>
5</dependency>
6
7<!-- 数据库驱动 -->
8<dependency>
9 <groupId>mysql</groupId>
10 <artifactId>mysql-connector-java</artifactId>
11 <scope>runtime</scope>
12</dependency>

步骤2:配置数据源

在application.yml或application.properties中配置数据库连接信息:

yaml
1spring:
2 datasource:
3 driver-class-name: com.mysql.cj.jdbc.Driver
4 url: jdbc:mysql://localhost:3306/mydatabase?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false
5 username: root
6 password: 123456

步骤3:创建实体类

创建与数据库表对应的实体类,添加相关注解:

java
1@Data
2@TableName("user")
3public class User {
4 @TableId(type = IdType.AUTO)
5 private Long id;
6
7 private String name;
8
9 private Integer age;
10
11 private String email;
12
13 @TableField(fill = FieldFill.INSERT)
14 private LocalDateTime createTime;
15
16 @TableField(fill = FieldFill.INSERT_UPDATE)
17 private LocalDateTime updateTime;
18
19 @Version
20 private Integer version;
21
22 @TableLogic
23 private Integer deleted;
24}

步骤4:创建Mapper接口

创建继承自BaseMapper的Mapper接口:

java
1@Mapper
2public interface UserMapper extends BaseMapper<User> {
3 // 可以添加自定义方法
4}

步骤5:创建Service层

创建Service接口和实现类:

java
1public interface UserService extends IService<User> {
2 // 可以添加自定义方法
3}
4
5@Service
6public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
7 // 实现自定义方法
8}

步骤6:创建Controller

创建Controller类,注入Service:

java
1@RestController
2@RequestMapping("/users")
3public class UserController {
4 @Autowired
5 private UserService userService;
6
7 @GetMapping
8 public List<User> list() {
9 return userService.list();
10 }
11
12 @GetMapping("/{id}")
13 public User getById(@PathVariable Long id) {
14 return userService.getById(id);
15 }
16
17 @PostMapping
18 public boolean save(@RequestBody User user) {
19 return userService.save(user);
20 }
21
22 @PutMapping
23 public boolean update(@RequestBody User user) {
24 return userService.updateById(user);
25 }
26
27 @DeleteMapping("/{id}")
28 public boolean delete(@PathVariable Long id) {
29 return userService.removeById(id);
30 }
31}

步骤7:配置插件

创建配置类,注册所需的插件:

java
1@Configuration
2public class MybatisPlusConfig {
3 /**
4 * 分页插件
5 */
6 @Bean
7 public MybatisPlusInterceptor mybatisPlusInterceptor() {
8 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
9 // 分页插件
10 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
11 // 乐观锁插件
12 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
13 // 防全表更新与删除插件
14 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
15 return interceptor;
16 }
17
18 /**
19 * 自动填充处理器
20 */
21 @Bean
22 public MetaObjectHandler metaObjectHandler() {
23 return new MetaObjectHandler() {
24 @Override
25 public void insertFill(MetaObject metaObject) {
26 this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
27 this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
28 }
29
30 @Override
31 public void updateFill(MetaObject metaObject) {
32 this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
33 }
34 };
35 }
36}

步骤8:启动类配置

在启动类中添加Mapper扫描注解:

java
1@SpringBootApplication
2@MapperScan("com.example.project.mapper")
3public class ProjectApplication {
4 public static void main(String[] args) {
5 SpringApplication.run(ProjectApplication.class, args);
6 }
7}

1.4.4 多数据源配置

在复杂业务场景中,可能需要连接多个数据源。MyBatis-Plus提供了dynamic-datasource-spring-boot-starter插件支持多数据源配置:

步骤1:引入依赖

xml
1<dependency>
2 <groupId>com.baomidou</groupId>
3 <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
4 <version>3.5.2</version>
5</dependency>

步骤2:配置多数据源

yaml
1spring:
2 datasource:
3 dynamic:
4 primary: master # 设置默认数据源
5 strict: false # 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
6 datasource:
7 master:
8 url: jdbc:mysql://localhost:3306/master?serverTimezone=Asia/Shanghai
9 username: root
10 password: 123456
11 driver-class-name: com.mysql.cj.jdbc.Driver
12 slave:
13 url: jdbc:mysql://localhost:3306/slave?serverTimezone=Asia/Shanghai
14 username: root
15 password: 123456
16 driver-class-name: com.mysql.cj.jdbc.Driver

步骤3:使用@DS注解切换数据源

java
1// 在类上使用,该类下所有方法都使用指定数据源
2@DS("slave")
3@Service
4public class OrderServiceImpl implements OrderService {
5
6 // 在方法上使用,覆盖类上的数据源配置
7 @DS("master")
8 @Transactional
9 public void createOrder(Order order) {
10 // 使用master数据源
11 }
12
13 public List<Order> listOrders() {
14 // 使用slave数据源
15 return list();
16 }
17}

1.4.5 与其他Spring Boot特性集成

1.4.5.1 事务管理

MyBatis-Plus与Spring Boot的事务管理无缝集成:

java
1@Service
2public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
3
4 @Transactional(rollbackFor = Exception.class)
5 public boolean saveWithRollback(User user) {
6 // 操作将在一个事务中执行
7 return save(user);
8 }
9}

1.4.5.2 Spring Cache集成

结合Spring Cache实现缓存功能:

java
1@Service
2@CacheConfig(cacheNames = "userCache")
3public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
4
5 @Cacheable(key = "#id")
6 @Override
7 public User getById(Serializable id) {
8 return super.getById(id);
9 }
10
11 @CacheEvict(key = "#user.id")
12 @Override
13 public boolean updateById(User user) {
14 return super.updateById(user);
15 }
16}

1.4.5.3 Spring Validation集成

结合Spring Validation实现参数校验:

java
1@RestController
2@RequestMapping("/users")
3public class UserController {
4 @Autowired
5 private UserService userService;
6
7 @PostMapping
8 public boolean save(@Valid @RequestBody User user) {
9 return userService.save(user);
10 }
11}
12
13@Data
14@TableName("user")
15public class User {
16 @TableId(type = IdType.AUTO)
17 private Long id;
18
19 @NotBlank(message = "用户名不能为空")
20 private String name;
21
22 @Min(value = 0, message = "年龄不能小于0")
23 @Max(value = 120, message = "年龄不能大于120")
24 private Integer age;
25
26 @Email(message = "邮箱格式不正确")
27 private String email;
28}

2. CRUD增强功能

2.1 通用Mapper接口

BaseMapper接口提供了常用的CRUD方法,只需让自己的Mapper接口继承BaseMapper即可获得这些方法。

java
1public interface UserMapper extends BaseMapper<User> {
2 // 无需编写任何方法,已经继承了BaseMapper的所有方法
3}

常用方法包括:

  • insert(T entity):插入一条记录
  • deleteById(Serializable id):根据ID删除
  • updateById(T entity):根据ID更新
  • selectById(Serializable id):根据ID查询
  • selectList(Wrapper<T> queryWrapper):条件查询
  • selectPage(Page<T> page, Wrapper<T> queryWrapper):分页查询

2.2 通用Service接口

IService接口和ServiceImpl实现类进一步封装了BaseMapper的方法,提供了更多的批量操作和链式调用方法。

java
1public interface UserService extends IService<User> {
2 // 自定义方法
3}
4
5@Service
6public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
7 // 实现自定义方法
8}

IService相比BaseMapper的优势:

  • 返回操作结果(boolean)而非影响行数(int)
  • 提供更多批量操作方法
  • 支持链式调用API
  • 更好的事务支持

2.3 ActiveRecord模式

ActiveRecord模式允许直接通过模型对象进行数据库操作,无需额外的DAO层。

java
1@Data
2@TableName("user")
3public class User extends Model<User> {
4 private Long id;
5 private String name;
6
7 @Override
8 protected Serializable pkVal() {
9 return this.id;
10 }
11}
12
13// 使用示例
14User user = new User();
15user.setName("张三");
16user.insert(); // 直接调用insert方法插入数据

3. 条件构造器

3.1 QueryWrapper

QueryWrapper用于构建WHERE条件、ORDER BY、GROUP BY等SQL片段。

java
1// 基本使用
2QueryWrapper<User> queryWrapper = new QueryWrapper<>();
3queryWrapper.eq("name", "张三") // name = '张三'
4 .ge("age", 18) // age >= 18
5 .orderByDesc("create_time"); // ORDER BY create_time DESC

3.2 LambdaQueryWrapper

LambdaQueryWrapper是QueryWrapper的Lambda表达式增强版,通过Lambda表达式实现类型安全的查询条件构建。

java
1LambdaQueryWrapper<User> lambdaQuery = Wrappers.<User>lambdaQuery();
2lambdaQuery.eq(User::getName, "张三")
3 .ge(User::getAge, 18);

3.3 条件构造器高级用法

  • 动态条件:根据参数值是否为空决定是否加入条件
  • 嵌套查询:使用nested、and、or方法实现复杂条件组合
  • 子查询:使用inSql、exists等方法实现子查询
  • SQL函数:使用apply方法支持SQL函数

4. 代码生成器

代码生成器可以快速生成Entity、Mapper、Service、Controller等各层代码。

java
1// 基本配置
2AutoGenerator generator = new AutoGenerator();
3// 全局配置、数据源配置、包配置、策略配置等
4generator.execute();

主要配置项:

  • 全局配置:输出目录、作者、是否覆盖等
  • 数据源配置:数据库连接信息
  • 包配置:各层包名设置
  • 策略配置:表名生成策略、字段名生成策略等
  • 模板配置:自定义代码模板

5. 分页插件

5.1 配置分页插件

java
1@Configuration
2public class MybatisPlusConfig {
3 @Bean
4 public MybatisPlusInterceptor mybatisPlusInterceptor() {
5 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
6 // 添加分页插件
7 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
8 return interceptor;
9 }
10}

5.2 使用分页查询

java
1// 创建Page对象
2Page<User> page = new Page<>(1, 10);
3// 执行分页查询
4Page<User> userPage = userMapper.selectPage(page, null);
5// 获取分页结果
6List<User> records = userPage.getRecords(); // 当前页数据
7long total = userPage.getTotal(); // 总记录数

5.3 自定义分页查询

对于复杂的分页查询,可以自定义SQL并使用分页插件:

java
1public interface UserMapper extends BaseMapper<User> {
2 IPage<UserVO> selectUserPage(Page<UserVO> page, Integer state);
3}
4
5// XML配置
6<select id="selectUserPage" resultType="com.example.vo.UserVO">
7 SELECT * FROM user WHERE state = #{state}
8</select>

6. 复杂SQL查询与自定义SQL

MyBatis-Plus在提供便捷CRUD操作的同时,也完全兼容MyBatis的自定义SQL功能,允许开发者处理复杂的业务查询需求。

6.1 复杂查询场景

6.1.1 多表联查

MyBatis-Plus支持通过自定义XML实现多表联查:

java
1// 在Mapper接口中定义方法
2public interface UserMapper extends BaseMapper<User> {
3 List<UserWithDeptVO> getUsersWithDept(@Param("status") Integer status);
4}

对应的XML配置:

xml
1<select id="getUsersWithDept" resultType="com.example.vo.UserWithDeptVO">
2 SELECT
3 u.id, u.name, u.age, u.email,
4 d.id as dept_id, d.name as dept_name
5 FROM user u
6 LEFT JOIN dept d ON u.dept_id = d.id
7 WHERE u.status = #{status}
8</select>

6.1.2 子查询

复杂子查询示例:

java
1// 在Mapper接口中定义方法
2public interface OrderMapper extends BaseMapper<Order> {
3 List<OrderVO> getOrdersWithDetails(@Param("userId") Long userId);
4}

对应的XML配置:

xml
1<select id="getOrdersWithDetails" resultMap="orderDetailResultMap">
2 SELECT
3 o.id, o.order_no, o.create_time,
4 (SELECT COUNT(*) FROM order_item WHERE order_id = o.id) as item_count,
5 (SELECT SUM(amount) FROM order_item WHERE order_id = o.id) as total_amount
6 FROM orders o
7 WHERE o.user_id = #{userId}
8</select>
9
10<resultMap id="orderDetailResultMap" type="com.example.vo.OrderVO">
11 <id column="id" property="id"/>
12 <result column="order_no" property="orderNo"/>
13 <result column="create_time" property="createTime"/>
14 <result column="item_count" property="itemCount"/>
15 <result column="total_amount" property="totalAmount"/>
16</resultMap>

6.1.3 动态SQL

MyBatis-Plus完全兼容MyBatis的动态SQL功能:

xml
1<select id="findUsers" resultType="com.example.entity.User">
2 SELECT * FROM user
3 <where>
4 <if test="name != null and name != ''">
5 AND name LIKE CONCAT('%', #{name}, '%')
6 </if>
7 <if test="age != null">
8 AND age >= #{age}
9 </if>
10 <if test="deptIds != null and deptIds.size() > 0">
11 AND dept_id IN
12 <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
13 #{deptId}
14 </foreach>
15 </if>
16 <choose>
17 <when test="status != null">
18 AND status = #{status}
19 </when>
20 <otherwise>
21 AND status = 1
22 </otherwise>
23 </choose>
24 </where>
25 ORDER BY create_time DESC
26</select>

6.2 高级SQL技巧

6.2.1 使用apply方法执行原生SQL

条件构造器的apply方法可以执行原生SQL:

java
1// 使用SQL函数
2LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
3 .apply("DATE_FORMAT(create_time, '%Y-%m-%d') = {0}", "2023-01-01")
4 .apply("FIND_IN_SET('manager', roles)");
5
6List<User> users = userMapper.selectList(wrapper);

6.2.2 使用last方法添加SQL后缀

last方法可以在SQL末尾添加任意SQL片段:

java
1// 使用FORCE INDEX强制使用指定索引
2LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
3 .eq(User::getStatus, 1)
4 .last("FORCE INDEX (idx_user_status)");
5
6// 使用LOCK IN SHARE MODE实现共享锁
7LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
8 .eq(User::getId, 1)
9 .last("LOCK IN SHARE MODE");

6.2.3 exists和notExists子查询

java
1// 查询有订单的用户
2LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
3 .exists("SELECT 1 FROM orders o WHERE o.user_id = user.id");
4
5// 查询没有订单的用户
6LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
7 .notExists("SELECT 1 FROM orders o WHERE o.user_id = user.id");

6.3 自定义SQL方法注入

MyBatis-Plus提供了SQL注入器机制,可以自定义通用方法:

java
1// 1. 定义自定义方法接口
2public interface InsertBatchMethod extends BaseMapper<T> {
3 int insertBatchSomeColumn(List<T> entityList);
4}
5
6// 2. 实现SQL提供者
7public class InsertBatchSqlInjector extends AbstractMethod {
8 @Override
9 public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
10 // 实现SQL注入逻辑
11 String sql = buildInsertBatchSql(tableInfo);
12 // 构建MappedStatement并返回
13 // ...
14 }
15
16 private String buildInsertBatchSql(TableInfo tableInfo) {
17 // 构建批量插入SQL
18 // ...
19 }
20}
21
22// 3. 注册SQL注入器
23@Configuration
24public class MybatisPlusConfig {
25 @Bean
26 public List<AbstractMethod> myInjectMethods() {
27 List<AbstractMethod> methodList = new ArrayList<>();
28 methodList.add(new InsertBatchSqlInjector());
29 return methodList;
30 }
31
32 @Bean
33 public GlobalConfig globalConfig() {
34 GlobalConfig globalConfig = new GlobalConfig();
35 globalConfig.setSqlInjector(new DefaultSqlInjector() {
36 @Override
37 public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
38 List<AbstractMethod> methodList = super.getMethodList(mapperClass);
39 methodList.addAll(myInjectMethods());
40 return methodList;
41 }
42 });
43 return globalConfig;
44 }
45}

6.4 自定义返回类型与结果映射

6.4.1 基本映射

使用@Results@Result进行结果映射:

java
1public interface UserMapper extends BaseMapper<User> {
2 @Results({
3 @Result(property = "id", column = "user_id"),
4 @Result(property = "name", column = "user_name"),
5 @Result(property = "deptName", column = "dept_name")
6 })
7 @Select("SELECT u.id as user_id, u.name as user_name, d.name as dept_name " +
8 "FROM user u LEFT JOIN dept d ON u.dept_id = d.id")
9 List<UserVO> selectUserVOList();
10}

6.4.2 使用XML定义复杂映射

对于更复杂的映射,可以使用XML配置:

xml
1<resultMap id="userDetailMap" type="com.example.vo.UserDetailVO">
2 <id column="id" property="id"/>
3 <result column="name" property="name"/>
4 <result column="email" property="email"/>
5 <!-- 一对一关联 -->
6 <association property="dept" javaType="com.example.entity.Dept">
7 <id column="dept_id" property="id"/>
8 <result column="dept_name" property="name"/>
9 </association>
10 <!-- 一对多关联 -->
11 <collection property="roles" ofType="com.example.entity.Role">
12 <id column="role_id" property="id"/>
13 <result column="role_name" property="name"/>
14 </collection>
15</resultMap>
16
17<select id="getUserDetail" resultMap="userDetailMap">
18 SELECT
19 u.id, u.name, u.email,
20 d.id as dept_id, d.name as dept_name,
21 r.id as role_id, r.name as role_name
22 FROM user u
23 LEFT JOIN dept d ON u.dept_id = d.id
24 LEFT JOIN user_role ur ON u.id = ur.user_id
25 LEFT JOIN role r ON ur.role_id = r.id
26 WHERE u.id = #{userId}
27</select>

6.5 复杂业务场景示例

6.5.1 统计分析查询

java
1public interface OrderMapper extends BaseMapper<Order> {
2 List<Map<String, Object>> getOrderStatsByMonth(@Param("year") Integer year);
3}
xml
1<select id="getOrderStatsByMonth" resultType="java.util.Map">
2 SELECT
3 DATE_FORMAT(create_time, '%Y-%m') as month,
4 COUNT(*) as order_count,
5 SUM(amount) as total_amount,
6 AVG(amount) as avg_amount,
7 MAX(amount) as max_amount,
8 MIN(amount) as min_amount
9 FROM orders
10 WHERE YEAR(create_time) = #{year}
11 GROUP BY DATE_FORMAT(create_time, '%Y-%m')
12 ORDER BY month
13</select>

6.5.2 复杂报表查询

java
1public interface SalesMapper extends BaseMapper<Sales> {
2 List<SalesReportVO> getSalesReport(
3 @Param("startDate") LocalDate startDate,
4 @Param("endDate") LocalDate endDate,
5 @Param("productTypes") List<String> productTypes
6 );
7}
xml
1<select id="getSalesReport" resultType="com.example.vo.SalesReportVO">
2 SELECT
3 p.type as product_type,
4 DATE_FORMAT(s.sale_date, '%Y-%m-%d') as sale_date,
5 SUM(s.quantity) as total_quantity,
6 SUM(s.amount) as total_amount,
7 COUNT(DISTINCT s.customer_id) as customer_count
8 FROM sales s
9 JOIN products p ON s.product_id = p.id
10 <where>
11 <if test="startDate != null">
12 AND s.sale_date >= #{startDate}
13 </if>
14 <if test="endDate != null">
15 AND s.sale_date <= #{endDate}
16 </if>
17 <if test="productTypes != null and productTypes.size() > 0">
18 AND p.type IN
19 <foreach collection="productTypes" item="type" open="(" separator="," close=")">
20 #{type}
21 </foreach>
22 </if>
23 </where>
24 GROUP BY p.type, DATE_FORMAT(s.sale_date, '%Y-%m-%d')
25 ORDER BY p.type, sale_date
26</select>

6.6 复杂条件构造器用法

6.6.1 嵌套查询条件

java
1// 构建复杂嵌套条件
2LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
3 .nested(w -> w
4 .eq(User::getStatus, 1)
5 .or()
6 .eq(User::getStatus, 2)
7 )
8 .and(w -> w
9 .ge(User::getAge, 18)
10 .le(User::getAge, 60)
11 );
12// 生成的SQL: (status = 1 OR status = 2) AND (age >= 18 AND age <= 60)

6.6.2 组合多表查询

java
1// 先用子查询找出部门ID列表,再查询用户
2List<Long> deptIds = deptMapper.selectList(Wrappers.<Dept>lambdaQuery()
3 .like(Dept::getName, "技术")
4 .select(Dept::getId))
5 .stream()
6 .map(Dept::getId)
7 .collect(Collectors.toList());
8
9// 使用查询到的部门ID列表查询用户
10if (!deptIds.isEmpty()) {
11 List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery()
12 .in(User::getDeptId, deptIds));
13}

6.7 性能优化建议

在使用自定义SQL时,以下是一些性能优化建议:

  1. **避免使用SELECT ***:明确指定需要的字段
  2. 合理使用索引:确保JOIN条件和WHERE条件有合适的索引
  3. 分页查询大数据集:必要时使用游标分页而非偏移量分页
  4. 控制IN条件的元素数量:IN条件中元素过多会影响性能
  5. 避免在循环中执行SQL:使用批量操作替代
  6. 合理设置获取数据量:使用LIMIT限制返回记录数
  7. 减少联表数量:必要时拆分复杂查询为多个简单查询

7. 逻辑删除与乐观锁

6.1 逻辑删除

逻辑删除是指在数据库中并不真正删除数据,而是通过修改一个字段的值来标记该数据已被删除。

yaml
1# 全局配置
2mybatis-plus:
3 global-config:
4 db-config:
5 logic-delete-field: deleted
6 logic-delete-value: 1
7 logic-not-delete-value: 0
java
1// 实体类配置
2public class User {
3 @TableLogic
4 private Integer deleted;
5}

6.2 乐观锁

乐观锁是一种并发控制机制,通过版本号或时间戳来实现。

java
1@Configuration
2public class MybatisPlusConfig {
3 @Bean
4 public MybatisPlusInterceptor mybatisPlusInterceptor() {
5 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
6 // 添加乐观锁插件
7 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
8 return interceptor;
9 }
10}
java
1// 实体类配置
2public class User {
3 @Version
4 private Integer version;
5}

7. 插件体系

MyBatis-Plus提供了丰富的插件体系,包括:

7.1 多租户插件

多租户插件可以实现行级别的多租户数据隔离:

java
1interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
2 @Override
3 public Expression getTenantId() {
4 return new LongValue(TenantContext.getCurrentTenantId());
5 }
6
7 @Override
8 public String getTenantIdColumn() {
9 return "tenant_id";
10 }
11}));

7.2 动态表名插件

动态表名插件可以在运行时动态替换SQL中的表名:

java
1DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
2dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
3 if ("user".equals(tableName)) {
4 return "user_" + DateUtil.format(new Date(), "yyyyMM");
5 }
6 return tableName;
7});

7.3 自定义插件开发

可以通过实现InnerInterceptor接口来开发自定义插件:

java
1public class MyCustomInterceptor implements InnerInterceptor {
2 @Override
3 public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
4 // 查询前的处理
5 }
6}

8. 实战应用与最佳实践

8.1 权限系统开发

在权限系统中,可以使用多租户插件实现数据隔离,使用动态表名插件实现分表,使用乐观锁插件实现并发控制。

8.2 大数据量表格优化

对于大数据量表格,可以使用分页插件实现高效分页,使用条件构造器优化查询条件,使用动态表名插件实现分表。

8.3 性能优化建议

  • 使用分页插件进行物理分页
  • 合理使用条件构造器,避免全表扫描
  • 使用批量操作方法,减少数据库交互次数
  • 使用二级缓存,提高查询性能
  • 使用乐观锁,避免悲观锁导致的性能问题

8.3 MyBatis-Plus性能优化全解

在高并发、大数据量场景下,MyBatis-Plus的性能优化显得尤为重要。本节将详细介绍MyBatis-Plus在各种场景下的性能优化策略和最佳实践。

8.3.1 数据库层面优化

8.3.1.1 合理设计索引

MyBatis-Plus虽然简化了开发,但良好的数据库设计仍是性能优化的基础:

java
1// 根据索引字段查询
2// 假设name字段有索引
3LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
4 .eq(User::getName, "张三"); // 会使用name字段索引

索引优化建议:

  • 为WHERE条件、JOIN条件、ORDER BY和GROUP BY字段创建索引
  • 使用复合索引时注意最左前缀原则
  • 避免在索引字段上使用函数,会导致索引失效
8.3.1.2 数据库连接池优化

MyBatis-Plus默认使用HikariCP连接池,可以进行如下优化:

yaml
1spring:
2 datasource:
3 hikari:
4 # 最大连接数
5 maximum-pool-size: 20
6 # 最小空闲连接数
7 minimum-idle: 5
8 # 连接超时时间
9 connection-timeout: 30000
10 # 空闲连接超时时间
11 idle-timeout: 600000
12 # 连接最大生命周期
13 max-lifetime: 1800000

连接池关键参数建议值:

  • maximum-pool-size:CPU核心数 * 2 + 有效磁盘数
  • minimum-idle:与maximum-pool-size相同,避免频繁创建销毁连接
  • connection-timeout:建议30000ms
  • idle-timeout:建议10分钟

8.3.2 SQL优化策略

8.3.2.1 避免全表扫描
java
1// 错误示范:未添加条件,导致全表扫描
2List<User> users = userMapper.selectList(null);
3
4// 正确示范:添加必要的查询条件
5LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
6 .eq(User::getStatus, 1)
7 .orderByDesc(User::getCreateTime)
8 .last("limit 100"); // 限制结果集大小
8.3.2.2 只查询必要字段
java
1// 错误示范:查询所有字段
2List<User> users = userMapper.selectList(wrapper);
3
4// 正确示范:只查询需要的字段
5LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
6 .select(User::getId, User::getName, User::getAge)
7 .eq(User::getStatus, 1);
8.3.2.3 合理使用分页和排序
java
1// 高性能分页查询
2Page<User> page = new Page<>(1, 10);
3// 只有需要排序时才添加
4page.addOrder(OrderItem.desc("create_time"));
5Page<User> userPage = userMapper.selectPage(page, wrapper);

分页优化建议:

  • 控制每页数据量,建议10-100条
  • 避免跳过大量数据(如查询第1000页)
  • 大数据量分页考虑使用游标分页(利用上次查询最后一条记录的ID)
java
1// 游标分页示例
2Long lastId = 0L;
3Integer pageSize = 100;
4List<User> result;
5
6do {
7 result = userMapper.selectList(Wrappers.<User>lambdaQuery()
8 .gt(User::getId, lastId)
9 .orderByAsc(User::getId)
10 .last("limit " + pageSize));
11
12 // 处理业务逻辑
13
14 // 更新lastId为当前查询结果中最后一条记录的ID
15 if (!result.isEmpty()) {
16 lastId = result.get(result.size() - 1).getId();
17 }
18} while (result.size() == pageSize);

8.3.3 批量操作优化

8.3.3.1 使用批量插入
java
1// 单条插入多次 - 性能较差
2for (User user : userList) {
3 userMapper.insert(user);
4}
5
6// 批量插入 - 性能更好
7userService.saveBatch(userList);

批量操作建议:

  • 控制每批次数据量,建议500-1000条
  • 超大批量数据考虑分批处理
  • 根据数据库类型选择合适的批处理策略
java
1// 分批处理示例
2List<User> totalList = ... // 10000条数据
3int batchSize = 500;
4
5for (int i = 0; i < totalList.size(); i += batchSize) {
6 int endIndex = Math.min(i + batchSize, totalList.size());
7 List<User> subList = totalList.subList(i, endIndex);
8 userService.saveBatch(subList);
9}
8.3.3.2 自定义批量操作

对于特殊的批量操作需求,可以使用自定义SQL:

java
1// 在Mapper中定义批量插入方法
2@Mapper
3public interface UserMapper extends BaseMapper<User> {
4 @Insert("<script>" +
5 "INSERT INTO user(name, age, email) VALUES " +
6 "<foreach collection='userList' item='user' separator=','>" +
7 "(#{user.name}, #{user.age}, #{user.email})" +
8 "</foreach>" +
9 "</script>")
10 int insertBatchSomeColumn(@Param("userList") List<User> userList);
11}

8.3.4 缓存优化

8.3.4.1 MyBatis二级缓存

MyBatis-Plus支持MyBatis的二级缓存机制:

java
1// 开启二级缓存的配置
2@CacheNamespace(implementation = MybatisRedisCache.class, eviction = MybatisRedisCache.class)
3public interface UserMapper extends BaseMapper<User> {
4 // ...
5}

自定义Redis缓存实现:

java
1public class MybatisRedisCache implements Cache {
2 private final RedisTemplate<String, Object> redisTemplate;
3 private final String id;
4
5 // 实现Cache接口的各个方法
6 // ...
7}
8.3.4.2 结合Spring Cache
java
1@Service
2@CacheConfig(cacheNames = "userCache")
3public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
4
5 @Cacheable(key = "#id")
6 @Override
7 public User getById(Serializable id) {
8 return super.getById(id);
9 }
10
11 @CacheEvict(key = "#user.id")
12 @Override
13 public boolean updateById(User user) {
14 return super.updateById(user);
15 }
16
17 @CacheEvict(allEntries = true)
18 public void clearCache() {
19 // 清除所有缓存
20 }
21}

8.3.5 插件优化

8.3.5.1 SQL性能分析插件
java
1@Bean
2public MybatisPlusInterceptor mybatisPlusInterceptor() {
3 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
4
5 // 性能分析插件,生产环境建议关闭
6 if (profile.equals("dev") || profile.equals("test")) {
7 interceptor.addInnerInterceptor(new PerformanceInterceptor()
8 .setMaxTime(100) // 设置最大执行时间,超过则抛出异常
9 .setFormat(true)); // 格式化SQL输出
10 }
11
12 return interceptor;
13}
8.3.5.2 动态表名插件优化分表
java
1@Bean
2public MybatisPlusInterceptor mybatisPlusInterceptor() {
3 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
4
5 // 动态表名插件,用于分表
6 DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
7 dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
8 // 根据业务动态返回真实表名
9 if ("order".equals(tableName)) {
10 return "order_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
11 }
12 return tableName;
13 });
14 interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
15
16 return interceptor;
17}

8.3.6 性能测试与基准

以下是在不同场景下MyBatis-Plus与原生MyBatis的性能比较数据(基于10万条记录测试):

操作类型MyBatis (ms)MyBatis-Plus (ms)性能提升
单条查询5.25.5-5.8%
列表查询(10条)8.38.7-4.8%
分页查询(100条)15.712.3+21.7%
单条插入4.85.1-6.3%
批量插入(100条)82.542.1+49.0%
单条更新4.54.8-6.7%
条件更新12.39.7+21.1%
单条删除4.24.5-7.1%
条件删除11.59.2+20.0%

测试结论:

  1. 单条操作:MyBatis-Plus有约5%-7%的性能损耗(可忽略)
  2. 批量操作:MyBatis-Plus性能提升显著,约20%-50%
  3. 分页查询:MyBatis-Plus优化明显,约20%左右

8.3.7 实际案例性能优化

以下是某电商订单系统的实际优化案例:

优化前:

  • 每秒订单查询处理:约200单
  • 数据库CPU负载:75%
  • 平均响应时间:350ms

优化措施:

  1. 引入二级缓存,缓存热点数据
  2. 优化查询,只查询必要字段
  3. 使用批量操作替代循环单条操作
  4. 添加合适的索引
  5. 使用分库分表策略处理大表

优化后:

  • 每秒订单查询处理:提升至800单
  • 数据库CPU负载:降至30%
  • 平均响应时间:降至80ms

8.3.8 性能优化最佳实践总结

  1. 数据库设计层面

    • 合理设计表结构和字段类型
    • 为常用查询条件创建索引
    • 大表考虑分库分表
  2. SQL优化层面

    • 只查询必要字段
    • 避免全表扫描
    • 合理使用分页,控制结果集大小
    • 使用批量操作替代循环单条操作
  3. 缓存层面

    • 合理使用二级缓存
    • 热点数据使用本地缓存
    • 频繁变化的数据慎用缓存
  4. 代码层面

    • 合理使用条件构造器
    • 避免循环中进行数据库操作
    • 大数据量操作采用分批策略
    • 使用多线程处理并行任务
  5. 监控与优化

    • 使用性能分析插件找出慢SQL
    • 定期检查SQL执行计划
    • 根据业务场景调整数据库参数
    • 建立性能基准,持续监控与优化

9. 总结

MyBatis-Plus作为MyBatis的增强工具,提供了丰富的功能和插件,大大简化了开发工作,提高了开发效率。通过本文的学习,你应该已经掌握了MyBatis-Plus的核心特性和使用方法,可以在实际项目中灵活应用。

10. 面试题精选

10.1 MyBatis-Plus的核心特性有哪些?

  • 通用CRUD操作
  • 条件构造器
  • 分页插件
  • 代码生成器
  • 逻辑删除
  • 乐观锁
  • 多租户
  • 动态表名

10.2 MyBatis-Plus如何防止全表更新与删除?

MyBatis-Plus提供了防止全表更新与删除的功能,可以通过配置update-strategynot_empty来实现。当没有WHERE条件的更新或删除操作将会被阻止。

10.3 MyBatis-Plus的条件构造器有哪些优势?

  • 链式调用,代码简洁
  • 类型安全(LambdaQueryWrapper)
  • 动态条件,根据参数值决定是否加入条件
  • 支持复杂条件组合,如嵌套查询、子查询等
  • 防SQL注入
MyBatis-Plus学习要点
  1. 掌握核心概念:理解BaseMapper、IService等核心接口
  2. 熟练使用条件构造器:掌握各种查询条件的构造方法
  3. 合理使用代码生成器:提高开发效率,但要理解生成的代码
  4. 关注性能优化:学会分析和优化SQL性能
  5. 持续学习:关注MyBatis-Plus新版本特性和最佳实践

11. 故障排查与问题解决指南

在使用MyBatis-Plus过程中,可能会遇到各种问题。本节将提供常见问题的排查思路和解决方案,帮助开发者快速定位和解决问题。

11.1 常见错误及解决方案

11.1.1 无法启动应用

问题表现:应用启动失败,控制台出现错误。

常见原因及解决方案

  1. 数据库连接问题

    1java.net.ConnectException: Connection refused

    解决方案

    • 检查数据库服务是否启动
    • 确认数据库连接URL、用户名和密码是否正确
    • 检查防火墙是否阻止了数据库连接
  2. 依赖冲突

    1java.lang.NoSuchMethodError: xxx

    解决方案

    • 检查pom.xml中是否存在冲突的依赖版本
    • 使用mvn dependency:tree命令查看依赖树,找出冲突
    • 通过<exclusion>排除冲突依赖
  3. 配置错误

    1org.springframework.beans.factory.BeanCreationException: Error creating bean

    解决方案

    • 检查配置文件中的属性名是否正确
    • 确认必要的配置是否缺失
    • 查看是否有配置冲突

11.1.2 查询结果不符合预期

问题表现:查询结果为空或与预期不符。

常见原因及解决方案

  1. 列名与字段名不匹配

    解决方案

    • 检查实体类属性名与表字段名的映射关系
    • 使用@TableField注解指定映射关系
    java
    1@TableField("column_name")
    2private String fieldName;
  2. 驼峰命名转换问题

    解决方案

    • 确认是否启用了驼峰命名转换
    yaml
    1mybatis-plus:
    2 configuration:
    3 map-underscore-to-camel-case: true
  3. 逻辑删除字段影响查询

    解决方案

    • 确认逻辑删除是否正确配置
    • 检查查询条件是否考虑了逻辑删除字段

11.1.3 插件问题

问题表现:插件功能不生效。

常见原因及解决方案

  1. 分页插件不生效

    解决方案

    • 确认插件是否正确注册
    • 检查是否使用了正确的Page对象
    • 排查是否有其他拦截器干扰
  2. 乐观锁插件问题

    解决方案

    • 确认实体类中是否正确使用@Version注解
    • 检查更新时是否包含version字段
    • 并发更新时version字段是否正确递增
  3. 多租户插件问题

    解决方案

    • 确认TenantLineHandler实现是否正确
    • 检查忽略表配置是否正确
    • 排查SQL执行时租户ID是否正确获取

11.2 SQL执行异常

11.2.1 SQL语法错误

问题表现:执行SQL时报语法错误。

常见原因及解决方案

  1. 字段名或表名使用了关键字

    解决方案

    • 使用反引号(`)或方言特定的转义符号包围关键字
    java
    1@TableField("`order`")
    2private String order;
  2. 条件构造器使用不当

    解决方案

    • 使用日志查看实际执行的SQL
    • 修正条件构造器的使用方式
    • 考虑使用last()方法添加自定义SQL片段
  3. 动态SQL拼接错误

    解决方案

    • 检查XML文件中动态SQL的逻辑
    • 确保标签闭合正确
    • 使用调试模式检查最终生成的SQL

11.2.2 数据库特定错误

问题表现:数据库返回特定错误码。

常见原因及解决方案

  1. 唯一约束冲突

    1Duplicate entry 'xxx' for key 'xxx'

    解决方案

    • 在插入前检查数据是否已存在
    • 使用saveOrUpdate方法代替insert
    • 考虑使用saveOrUpdateBatch批量操作
  2. 外键约束问题

    解决方案

    • 确保引用的外键数据已存在
    • 检查操作顺序是否符合外键约束
    • 考虑使用事务保证数据一致性

11.3 性能问题

11.3.1 查询性能低下

问题表现:查询执行时间长,CPU使用率高。

常见原因及解决方案

  1. 缺少必要索引

    解决方案

    • 分析SQL执行计划,找出全表扫描的操作
    • 为常用查询条件添加合适的索引
    • 优化复合索引的字段顺序
  2. 查询返回数据量过大

    解决方案

    • 使用分页查询限制返回数据量
    • 只查询必要的字段而非所有字段
    • 添加合适的查询条件缩小结果集
  3. N+1查询问题

    解决方案

    • 使用联表查询替代多次单表查询
    • 利用批量查询代替循环单条查询
    • 考虑使用缓存减少重复查询

11.3.2 更新性能问题

问题表现:批量更新或插入性能差。

常见原因及解决方案

  1. 循环单条操作

    解决方案

    • 使用批量方法如saveBatchupdateBatchById
    • 合理设置批次大小(通常500-1000条为宜)
    • 对超大数据量考虑分批处理
  2. 事务设置不当

    解决方案

    • 确保批量操作在单一事务中执行
    • 调整事务隔离级别
    • 优化事务超时设置

11.4 调试技巧

11.4.1 开启SQL日志

方法一:配置文件开启

yaml
1mybatis-plus:
2 configuration:
3 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

方法二:使用P6Spy监控SQL

xml
1<dependency>
2 <groupId>p6spy</groupId>
3 <artifactId>p6spy</artifactId>
4 <version>3.9.1</version>
5</dependency>
yaml
1spring:
2 datasource:
3 driver-class-name: com.p6spy.engine.spy.P6SpyDriver
4 url: jdbc:p6spy:mysql://localhost:3306/mydatabase

11.4.2 使用性能分析插件

java
1@Bean
2@Profile({"dev","test"}) // 仅在开发和测试环境开启
3public MybatisPlusInterceptor mybatisPlusInterceptor() {
4 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
5 interceptor.addInnerInterceptor(new PerformanceInterceptor()
6 .setMaxTime(500) // 超过500ms则报告
7 .setFormat(true)); // 格式化SQL输出
8 return interceptor;
9}

11.4.3 调试条件构造器

在复杂查询构建过程中,可以使用以下方法验证条件构造器:

java
1// 构建查询条件
2LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
3 .eq(User::getStatus, 1)
4 .like(User::getName, "张");
5
6// 获取SQL片段进行调试
7String sqlSegment = wrapper.getSqlSegment();
8log.debug("SQL条件片段: {}", sqlSegment);

11.5 常见异常列表及解决方法

异常类型错误信息可能原因解决方案
MybatisPlusExceptionTable info has not exists找不到对应的表检查表名、实体类@TableName注解
BadSqlGrammarExceptionYou have an error in your SQL syntaxSQL语法错误检查SQL语句、字段名、表名
BindingExceptionParameter 'xxx' not found参数绑定错误检查@Param注解、XML中的参数名
TooManyResultsExceptionToo many results期望一条结果,返回多条调整查询条件使结果唯一
DuplicateKeyExceptionDuplicate entry唯一键或主键冲突检查插入数据的唯一性
DataAccessExceptionConnection refused数据库连接问题检查数据库连接配置
QueryTimeoutExceptionQuery timeout查询超时优化SQL、添加索引、调整超时设置

11.6 版本升级问题处理

11.6.1 从2.x升级到3.x

主要变化

  • 移除了实体属性ID_WORKERID_WORKER_STR
  • 调整了分页插件配置方式
  • 修改了批量操作接口

解决方案

  1. 将IdType.ID_WORKER替换为IdType.ASSIGN_ID
  2. 按新方式配置分页插件:
java
1@Bean
2public MybatisPlusInterceptor mybatisPlusInterceptor() {
3 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
4 interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
5 return interceptor;
6}
  1. 更新批量操作的调用方式和参数

11.6.2 从3.x早期版本升级到3.5+

主要变化

  • 插件体系改为InnerInterceptor
  • 条件构造器API调整
  • 增强了Lambda表达式支持

解决方案

  1. 更新插件配置方式
  2. 检查并调整条件构造器的使用方式
  3. 利用新特性优化代码

11.7 问题排查流程

遇到问题时,建议按以下流程排查:

  1. 查看日志:分析错误日志,定位异常位置
  2. 检查配置:验证相关配置项是否正确
  3. 查看SQL:使用日志或监控工具查看实际执行的SQL
  4. 单元测试:编写简单测试验证功能点
  5. 调试跟踪:使用断点调试跟踪代码执行流程
  6. 环境对比:如果可能,对比不同环境下的行为差异
  7. 查阅文档:参考官方文档和更新日志
  8. 搜索社区:在GitHub Issues、Stack Overflow等平台搜索类似问题

11.8 常见问题FAQ

Q1: 使用MyBatis-Plus后,自定义的XML映射文件不生效?

A1: 检查以下几点:

  • 确保XML文件在mapper-locations配置路径下
  • 确认Mapper接口与XML的命名空间一致
  • 检查方法签名与XML中的ID是否一致
  • 确认XML文件已被正确打包进classpath

Q2: 如何在一个Service中使用多个Mapper?

A2: 通过依赖注入使用多个Mapper:

java
1@Service
2public class ComplexServiceImpl extends ServiceImpl<UserMapper, User> implements ComplexService {
3 @Autowired
4 private OrderMapper orderMapper;
5
6 @Autowired
7 private ProductMapper productMapper;
8
9 public void complexOperation() {
10 // 使用this.baseMapper操作User
11 List<User> users = this.baseMapper.selectList(null);
12
13 // 使用注入的其他Mapper
14 List<Order> orders = orderMapper.selectList(null);
15 List<Product> products = productMapper.selectList(null);
16 }
17}

Q3: 条件构造器中如何实现"OR"查询?

A3: 使用or()方法或嵌套查询:

java
1// 方式1:使用or()方法
2QueryWrapper<User> wrapper = new QueryWrapper<>();
3wrapper.eq("status", 1)
4 .or()
5 .eq("status", 2);
6
7// 方式2:使用嵌套查询
8wrapper.nested(w -> w.eq("status", 1).or().eq("status", 2));

Q4: 如何处理字段名与Java关键字冲突?

A4: 使用@TableField注解指定映射关系:

java
1@TableField("`order`")
2private String order;
3
4@TableField("`default`")
5private String default;

11.9 故障排除工具清单

  1. 日志工具:SLF4J + Logback配置
  2. SQL监控:P6Spy、PerformanceInterceptor
  3. 数据库分析:MySQL Explain、执行计划分析
  4. 性能分析:JProfiler、Arthas、VisualVM
  5. 测试工具:JUnit、Mockito、Spring Test

通过本节的指南,你应该能够更快地定位和解决MyBatis-Plus使用过程中的常见问题,提高开发效率和系统稳定性。

参与讨论