找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1181

积分

0

好友

153

主题
发表于 15 小时前 | 查看: 1| 回复: 0

01. 核心组件:IService与BaseMapper的精准使用

IService 是MyBatis-Plus封装的服务层核心接口,内置了 saveupdatelistpage 等海量开箱即用的方法,能覆盖90%以上的单表CRUD场景,直接调用就能省掉大量重复代码。

BaseMapper 则是自定义SQL的主战场,当遇到复杂联表查询、多表关联统计等场景时,基于它编写自定义XML或注解SQL,能灵活应对复杂业务需求。

// 示例1:IService Lambda查询(简洁高效)
List<User> users = userService.lambdaQuery()
        .eq(User::getStatus, 1) // 状态为启用
        .like(User::getName, "张") // 姓名包含“张”
        .list(); // 执行查询并返回列表

// 示例2:BaseMapper自定义查询(复杂场景)
// 需在UserMapper中定义方法,配合XML实现联表查询
UserVO selectUserDetail(Long userId);

小贴士:别把复杂SQL硬塞在Service层,该用BaseMapper写自定义SQL时千万别偷懒,清晰的分层才是代码易维护的关键。

02. Lambda查询:编译期规避字段名错误

核心优势

MyBatis-Plus的 Lambda查询 是最实用的特性之一,相比传统字符串拼接字段名的方式,它能在编译期就校验字段名是否正确,彻底避免运行时才发现“字段名拼错”的低级问题。

// 错误示例:字符串拼接字段名,拼错“name”为“naem”,编译不报错,运行才出问题
userService.lambdaQuery().eq("naem", "张三");

// 正确示例:Lambda表达式引用实体字段,编译期就能检测字段名错误
userService.lambdaQuery().eq(User::getName, "张三");

03. 分页查询:条件构造器+分页无缝集成

核心能力

MyBatis-Plus的分页功能深度适配自身条件构造器,支持自定义排序、分页结果转换,是后端分页场景的“最优解”,无需手动拼接分页SQL。

// 1. 构建分页对象:参数1=页码,参数2=每页条数
Page<User> page = new Page<>(1, 20);
// 2. 添加排序规则:按创建时间降序
page.addOrder(OrderItem.desc("create_time"));

// 3. 执行分页查询:结合Lambda条件构造器
Page<User> userPage = userService.page(page,
        Wrappers.lambdaQuery(User.class)
                .eq(User::getDeptId, 2) // 筛选部门ID为2的用户
);

// 4. 分页结果转换:User实体转UserVO(避免返回冗余字段)
Page<UserVO> voPage = userPage.convert(user -> {
    UserVO vo = new UserVO();
    // 拷贝属性(可使用Spring的BeanUtils或MapStruct)
    BeanUtils.copyProperties(user, vo);
    return vo;
});

04. 批量操作:解决大批量数据插入/更新性能问题

核心场景

如果大批量数据插入/更新时还逐条处理,数据库IO会被打满,性能直线下降。MyBatis-Plus的批量操作方法能按批次提交,大幅提升效率。

// 示例1:批量插入(指定批次大小,推荐1000条/批)
List<User> hugeUserList = new ArrayList<>(); // 待插入的海量用户数据
// 第二个参数为批次大小,控制每次提交的条数,避免数据库压力过大
userService.saveBatch(hugeUserList, 1000);

// 示例2:批量更新状态(按ID批量更新)
public void batchUpdateStatus(List<Long> ids, Integer status) {
    // 1. 转换ID列表为待更新的User对象列表
    List<User> updateList = ids.stream().map(id -> {
        User user = new User();
        user.setId(id); // 设置要更新的用户ID
        user.setStatus(status); // 设置新状态
        return user;
    }).collect(Collectors.toList());
    // 2. 批量更新(按ID更新)
    userService.updateBatchById(updateList);
}

05. 复杂查询:QueryWrapper构建多条件逻辑

核心能力

QueryWrapper(及Lambda版 LambdaQueryWrapper)能灵活构建多条件、多逻辑的查询语句,支持 and/or 嵌套、区间查询、指定字段查询等场景。

// 示例1:多条件嵌套查询(and/or组合)
List<User> users = userService.lambdaQuery()
        .eq(User::getStatus, 1) // 基础条件:状态启用
        .and(wrapper -> wrapper // 嵌套条件:姓名含“张” 或 邮箱含“zhang”
                .like(User::getName, "张")
                .or()
                .like(User::getEmail, "zhang")
        )
        .between(User::getCreateTime, startTime, endTime) // 时间区间:创建时间在start-end之间
        .list();

// 示例2:指定字段查询(避免返回冗余字段,提升查询效率)
List<User> userList = userService.lambdaQuery()
        .select(User::getId, User::getName) // 只查询ID和姓名字段
        .eq(User::getStatus, 1) // 状态启用
        .list();

06. 自动填充:告别手动设置公共字段

核心场景

create_time(创建时间)、update_time(更新时间)、create_by(创建人)这类公共字段,无需每次手动 set,通过MyBatis-Plus的自动填充功能就能自动赋值。

// 1. 实现MetaObjectHandler接口,自定义填充逻辑
public class MyMetaObjectHandler implements MetaObjectHandler {

    // 插入操作时填充(如createTime、createBy)
    @Override
    public void insertFill(MetaObject metaObject) {
        // strictInsertFill:严格模式填充,确保字段存在且未赋值时才填充
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser()); // 获取当前登录用户
    }

    // 更新操作时填充(如updateTime)
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    // 模拟获取当前登录用户(实际项目中可从ThreadLocal/Token中获取)
    private String getCurrentUser() {
        return "admin";
    }
}

// 2. 实体类定义对应字段
public class User {
    // 自动填充的创建时间
    private LocalDateTime createTime;
    // 自动填充的更新时间
    private LocalDateTime updateTime;
}

07. 逻辑删除:避免物理删除导致的数据丢失

核心价值

生产环境中绝对不要用 delete from 物理删除数据!MyBatis-Plus的逻辑删除能将“删除”操作转为“更新标记”,既保留数据,又不影响业务查询。

# 1. 配置逻辑删除(application.yml)
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 逻辑删除字段名(实体类需对应)
      logic-delete-value: 1 # 已删除标记值
      logic-not-delete-value: 0 # 未删除标记值
// 2. 实体类添加逻辑删除字段
public class User {
    private Long id;
    private String name;
    // 逻辑删除标记(0=未删,1=已删)
    private Integer deleted;
}

// 3. 调用删除方法(实际执行UPDATE而非DELETE)
userService.removeById(1L);
// 实际执行SQL:UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
// 所有查询会自动拼接:AND deleted = 0,无需手动加条件

08. 枚举映射:让状态字段更具可读性

核心场景

数据库中存储的状态码(如 status=1),在代码中直接用数字会导致可读性差,MyBatis-Plus支持枚举与数据库字段的自动映射,代码更清晰。

// 1. 定义状态枚举(与数据库状态码对应)
public enum UserStatus {
    ENABLED(1, "启用"), // 状态码1=启用
    DISABLED(0, "禁用"); // 状态码0=禁用

    private final int code; // 数据库存储的数字值
    private final String desc; // 状态描述

    UserStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    // 枚举需提供code的getter,MP会自动映射
    public int getCode() {
        return code;
    }
}

// 2. 实体类直接使用枚举类型
public class User {
    private Long id;
    private String name;
    // 状态字段,类型为枚举
    private UserStatus status;
}

// 3. 业务代码中直接使用枚举(无需关心数字值)
userService.lambdaQuery().eq(User::getStatus, UserStatus.ENABLED).list();

09. 多租户隔离:SAAS系统的核心解决方案

核心能力

SAAS系统中,不同租户的数据必须严格隔离,MyBatis-Plus的租户插件能自动在所有查询/更新语句中拼接租户ID条件,无需手动加条件。

// 配置租户拦截器(MybatisPlusConfig)
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 初始化租户拦截器
        TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
        tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
            // 1. 获取当前租户ID(实际项目中从ThreadLocal获取)
            @Override
            public Expression getTenantId() {
                // TenantContext:自定义的租户上下文,存储当前租户ID
                return new StringValue(TenantContext.getCurrentTenantId());
            }

            // 2. 租户ID的字段名(数据库表中需有该字段)
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            // 3. 忽略租户隔离的表(如系统配置表)
            @Override
            public boolean ignoreTable(String tableName) {
                return "system_config".equals(tableName);
            }
        });

        // 添加租户拦截器到MP拦截器链
        interceptor.addInnerInterceptor(tenantInterceptor);
        return interceptor;
    }
}

10. 代码生成器:一键生成CRUD代码

核心效率

不用再手动编写 EntityMapperServiceController 这些重复代码!MyBatis-Plus的代码生成器能根据数据库表结构,一键生成全套代码,节省80%的重复工作量。

public class CodeGenerator {
    public static void main(String[] args) {
        // 1. 初始化代码生成器
        AutoGenerator generator = new AutoGenerator();

        // 2. 配置数据源(连接数据库信息)
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("123456");
        generator.setDataSource(dataSourceConfig);

        // 3. 全局配置(生成代码的基础信息)
        GlobalConfig globalConfig = new GlobalConfig();
        // 代码输出目录(项目的java目录)
        globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
        globalConfig.setAuthor("大华"); // 作者名
        globalConfig.setOpen(false); // 生成后不自动打开文件夹
        globalConfig.setSwagger2(true); // 生成Swagger注解(接口文档)
        generator.setGlobalConfig(globalConfig);

        // 4. 包配置(代码的包结构)
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.laomao.demo"); // 父包名
        packageConfig.setEntity("domain.entity"); // 实体类包名
        packageConfig.setMapper("dao.mapper"); // Mapper接口包名
        packageConfig.setService("service"); // Service接口包名
        packageConfig.setServiceImpl("service.impl"); // Service实现类包名
        generator.setPackageInfo(packageConfig);

        // 5. 执行生成
        generator.execute();
    }
}

11. 自定义拦截器:扩展SQL执行逻辑

核心场景

MyBatis-Plus支持自定义 InnerInterceptor,可用于实现数据权限控制、SQL性能监控、字段加解密等场景,是扩展MyBatis-Plus能力的核心方式。

// 示例:慢SQL监控拦截器
@Component
public class SqlLogInterceptor implements InnerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(SqlLogInterceptor.class);

    // 查询执行前:记录开始时间
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 将开始时间存入BoundSql的附加信息中
        boundSql.setAdditionalParameter("startTime", System.currentTimeMillis());
    }

    // 查询执行后:计算耗时,慢SQL告警
    @Override
    public void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 获取开始时间
        Long startTime = (Long) boundSql.getAdditionalParameter("startTime");
        long end = System.currentTimeMillis();
        long cost = end - startTime; // 计算执行耗时(毫秒)

        // 耗时超过1000ms,触发慢SQL警告
        if (cost > 1000) {
            log.warn("慢SQL警告: SQL语句={}, 执行耗时={}ms", boundSql.getSql(), cost);
        }
    }
}

12. 雪花算法:分布式ID生成方案

核心场景

在分布式系统中,数据库自增ID容易导致冲突,而MyBatis-Plus默认集成了雪花算法来生成全局唯一的64位Long型ID,开发者无需再依赖数据库的自增特性。

// 实体类中直接使用Long类型ID,MP默认用雪花算法生成ID
public class User {
    // 主键ID(MP会自动生成雪花算法ID)
    @TableId(type = IdType.ASSIGN_ID) // 显式指定ID生成策略(可选,默认就是ASSIGN_ID)
    private Long id;

    private String name;
    private Integer status;
}

// 插入数据时,无需设置ID,MP自动生成
User user = new User();
user.setName("张三");
userService.save(user);
// user.getId() 即为雪花算法生成的全局唯一ID

13. 乐观锁:解决高并发数据覆盖问题

核心场景

高并发场景下,多个请求同时更新同一条数据,易出现“后提交覆盖先提交”的问题,MyBatis-Plus的乐观锁通过 version 字段实现无锁并发控制。

// 1. 实体类添加version字段(乐观锁版本号)
public class User {
    private Long id;
    private String name;
    // 乐观锁版本号(必须有,且类型为Integer/Long)
    @Version
    private Integer version;
}

// 2. 配置乐观锁拦截器(MybatisPlusConfig)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 添加乐观锁拦截器
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

// 3. 业务代码中使用(自动基于version更新)
public void updateUserName(Long userId, String newName) {
    // 第一步:查询数据(获取当前version)
    User user = userService.getById(userId);
    // 第二步:修改数据
    user.setName(newName);
    // 第三步:更新数据(MP会自动拼接WHERE version = 原版本号)
    boolean success = userService.updateById(user);
    // 若更新失败(version不匹配),需处理并发冲突(如重试/提示用户)
    if (!success) {
        throw new RuntimeException("数据已被其他用户修改,请刷新后重试");
    }
}

14. 结果映射:与MyBatis注解完美兼容

核心场景

MyBatis-Plus完全不干扰MyBatis原有的强大功能,你可以结合 @Result@ResultMap 等注解实现复杂的结果映射,轻松满足多表联查和VO对象组装的需求。

// Mapper接口中定义联表查询方法(结合MyBatis注解/XML)
public interface UserMapper extends BaseMapper<User> {
    // 示例:查询用户+所属部门信息,返回UserVO
    @Select("SELECT u.id, u.name, d.name as deptName FROM user u LEFT JOIN dept d ON u.dept_id = d.id WHERE u.id = #{id}")
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "name", property = "name"),
            @Result(column = "deptName", property = "deptName") // 映射部门名称
    })
    UserVO findUserWithDept(Long id);
}

// Service层调用
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public UserVO findUserWithDept(Long id) {
        return getBaseMapper().findUserWithDept(id);
    }
}

15. 事务控制:保证数据操作的原子性

核心场景

在进行多表或多项操作(如创建用户记录、初始化账户信息、发送通知消息)时,必须保证其原子性。结合Spring的声明式事务,可以有效避免因部分操作失败而导致的数据不一致问题。

@Service
public class UserService extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private AccountService accountService;
    @Autowired
    private MessageService messageService;

    // 声明事务:一旦异常,所有操作回滚
    @Transactional(rollbackFor = Exception.class)
    public void createUserWithInitData(User user) {
        // 1. 保存用户
        this.save(user);

        // 2. 初始化用户账户
        Account account = new Account();
        account.setUserId(user.getId());
        accountService.save(account);

        // 3. 发送欢迎消息
        messageService.sendWelcomeMessage(user.getId());
    }
}

MyBatis-Plus的强大远不止于此,但掌握以上15个核心技巧,足以覆盖日常开发的绝大多数场景,让你从重复的CRUD中解放出来,把精力聚焦在业务逻辑上。如果你想了解更多关于Java后端开发的深度内容与实战经验,欢迎访问云栈社区进行交流与学习。




上一篇:智能体架构演进全解析:从RAG到多智能体协同,赋能可观测平台
下一篇:如何在个人电脑部署轻量级AI助手nanobot:GitHub开源OS Agent实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-7 19:24 , Processed in 0.364243 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表