当前位置: 首页 > news >正文

MyBatis-Plus深度全解:从入门到企业级实战

MyBatis-Plus深度全解:从入门到企业级实战

一、为什么选择MyBatis-Plus?

1.1 MyBatis的痛点

- 重复CRUD代码编写
- 分页功能实现复杂
- 缺少通用Service层封装
- 动态表名支持困难
- 多租户方案需自行实现

1.2 MyBatis-Plus核心优势

+ 无侵入:只做增强不做改变
+ 强大的CRUD操作:内置通用Mapper/Service
+ 支持Lambda形式调用
+ 主键自动生成策略
+ 全局拦截器(分页/租户/性能分析)

1.3 技术栈对比

特性MyBatisMyBatis-PlusJPA
CRUD简化手动自动生成自动
分页插件需集成内置内置
代码生成器强大中等
多租户支持手动注解配置需扩展
学习曲线中等平滑陡峭

二、SpringBoot 3.x整合实战

2.1 环境准备

技术栈版本说明
SpringBoot3.1.0基础框架
Java17LTS版本
MySQL8.0数据库
MyBatis-Plus3.5.3.1ORM框架

2.2 依赖引入

<dependencies><!-- SpringBoot基础 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MyBatis-Plus核心 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><!-- 数据库驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Lombok简化开发 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>

2.3 配置文件

spring:datasource:url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver# MyBatis-Plus配置
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启SQL日志map-underscore-to-camel-case: true # 下划线转驼峰global-config:db-config:id-type: assign_id # 雪花算法ID生成logic-delete-field: deleted # 逻辑删除字段logic-delete-value: 1 # 已删除值logic-not-delete-value: 0 # 未删除值

2.4 实体类与Mapper

@Data
@TableName("sys_user") // 表名映射
public class User {@TableId(type = IdType.ASSIGN_ID) // 雪花算法IDprivate Long id;@TableField("username") // 字段映射private String name;private Integer age;private String email;@TableField(fill = FieldFill.INSERT) // 自动填充private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;
}// Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {// 自定义方法@Select("SELECT * FROM sys_user WHERE age > #{age}")List<User> selectUsersByAge(@Param("age") Integer age);
}

三、核心功能深度解析

3.1 CRUD接口详解

3.1.1 Mapper层CRUD
// 插入
User user = new User();
user.setName("John");
user.setAge(30);
userMapper.insert(user); // 批量插入
List<User> users = Arrays.asList(new User(...), new User(...));
userMapper.insertBatchSomeColumn(users); // 批处理优化// 更新
User updateUser = new User();
updateUser.setId(1L);
updateUser.setEmail("new@example.com");
userMapper.updateById(updateUser);// 条件更新
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.set("email", "admin@example.com").eq("name", "admin");
userMapper.update(null, wrapper);// 删除
userMapper.deleteById(1L); // ID删除
userMapper.delete(new QueryWrapper<User>().eq("age", 18)); // 条件删除
3.1.2 Service层CRUD
public interface UserService extends IService<User> {// 自定义业务方法List<User> findAdmins();
}@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic List<User> findAdmins() {LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();wrapper.eq(User::getRole, "admin");return baseMapper.selectList(wrapper);}
}// 使用示例
userService.save(user); // 保存
userService.updateById(user); // 更新
userService.removeByIds(Arrays.asList(1L,2L)); // 批量删除

3.2 条件构造器(Wrapper)

3.2.1 QueryWrapper
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "age") // 指定字段.like("name", "张")          // 模糊查询.between("age", 20, 30)     // 范围查询.isNotNull("email")          // 非空判断.orderByDesc("create_time"); // 排序
List<User> users = userMapper.selectList(wrapper);
3.2.2 LambdaQueryWrapper(推荐)
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.select(User::getId, User::getName, User::getAge).like(User::getName, "张").ge(User::getAge, 18).orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(lambdaWrapper);
3.2.3 复杂条件示例
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.and(wq -> wq.gt(User::getAge, 18).or().isNull(User::getEmail)).nested(nq -> nq.eq(User::getRole, "admin").lt(User::getCreateTime, LocalDateTime.now())).apply("date_format(create_time,'%Y-%m-%d') = {0}", "2023-07-01");

3.3 分页插件(企业级优化)

@Configuration
public class MybatisPlusConfig {/*** 分页插件 + 性能分析插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分页插件(MySQL方言)PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);paginationInterceptor.setMaxLimit(1000L); // 单页最大1000条paginationInterceptor.setOverflow(true);   // 超过总页数返回第一页interceptor.addInnerInterceptor(paginationInterceptor);// 性能分析插件(仅开发环境)if (log.isDebugEnabled()) {PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();performanceInterceptor.setFormat(true);performanceInterceptor.setMaxTime(2000); // SQL执行超过2秒记录警告interceptor.addInnerInterceptor(performanceInterceptor);}return interceptor;}
}// 使用示例
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(User::getRole, "admin");
Page<User> result = userMapper.selectPage(page, wrapper);// 结果处理
List<User> records = result.getRecords();
long total = result.getTotal();

四、高级特性实战

4.1 逻辑删除

# 全局配置
mybatis-plus:global-config:db-config:logic-delete-field: deleted  # 逻辑删除字段名logic-delete-value: 1        # 删除值logic-not-delete-value: 0    # 未删除值

4.2 自动填充

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());}@Overridepublic void updateFill(MetaObject metaObject) {this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());}
}

4.3 多租户方案

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 多租户插件TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 从安全上下文获取租户IDreturn new LongValue(SecurityUtils.getTenantId());}@Overridepublic String getTenantIdColumn() {return "tenant_id";}@Overridepublic boolean ignoreTable(String tableName) {// 忽略系统表return "sys_config".equals(tableName);}});interceptor.addInnerInterceptor(tenantInterceptor);return interceptor;
}

4.4 枚举处理

// 枚举定义
@Getter
public enum UserStatus {ENABLED(1, "启用"),DISABLED(0, "禁用");@EnumValue // 标记数据库存储值private final int code;private final String desc;UserStatus(int code, String desc) {this.code = code;this.desc = desc;}
}// 实体类字段
private UserStatus status;

4.5 动态表名

public class DynamicTableNameParser implements TableNameHandler {private ThreadLocal<String> tableName = new ThreadLocal<>();public void setTableName(String tableName) {this.tableName.set(tableName);}@Overridepublic String dynamicTableName(String sql, String tableName) {return this.tableName.get() != null ? this.tableName.get() : tableName;}
}// 使用示例
DynamicTableNameParser parser = new DynamicTableNameParser();
parser.setTableName("user_2023"); // 设置动态表名TableNameHelper.setTableNameParser(parser);
List<User> users = userMapper.selectList(null); // 操作user_2023表

五、代码生成器(企业级配置)

5.1 生成器配置

public class CodeGenerator {public static void main(String[] args) {AutoGenerator generator = new AutoGenerator();// 全局配置GlobalConfig globalConfig = new GlobalConfig();globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");globalConfig.setAuthor("YourName");globalConfig.setOpen(false);globalConfig.setSwagger2(true); // 开启Swagger注解generator.setGlobalConfig(globalConfig);// 数据源配置DataSourceConfig dataSourceConfig = new DataSourceConfig();dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mp_demo");dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");dataSourceConfig.setUsername("root");dataSourceConfig.setPassword("123456");generator.setDataSource(dataSourceConfig);// 包配置PackageConfig packageConfig = new PackageConfig();packageConfig.setParent("com.example.mp");packageConfig.setEntity("entity");packageConfig.setMapper("mapper");packageConfig.setService("service");packageConfig.setController("controller");generator.setPackageInfo(packageConfig);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);strategy.setEntityLombokModel(true); // 使用Lombokstrategy.setRestControllerStyle(true); // RESTful风格strategy.setInclude("sys_user", "sys_role"); // 生成表// 自定义模板TemplateConfig templateConfig = new TemplateConfig();templateConfig.setController("templates/controller.java");templateConfig.setService("templates/service.java");templateConfig.setServiceImpl("templates/serviceImpl.java");generator.setTemplate(templateConfig);generator.execute();}
}

5.2 自定义模板示例

// templates/controller.java.vm
package ${package.Controller};@RestController
@RequestMapping("/${table.entityPath}")
@RequiredArgsConstructor
public class ${table.controllerName} {private final ${table.serviceName} ${table.entityPath}Service;@GetMapping("/{id}")public Result<${entity}> getById(@PathVariable ${table.keyType} id) {return Result.success(${table.entityPath}Service.getById(id));}// 其他方法...
}

六、性能优化指南

6.1 SQL执行效率优化

// 1. 启用批处理
@Bean
public ConfigurationCustomizer configurationCustomizer() {return configuration -> {configuration.setDefaultExecutorType(ExecutorType.BATCH); // 批处理模式};
}// 2. 使用流式查询
try (Cursor<User> cursor = userMapper.selectCursor(queryWrapper)) {cursor.forEach(user -> process(user));
}// 3. 禁用XML热加载(生产环境)
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl # 关闭日志local-cache-scope: statement # 减小缓存范围

6.2 查询优化技巧

// 1. 只查询必要字段
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.select(User::getId, User::getName);// 2. 避免N+1查询(关联查询优化)
@Select("SELECT u.*, d.name AS dept_name FROM user u LEFT JOIN dept d ON u.dept_id = d.id")
List<UserVO> selectUserWithDept();// 3. 使用二级缓存
@CacheNamespace(implementation = MybatisRedisCache.class, eviction = MybatisRedisCache.class)
public interface UserMapper extends BaseMapper<User> {
}

6.3 索引优化建议

-- 联合索引示例
CREATE INDEX idx_user_age_role ON sys_user(age, role);-- 覆盖索引查询
EXPLAIN SELECT id, name FROM sys_user WHERE age BETWEEN 20 AND 30;

七、企业级实战案例

7.1 数据权限控制

public class DataPermissionInterceptor implements InnerInterceptor {@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 获取当前用户数据权限DataScope dataScope = SecurityUtils.getDataScope();if (dataScope != null) {// 修改SQL添加权限过滤String sql = boundSql.getSql();String newSql = sql + " AND " + dataScope.getSqlSegment();// 反射修改BoundSQLField field = boundSql.getClass().getDeclaredField("sql");field.setAccessible(true);field.set(boundSql, newSql);}}
}// 注册拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new DataPermissionInterceptor());return interceptor;
}

7.2 多数据源动态切换

// 1. 配置多数据源
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DruidDataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> dataSourceMap = new HashMap<>();dataSourceMap.put("master", masterDataSource());dataSourceMap.put("slave", slaveDataSource());DynamicDataSource dataSource = new DynamicDataSource();dataSource.setDefaultTargetDataSource(masterDataSource());dataSource.setTargetDataSources(dataSourceMap);return dataSource;}
}// 2. 数据源切换注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DS {String value() default "master";
}// 3. 切面实现
@Aspect
@Component
public class DataSourceAspect {@Around("@annotation(ds)")public Object around(ProceedingJoinPoint point, DS ds) throws Throwable {String dsKey = ds.value();DynamicDataSourceContextHolder.push(dsKey);try {return point.proceed();} finally {DynamicDataSourceContextHolder.poll();}}
}// 4. 使用示例
@Service
public class UserService {@DS("slave") // 从库查询public User getById(Long id) {return userMapper.selectById(id);}@DS("master") // 主库写入public void saveUser(User user) {userMapper.insert(user);}
}

八、源码解析(核心设计思想)

8.1 SQL注入器原理

// 核心流程
AbstractSqlInjector#inspectInject() -> 解析Mapper接口方法-> 根据方法名匹配内置方法(selectById等)-> 构造对应的SqlMethod-> 创建MappedStatement// 自定义注入示例
public class MySqlInjector extends DefaultSqlInjector {@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass) {List<AbstractMethod> methods = super.getMethodList(mapperClass);methods.add(new FindAll()); // 添加自定义方法return methods;}
}// 自定义方法实现
public class FindAll extends AbstractMethod {@Overridepublic MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {String sql = "SELECT * FROM %s";String formattedSql = String.format(sql, tableInfo.getTableName());SqlSource sqlSource = languageDriver.createSqlSource(configuration, formattedSql, modelClass);return this.addSelectMappedStatementForTable(mapperClass, "findAll", sqlSource, tableInfo);}
}

8.2 条件构造器原理

// 核心:AbstractWrapper
public abstract class AbstractWrapper<T, R, Children> implements Compare<Children, R>, Nested<Children, Children> {// 存储条件表达式protected List<SqlSegment> expression = new ArrayList<>();// 条件构建示例public Children eq(boolean condition, R column, Object val) {if (condition) {expression.add(new SimpleSqlSegment(() -> columnToString(column), () -> " = ", () -> formatSqlValue(val)));}return typedThis;}
}// SQL片段生成
public String getSqlSegment() {return expression.stream().map(SqlSegment::getSqlSegment).filter(Objects::nonNull).collect(Collectors.joining(" "));
}

九、常见问题排查

9.1 字段映射问题

问题现象:实体类字段与数据库列不匹配
解决方案

// 1. 明确指定映射
@TableField(value = "db_column")// 2. 关闭自动驼峰转换
mybatis-plus:configuration:map-underscore-to-camel-case: false// 3. 检查字段类型是否匹配

9.2 分页失效问题

问题现象:分页查询返回所有结果
排查步骤

  1. 检查是否配置分页插件
  2. 确认Page对象作为第一个参数
  3. 验证SQL是否支持分页(无order by可能导致分页异常)

9.3 逻辑删除不生效

解决方案

# 1. 确认全局配置
mybatis-plus:global-config:db-config:logic-delete-field: deleted# 2. 实体类添加注解
@TableLogic
private Integer deleted;

9.4 多租户SQL异常

典型错误:INSERT语句缺少租户ID
解决方案

// 在TenantLineHandler中实现租户ID获取
@Override
public Expression getTenantId() {return new LongValue(SecurityUtils.getTenantId());
}// 确保INSERT操作包含租户字段

十、未来展望(MyBatis-Plus 4.0)

10.1 新特性预览

  • 响应式编程支持:整合Project Reactor
  • GraalVM原生镜像:提升启动速度
  • 增强的多租户方案:支持更复杂的租户隔离
  • 分布式ID生成器:内置更多分布式ID方案

10.2 架构演进

MyBatis Core
MyBatis-Plus 3.x
扩展接口
SQL注入器
元数据处理
拦截器链
4.0插件体系

结语

MyBatis-Plus作为MyBatis的增强工具,在企业级应用开发中展现出强大价值。本文涵盖了从基础配置到高级特性的全链路实践,重点包含:

  1. 核心功能深度解析:条件构造器、分页插件、代码生成器
  2. 企业级方案:多租户、数据权限、动态数据源
  3. 性能优化:批处理、流式查询、索引优化
  4. 源码级原理:SQL注入器、条件构造器实现
  5. 生产环境问题排查

最佳实践建议

  • 复杂查询仍推荐XML方式
  • 生产环境关闭SQL日志
  • 使用LambdaQueryWrapper避免字段魔法值
  • 定期进行SQL性能分析

相关文章:

  • idea json生成实体类
  • 【Git系列】如何同步原始仓库的更新到你的fork仓库?
  • Spring之事务管理方式
  • Ubuntu系统配置C++的boost库(含filesystem模块)的方法
  • 自驾总结_Prediction
  • 沉金电路板工艺解析:从原理到应用的全面指南
  • 面向开发者的提示词工程③——文本总结(Summarizing)
  • 麒麟信安系统下修改系统默认记录日志大小
  • mongodb源码分析session异步接受asyncSourceMessage()客户端流变Message对象
  • [Java恶补day16] 238.除自身以外数组的乘积
  • [蓝桥杯]全球变暖
  • 常见排序算法时间、空间复杂度总结
  • vue-19(Vuex异步操作和变更)
  • Python训练营打卡Day45
  • Spring Boot统一功能处理深度解析
  • 系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
  • 网络安全面试题目(无答案)
  • Next.js中Protected Route(受保护路由)
  • reverse笔记
  • Neo4j 完全指南:从入门到精通
  • 可以做淘宝推广的网站有哪些内容/长沙关键词快速排名
  • 模板网站有什么不好/it培训机构排名及学费
  • 做网站fjfzwl/郑州seo外包顾问
  • 上海网站开发哪里好薇/上海疫情突然消失的原因
  • 天娇易业网站建设公司/厦门网站建设公司哪家好
  • 做论坛网站需要多少钱/广州网络公司