MyBatis-Plus 开发指北

MyBatis-Plus 开发指北

📖 内容概览

分享MyBatis-Plus的使用经验,涵盖从基础配置到高级特性的完整知识体系。通过实际业务场景(电商、仓储、财务)的示例,帮助开发者快速掌握MyBatis-Plus的核心功能。

🚀 快速开始

第一步:添加依赖

pom.xml 中添加MyBatis-Plus依赖:

xml 复制代码
<dependencies>
    <!-- MyBatis-Plus 核心依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    
    <!-- 数据库连接池 -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
</dependencies>

第二步:配置数据源

application.yml 中配置数据库连接:

yaml 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000

# MyBatis-Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  mapper-locations: classpath*:/mapper/**/*.xml

第三步:创建实体类

创建用户实体类:

java 复制代码
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    @TableField("user_name")
    private String username;
    
    private String email;
    
    private Integer age;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableLogic
    private Integer deleted;
}

第四步:创建Mapper接口

创建用户Mapper接口:

java 复制代码
@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承BaseMapper后自动拥有CRUD方法
    // 可以添加自定义方法
    List<User> selectUsersByAge(@Param("minAge") Integer minAge, @Param("maxAge") Integer maxAge);
}

第五步:创建Service层

创建用户Service:

java 复制代码
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
    // 继承ServiceImpl后自动拥有CRUD方法
    
    public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {
        return baseMapper.selectUsersByAge(minAge, maxAge);
    }
    
    public Page<User> getUsersWithPagination(Page<User> page, String keyword) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(keyword)) {
            wrapper.like(User::getUsername, keyword)
                   .or()
                   .like(User::getEmail, keyword);
        }
        return page(page, wrapper);
    }
}

第六步:创建Controller

创建用户Controller:

java 复制代码
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getById(id);
    }
    
    @PostMapping
    public boolean createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    @PutMapping("/{id}")
    public boolean updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return userService.updateById(user);
    }
    
    @DeleteMapping("/{id}")
    public boolean deleteUser(@PathVariable Long id) {
        return userService.removeById(id);
    }
    
    @GetMapping
    public Page<User> getUsers(
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) String keyword) {
        Page<User> page = new Page<>(current, size);
        return userService.getUsersWithPagination(page, keyword);
    }
}

第七步:测试接口

启动应用并测试:

bash 复制代码
# 创建用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "张三",
    "email": "zhangsan@example.com",
    "age": 25
  }'

# 获取用户列表
curl http://localhost:8080/api/users?current=1&size=10&keyword=张

# 更新用户
curl -X PUT http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{
    "username": "张三丰",
    "email": "zhangsanfeng@example.com",
    "age": 30
  }'

# 删除用户
curl -X DELETE http://localhost:8080/api/users/1

完成! 现在您已经成功使用MyBatis-Plus进行数据库操作。接下来可以继续阅读详细的技术内容。


1. MyBatis-Plus 简介

1.1 什么是MyBatis-Plus

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

1.2 核心特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:启动即会自动注入基本CRUD,性能基本无损耗,直接面向对象操作
  • 强大的CRUD操作:内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作
  • 支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达4种主键策略(包含分布式唯一ID生成器-Sequence),可自由配置
  • 支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作
  • 支持自定义全局通用操作:支持全局通用方法注入(Write once, use anywhere)
  • 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Service、Controller层代码
  • 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
  • 分页插件支持多种数据库:支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer等多种数据库
  • 内置性能分析插件:可输出SQL语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表delete、update操作智能分析阻断,也可自定义拦截规则,预防误操作

1.3 适用场景

  • 快速开发:新项目快速搭建,减少重复代码编写
  • CRUD操作:单表增删改查操作频繁的业务场景
  • 分页查询:需要分页展示数据的场景
  • 条件查询:复杂的动态条件查询
  • 批量操作:批量插入、更新、删除操作

2. 核心注解详解

2.1 实体类注解

@TableName

指定实体类对应的数据库表名。

java 复制代码
@TableName("sys_user")
public class User {
    // 实体类字段
}

参数说明

  • value:数据库表名
  • schema:数据库schema
  • resultMap:XML中resultMap的id
@TableId

指定主键字段。

java 复制代码
@TableId(type = IdType.AUTO)
private Long id;

主键策略

  • AUTO:数据库自增
  • NONE:无状态,需要手动设置
  • INPUT:手动输入
  • ASSIGN_ID:分配ID(雪花算法)
  • ASSIGN_UUID:分配UUID
@TableField

指定字段映射。

java 复制代码
@TableField("user_name")
private String username;

@TableField(exist = false)
private String tempField;

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

参数说明

  • value:数据库字段名
  • exist:是否为数据库表字段
  • fill:字段自动填充策略
  • select:是否查询该字段
  • update:是否更新该字段
@TableLogic

逻辑删除字段。

java 复制代码
@TableLogic
private Integer deleted;

配置示例

yaml 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

2.2 条件构造器注解

@SqlParser

SQL解析注解,用于过滤SQL。

java 复制代码
@SqlParser(filter = true)
public class UserMapper extends BaseMapper<User> {
    // 自定义SQL方法
}

3. 核心接口与类

3.1 BaseMapper接口

MyBatis-Plus提供的通用Mapper接口,包含基本的CRUD操作。

java 复制代码
public interface BaseMapper<T> extends Mapper<T> {
    // 插入
    int insert(T entity);
    
    // 根据ID删除
    int deleteById(Serializable id);
    
    // 根据条件删除
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根据ID更新
    int updateById(@Param(Constants.ENTITY) T entity);
    
    // 根据条件更新
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
    
    // 根据ID查询
    T selectById(Serializable id);
    
    // 根据条件查询单个
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根据条件查询列表
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根据条件查询分页
    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根据条件查询总数
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

3.2 IService接口

通用Service接口,提供业务层常用方法。

java 复制代码
public interface IService<T> {
    // 保存
    boolean save(T entity);
    
    // 批量保存
    boolean saveBatch(Collection<T> entityList);
    
    // 根据ID删除
    boolean removeById(Serializable id);
    
    // 根据条件删除
    boolean remove(Wrapper<T> queryWrapper);
    
    // 根据ID更新
    boolean updateById(T entity);
    
    // 根据条件更新
    boolean update(Wrapper<T> updateWrapper);
    
    // 根据ID查询
    T getById(Serializable id);
    
    // 根据条件查询单个
    T getOne(Wrapper<T> queryWrapper);
    
    // 根据条件查询列表
    List<T> list(Wrapper<T> queryWrapper);
    
    // 分页查询
    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
    
    // 根据条件查询总数
    int count(Wrapper<T> queryWrapper);
}

3.3 ServiceImpl类

IService接口的实现类。

java 复制代码
@Service
public class UserService extends ServiceImpl<UserMapper, User> implements IUserService {
    // 可以添加自定义业务方法
}

4. 条件构造器

4.1 QueryWrapper

查询条件构造器,用于构建WHERE条件。

java 复制代码
// 基础查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "张三")
       .gt("age", 18)
       .like("email", "@example.com")
       .orderByDesc("create_time");

List<User> users = userMapper.selectList(wrapper);

4.2 LambdaQueryWrapper

Lambda表达式查询条件构造器,类型安全。

java 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, "张三")
       .gt(User::getAge, 18)
       .like(User::getEmail, "@example.com")
       .orderByDesc(User::getCreateTime);

List<User> users = userMapper.selectList(wrapper);

4.3 UpdateWrapper

更新条件构造器,用于构建UPDATE语句。

java 复制代码
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("username", "张三")
       .set("age", 25)
       .set("email", "newemail@example.com");

userMapper.update(null, wrapper);

4.4 LambdaUpdateWrapper

Lambda表达式更新条件构造器。

java 复制代码
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getUsername, "张三")
       .set(User::getAge, 25)
       .set(User::getEmail, "newemail@example.com");

userMapper.update(null, wrapper);

4.5 常用条件方法

比较条件
java 复制代码
// 等于
wrapper.eq("age", 25);
// 不等于
wrapper.ne("age", 25);
// 大于
wrapper.gt("age", 18);
// 大于等于
wrapper.ge("age", 18);
// 小于
wrapper.lt("age", 65);
// 小于等于
wrapper.le("age", 65);
模糊查询
java 复制代码
// LIKE '%value%'
wrapper.like("username", "张");
// LIKE 'value%'
wrapper.likeRight("username", "张");
// LIKE '%value'
wrapper.likeLeft("username", "三");
// NOT LIKE '%value%'
wrapper.notLike("username", "张");
范围查询
java 复制代码
// IN
wrapper.in("age", Arrays.asList(18, 19, 20));
// NOT IN
wrapper.notIn("age", Arrays.asList(18, 19, 20));
// BETWEEN
wrapper.between("age", 18, 65);
// NOT BETWEEN
wrapper.notBetween("age", 18, 65);
空值判断
java 复制代码
// IS NULL
wrapper.isNull("email");
// IS NOT NULL
wrapper.isNotNull("email");
排序
java 复制代码
// 升序
wrapper.orderByAsc("age");
// 降序
wrapper.orderByDesc("create_time");
// 多字段排序
wrapper.orderByAsc("age", "create_time");
分组
java 复制代码
// GROUP BY
wrapper.groupBy("age");
// HAVING
wrapper.having("COUNT(*) > 1");

5. 分页插件

5.1 配置分页插件

java 复制代码
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        
        return interceptor;
    }
}

5.2 使用分页查询

java 复制代码
// 创建分页对象
Page<User> page = new Page<>(1, 10);

// 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getUsername, "张");

// 执行分页查询
Page<User> result = userMapper.selectPage(page, wrapper);

// 获取结果
List<User> records = result.getRecords();
long total = result.getTotal();
long current = result.getCurrent();
long size = result.getSize();

5.3 自定义分页查询

java 复制代码
// 在Mapper中定义自定义分页方法
@Mapper
public interface UserMapper extends BaseMapper<User> {
    IPage<User> selectUsersWithRole(IPage<User> page, @Param("roleId") Long roleId);
}

// 在XML中实现
<select id="selectUsersWithRole" resultType="com.example.entity.User">
    SELECT u.*, r.role_name 
    FROM sys_user u 
    LEFT JOIN sys_user_role ur ON u.id = ur.user_id 
    LEFT JOIN sys_role r ON ur.role_id = r.id 
    WHERE r.id = #{roleId}
</select>

6. 代码生成器

6.1 配置代码生成器

java 复制代码
@Component
public class CodeGenerator {
    
    public void generateCode() {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("Your Name");
        gc.setOpen(false);
        gc.setFileOverride(true);
        gc.setServiceName("%sService");
        gc.setMapperName("%sMapper");
        mpg.setGlobalConfig(gc);
        
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);
        
        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("sys");
        pc.setParent("com.example");
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setController("controller");
        mpg.setPackageInfo(pc);
        
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setInclude("sys_user", "sys_role", "sys_menu");
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("sys_");
        mpg.setStrategy(strategy);
        
        // 执行生成
        mpg.execute();
    }
}

6.2 使用Maven插件生成

xml 复制代码
<plugin>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3.1</version>
</plugin>

7. 自动填充

7.1 配置自动填充处理器

java 复制代码
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public 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, getCurrentUserId());
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUserId());
    }
    
    private String getCurrentUserId() {
        // 获取当前用户ID的逻辑
        return "admin";
    }
}

7.2 在实体类中使用自动填充

java 复制代码
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;
}

8. 性能分析插件

8.1 配置性能分析插件

java 复制代码
@Configuration
public class MybatisPlusConfig {
    
    @Bean
    @Profile({"dev", "test"}) // 只在开发环境和测试环境启用
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor interceptor = new PerformanceInterceptor();
        interceptor.setMaxTime(1000); // 设置SQL执行的最大时间,单位:ms
        interceptor.setFormat(true); // 是否格式化SQL
        return interceptor;
    }
}

8.2 配置SQL日志输出

yaml 复制代码
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

9. 多数据源配置

9.1 配置多数据源

java 复制代码
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {
    
    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
        return bean.getObject();
    }
    
    @Bean(name = "primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

9.2 使用多数据源

java 复制代码
@Service
public class UserService {
    
    @Autowired
    @Qualifier("primaryUserMapper")
    private UserMapper primaryUserMapper;
    
    @Autowired
    @Qualifier("secondaryUserMapper")
    private UserMapper secondaryUserMapper;
    
    public List<User> getUsersFromPrimary() {
        return primaryUserMapper.selectList(null);
    }
    
    public List<User> getUsersFromSecondary() {
        return secondaryUserMapper.selectList(null);
    }
}

10. 业务场景示例

10.1 电商场景:商品管理

java 复制代码
// 商品实体
@Data
@TableName("product")
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private BigDecimal price;
    
    private Integer stock;
    
    private String category;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableLogic
    private Integer deleted;
}

// 商品Service
@Service
public class ProductService extends ServiceImpl<ProductMapper, Product> {
    
    // 根据分类查询商品
    public List<Product> getProductsByCategory(String category) {
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Product::getCategory, category)
               .gt(Product::getStock, 0)
               .orderByDesc(Product::getCreateTime);
        return list(wrapper);
    }
    
    // 分页查询商品
    public Page<Product> getProductsWithPagination(Page<Product> page, String keyword, String category) {
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(keyword)) {
            wrapper.like(Product::getName, keyword);
        }
        if (StringUtils.isNotBlank(category)) {
            wrapper.eq(Product::getCategory, category);
        }
        wrapper.orderByDesc(Product::getCreateTime);
        return page(page, wrapper);
    }
    
    // 批量更新库存
    public boolean updateStock(List<Long> productIds, Integer stock) {
        LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<>();
        wrapper.in(Product::getId, productIds)
               .set(Product::getStock, stock);
        return update(wrapper);
    }
}

10.2 仓储场景:库存管理

java 复制代码
// 库存实体
@Data
@TableName("inventory")
public class Inventory {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String productCode;
    
    private String warehouseCode;
    
    private Integer quantity;
    
    private Integer reservedQuantity;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

// 库存Service
@Service
public class InventoryService extends ServiceImpl<InventoryMapper, Inventory> {
    
    // 根据商品编码查询库存
    public List<Inventory> getInventoryByProductCode(String productCode) {
        LambdaQueryWrapper<Inventory> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Inventory::getProductCode, productCode)
               .gt(Inventory::getQuantity, 0)
               .orderByDesc(Inventory::getQuantity);
        return list(wrapper);
    }
    
    // 库存预警查询
    public List<Inventory> getLowStockInventory(Integer threshold) {
        LambdaQueryWrapper<Inventory> wrapper = new LambdaQueryWrapper<>();
        wrapper.le(Inventory::getQuantity, threshold)
               .orderByAsc(Inventory::getQuantity);
        return list(wrapper);
    }
    
    // 批量更新库存
    public boolean updateInventory(List<Inventory> inventoryList) {
        return updateBatchById(inventoryList);
    }
}

10.3 财务场景:订单管理

java 复制代码
// 订单实体
@Data
@TableName("order")
public class Order {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String orderNo;
    
    private Long userId;
    
    private BigDecimal totalAmount;
    
    private String status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

// 订单Service
@Service
public class OrderService extends ServiceImpl<OrderMapper, Order> {
    
    // 根据用户ID查询订单
    public List<Order> getOrdersByUserId(Long userId) {
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Order::getUserId, userId)
               .orderByDesc(Order::getCreateTime);
        return list(wrapper);
    }
    
    // 根据状态查询订单
    public List<Order> getOrdersByStatus(String status) {
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Order::getStatus, status)
               .orderByDesc(Order::getCreateTime);
        return list(wrapper);
    }
    
    // 统计订单金额
    public BigDecimal getTotalAmountByDateRange(LocalDateTime startTime, LocalDateTime endTime) {
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
        wrapper.between(Order::getCreateTime, startTime, endTime)
               .eq(Order::getStatus, "COMPLETED");
        
        List<Order> orders = list(wrapper);
        return orders.stream()
                .map(Order::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

11. 最佳实践

11.1 实体类设计

java 复制代码
// 推荐:使用Lombok简化代码
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    @TableField("user_name")
    private String username;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableLogic
    private Integer deleted;
}

// 不推荐:手动编写getter/setter
public class User {
    private Long id;
    private String username;
    
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    // ... 其他getter/setter
}

11.2 条件构造器使用

java 复制代码
// 推荐:使用LambdaQueryWrapper,类型安全
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, "张三")
       .gt(User::getAge, 18)
       .orderByDesc(User::getCreateTime);

// 不推荐:使用QueryWrapper,容易写错字段名
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "张三")
       .gt("age", 18)
       .orderByDesc("create_time");

11.3 分页查询

java 复制代码
// 推荐:使用Page对象进行分页
Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getUsername, "张");
Page<User> result = userMapper.selectPage(page, wrapper);

// 不推荐:手动计算分页参数
int offset = (current - 1) * size;
List<User> users = userMapper.selectUsersWithLimit(offset, size);

11.4 批量操作

java 复制代码
// 推荐:使用MyBatis-Plus提供的批量方法
List<User> users = Arrays.asList(user1, user2, user3);
boolean result = userService.saveBatch(users);

// 不推荐:循环调用单个保存方法
for (User user : users) {
    userService.save(user);
}

11.5 事务管理

java 复制代码
// 推荐:在Service层使用@Transactional
@Service
@Transactional
public class UserService extends ServiceImpl<UserMapper, User> {
    
    public boolean createUserWithRole(User user, Long roleId) {
        // 保存用户
        boolean userSaved = save(user);
        
        // 分配角色
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(roleId);
        boolean roleAssigned = userRoleService.save(userRole);
        
        return userSaved && roleAssigned;
    }
}

12. 常见问题与解决方案

12.1 字段映射问题

问题:数据库字段名与实体类字段名不匹配

java 复制代码
// 解决方案:使用@TableField注解
@TableField("user_name")
private String username;

12.2 逻辑删除问题

问题:删除数据时希望逻辑删除而不是物理删除

java 复制代码
// 解决方案:使用@TableLogic注解
@TableLogic
private Integer deleted;

// 配置逻辑删除值
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

12.3 分页查询问题

问题:分页查询没有生效

java 复制代码
// 解决方案:确保配置了分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
}

12.4 自动填充问题

问题:自动填充字段没有生效

java 复制代码
// 解决方案:实现MetaObjectHandler接口
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

12.5 性能问题

问题:查询性能较慢

java 复制代码
// 解决方案:
// 1. 添加索引
// 2. 使用分页查询
// 3. 避免使用select *,指定需要的字段
// 4. 使用缓存

13. 总结

MyBatis-Plus是一个功能强大的MyBatis增强工具,通过本指南的学习,您应该能够:

  1. 快速上手:掌握MyBatis-Plus的基本配置和使用方法
  2. 核心功能:熟练使用条件构造器、分页插件、代码生成器等核心功能
  3. 最佳实践:了解在实际项目中的最佳实践和注意事项
  4. 问题解决:能够解决常见的配置和使用问题

MyBatis-Plus大大简化了MyBatis的使用,提高了开发效率,是现代Java项目中的优秀选择。建议在实际项目中逐步引入,根据业务需求选择合适的特性使用。


持续学习建议

  • 关注MyBatis-Plus官方文档的更新
  • 参与开源社区讨论,分享使用经验
  • 在实际项目中不断实践和优化
  • 学习相关的数据库优化知识

14. 高阶应用(含业务场景)

本章节聚焦企业级落地的常用"增强能力"。每条都给出:适用场景 → 规约要点 → 最小示例 → 业务说明(电商/仓储/财务)。

14.1 多租户隔离(TenantLineInnerInterceptor)

  • 适用:SaaS 多租户,一库多租按租户列隔离(如 tenant_id)。
  • 规约:
    • 全局拦截器统一追加 tenant_id = ? 条件;
    • 白名单表(如公共字典)跳过租户拼接;
    • 不要在SQL手写租户条件,避免遗漏。
java 复制代码
@Configuration
public class MybatisPlusTenantConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        MybatisPlusInterceptor i = new MybatisPlusInterceptor();
        i.addInnerInterceptor(new TenantLineInnerInterceptor(() -> new LongValue(getTenantId())));
        i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return i;
    }
    private Long getTenantId(){ return TenantContext.getTenantId(); }
}
  • 业务说明:
    • 电商:按 tenant_id 隔离商家数据;
    • 仓储:多仓储系统按租户隔离库存;
    • 财务:各租户账套互不见。

14.2 乐观锁并发控制(@Version + OptimisticLockerInnerInterceptor)

  • 适用:并发更新同一行,避免"后写覆盖前写"。
  • 规约:
    • 在热点表加 version 整型列;
    • 更新时携带版本号,失败则重试/前端提示。
java 复制代码
@Data @TableName("order")
public class OrderEntity {
    @TableId(type=IdType.ASSIGN_ID) private Long id;
    private BigDecimal totalAmount; private String status;
    @Version private Integer version; // 自动维护
}

@Configuration
class MpConfig {
    @Bean MybatisPlusInterceptor interceptor(){
        MybatisPlusInterceptor i=new MybatisPlusInterceptor();
        i.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return i;
    }
}
  • 业务说明:
    • 电商:订单支付状态更新;
    • 仓储:扣减库存;
    • 财务:对账记录确认。

14.3 全表操作保护(BlockAttackInnerInterceptor)

  • 适用:防止误写 UPDATE table/DELETE FROM table 无条件语句。
  • 规约:开发/测试/生产都建议开启,生产必须开启。
java 复制代码
@Bean
public MybatisPlusInterceptor interceptor(){
    MybatisPlusInterceptor i = new MybatisPlusInterceptor();
    i.addInnerInterceptor(new BlockAttackInnerInterceptor());
    return i;
}

14.4 JSON 字段映射(JacksonTypeHandler)

  • 适用:可变扩展字段(商品属性、地址详情、配置项)。
  • 规约:列类型使用 JSON/Text,统一对象结构与版本。
java 复制代码
@Data @TableName(value="product", autoResultMap = true)
class Product {
    @TableId(type=IdType.ASSIGN_ID) Long id;
    String name; BigDecimal price;
    @TableField(typeHandler = JacksonTypeHandler.class)
    Map<String, Object> attributes; // 如 {color: "red", size: "M"}
}
  • 电商:商品属性;仓储:库位扩展;财务:凭证扩展信息。

14.5 字段透明加解密(自定义 TypeHandler)

  • 适用:手机号、身份证、银行卡等敏感字段。
  • 规约:
    • 写库前加密,读库后解密;
    • 操作员仅见脱敏显示,不反查密文。
java 复制代码
public class EncryptStringTypeHandler extends BaseTypeHandler<String> {
    @Override public void setNonNullParameter(PreparedStatement ps,int i,String param,JdbcType jdbcType) throws SQLException {
        ps.setString(i, AesUtil.encrypt(param));
    }
    @Override public String getNullableResult(ResultSet rs,String column) throws SQLException {
        String v = rs.getString(column); return v==null?null:AesUtil.decrypt(v);
    }
    @Override public String getNullableResult(ResultSet rs,int i) throws SQLException {return getNullableResult(rs, rs.getMetaData().getColumnName(i));}
    @Override public String getNullableResult(CallableStatement cs,int i) throws SQLException {return cs.getString(i);} }

@TableField(typeHandler = EncryptStringTypeHandler.class)
private String phone;

14.6 批量 UPSERT(Insert or Update)

  • 适用:导入覆盖、增量同步。
  • 规约:使用数据库原生语法,控制批次大小。
xml 复制代码
<insert id="upsertBatch">
  INSERT INTO t (id,name,val) VALUES
  <foreach collection="list" item="e" separator=",">
    (#{e.id},#{e.name},#{e.val})
  </foreach>
  ON DUPLICATE KEY UPDATE name=VALUES(name), val=VALUES(val)
  <!-- PostgreSQL: ON CONFLICT (id) DO UPDATE SET ... -->
</insert>
  • 电商:商品价格批量同步;仓储:库存对账回填;财务:科目余额导入。

14.7 复杂联表查询(XML 或 MP-Join)

  • 适用:多表聚合(订单+明细+用户)。
  • 规约:单表 CRUD 用 MP,联表尽量走 XML 以便可控与调优。
xml 复制代码
<select id="selectOrderWithItems" resultType="com.example.dto.OrderDTO">
  SELECT o.id,o.order_no,u.username,i.product_name,i.qty
  FROM `order` o
  LEFT JOIN user u ON u.id=o.user_id
  LEFT JOIN order_item i ON i.order_id=o.id
  WHERE o.id=#{orderId}
</select>

14.8 动态数据源(读写分离/多库路由)

  • 规约:只在 Service 层标注,避免散布到控制器。
java 复制代码
@DS("slave")
public List<User> listFromSlave(){ return list(); }
  • 电商:主写从读;财务:报表读从库。

14.9 审计与逻辑删除(MetaObjectHandler + @TableLogic)

  • 规约:统一 createBy/createTime/updateBy/updateTime/deleted;逻辑删除默认开启,归档任务做物理清理。
java 复制代码
@TableField(fill = FieldFill.INSERT)       private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
@TableLogic private Integer deleted;

14.10 自定义全局方法注入(SqlInjector)

  • 适用:给所有 Mapper 注入通用方法(如批量软删、还原)。
java 复制代码
public class CustomInjector extends DefaultSqlInjector {
  @Override public List<AbstractMethod> getMethodList(Configuration c, Class<?> m){
    List<AbstractMethod> list = new ArrayList<>(super.getMethodList(c, m));
    list.add(new LogicRestoreMethod()); return list; }
}

14.11 选择列与聚合(select / func)

  • 规约:只查必要列,减少 IO;聚合下沉数据库。
java 复制代码
lambdaQuery().select(User::getId, User::getUsername).list();
// 分组聚合
query().select("age, COUNT(1) cnt").groupBy("age").list();

14.12 高效分页(Keyset/Seek 分页)

  • 适用:深分页性能差时,基于主键游标翻页。
java 复制代码
lambdaQuery().gt(User::getId, lastId).orderByAsc(User::getId).last("limit 100").list();

14.13 业务数据权限(自定义 InnerInterceptor)

  • 适用:按组织/仓库/用户维度追加权限条件。
java 复制代码
public class DataScopeInterceptor implements InnerInterceptor {
  @Override public void beforePrepare(Executor executor, MappedStatement ms, BoundSql bs) {
    // 解析SQL并在 where 中追加 data_scope 条件(略)
  }
}

14.14 软硬删除并存策略

  • 规约:默认逻辑删除;物理删除走受控接口,需审计。
java 复制代码
@InterceptorIgnore(tenantLine = "true", logicDelete = "true")
int physicalDeleteByIds(@Param("ids") List<Long> ids);

14.15 批量写入性能优化

  • 规约:saveBatch(list, batchSize);或使用 ExecutorType.BATCH,分批 flush。
java 复制代码
userService.saveBatch(users, 1000);

14.16 分库分表(ShardingSphere 等)

  • 适用:订单、流水按月分表;MP 负责 CRUD,路由交给中间件。
  • 规约:分片键落到查询条件;避免跨分片聚合。

14.17 统一ID与时间戳

  • 规约:使用 ASSIGN_ID(雪花)或自研发号器;时间统一 UTC 存储、展示本地化。
java 复制代码
@TableId(type = IdType.ASSIGN_ID) private Long id;

14.18 幂等保障(唯一约束 + 去重键)

  • 适用:支付回调、消息消费、对账导入。
xml 复制代码
<!-- MySQL 幂等写:忽略重复 -->
INSERT IGNORE INTO t(unique_key, data) VALUES(#{uk}, #{data})

14.19 变更审计(差异记录)

  • 规约:更新前后比对差异,写入审计表;可 AOP 拦截 update*
java 复制代码
class ChangeLog { Long bizId; String table; String diffJson; LocalDateTime at; String operator; }

14.x 速查建议(选型指南)

  • 高并发扣减:优先"乐观锁 + 幂等 + 短事务",必要时行级锁;
  • 多租户:首选列级隔离 + 拦截器;再考虑库/表级;
  • 大表分页:Seek 分页替代深度 offset;
  • 批量导入:分批 + UPSERT + 异步;
  • 数据权限:统一拦截器拼条件,避免 Controller/Mapper 分散;
  • 敏感数据:TypeHandler 加解密 + 脱敏展示 + 严控可见性。

以上高阶能力均可与现有章节组合使用,建议按业务优先级渐进引入,配合监控和回滚预案,确保风险可控。

相关推荐
bcbnb2 小时前
没有 Mac,如何上架 iOS App?跨平台开发者的完整上架实战指南
后端
ZhengEnCi2 小时前
P3E-Python Lambda表达式完全指南-什么是匿名函数?为什么90%程序员都在用?怎么快速掌握函数式编程利器?
后端·python
爱分享的鱼鱼2 小时前
Java基础 (七:网络编程、HTTP客户端开发)(待完善)
后端
aiopencode3 小时前
iOS WebKit Debug Proxy 深度解析与替代方案,跨平台远程调试的新思路
后端
Java小混子3 小时前
golang项目CRUD示例
开发语言·后端·golang
想搞艺术的程序员3 小时前
Go 优雅关闭实践指南:从原理到框架落地
开发语言·后端·golang
JohnYan3 小时前
Bun技术评估 - 29 Docker集成
javascript·后端·docker
华仔啊3 小时前
MyBatis-Plus 让你开发效率翻倍!新手也能5分钟上手!
java·后端·mybatis
绝无仅有3 小时前
某东互联网大厂的Redis面试知识点分析
后端·面试·架构