1. MyBatis-Plus 核心价值与适用场景
作为国内Java开发者最常用的ORM框架之一,MyBatis-Plus在传统MyBatis基础上进行了深度增强。我在实际企业级项目开发中发现,当项目需要快速迭代且团队规模有限时,MyBatis-Plus的自动化CRUD能力可以节省约40%的基础代码编写时间。特别是在微服务架构中,每个服务模块都需要独立的数据访问层,这种效率提升尤为明显。
框架的核心优势主要体现在三个方面:首先是内置通用Mapper,开发者无需编写基础SQL语句;其次是提供了强大的条件构造器,支持链式调用构建复杂查询;最后是活跃的社区生态,配套的代码生成器、分页插件等工具链完善。这些特性使其特别适合中小型互联网企业的后台管理系统、快速原型开发等场景。
2. 环境准备与基础配置
2.1 依赖引入与版本选择
在Spring Boot项目中引入MyBatis-Plus需要特别注意版本兼容性。以当前主流的Spring Boot 2.7.x版本为例,推荐使用MyBatis-Plus 3.5.x系列。Maven配置示例如下:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
注意:避免直接引入mybatis-plus-core而遗漏boot-starter,后者已经包含必要的自动配置类。我在实际项目中曾因这个疏忽导致分页插件失效,排查了整整两小时。
2.2 数据源与基础配置
application.yml中的最小化配置应包含:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启SQL日志
global-config:
db-config:
id-type: auto # 主键自增策略
关键配置项说明:
log-impl建议在开发环境开启,生产环境需要关闭id-type根据数据库设计选择,如使用雪花算法可配置为assign_id
3. 核心注解深度解析
3.1 实体类注解
@TableName 注解在实际使用中有几个易忽略但实用的参数:
java复制@TableName(value = "sys_user", autoResultMap = true)
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "username", fill = FieldFill.INSERT)
private String name;
@TableLogic
private Integer deleted;
}
autoResultMap开启后会自动映射resultMap,解决复杂类型(如JSON字段)的存储问题fill字段填充策略配合MetaObjectHandler使用,可实现自动填充创建时间等字段@TableLogic实现逻辑删除,实际执行的是UPDATE而非DELETE语句
3.2 条件构造器实战技巧
QueryWrapper 的高级用法示例:
java复制QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username as name") // 字段筛选与别名
.nested(i -> i.eq("status", 1).or().gt("login_count", 10)) // 嵌套条件
.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2023-08-01") // 日期格式化查询
.orderByDesc("id")
.last("limit 10"); // 手动追加SQL片段
踩坑记录:
last()方法会直接拼接SQL字符串,有SQL注入风险,务必确保参数可控。我曾见过有团队在分页查询中使用last("limit "+pageNo+","+pageSize)导致的安全漏洞。
4. 常用功能模块实现
4.1 分页插件配置与优化
标准分页配置需要创建配置类:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL){
@Override
protected void optimizeCount(IPage<?> page, MappedStatement ms, BoundSql boundSql) {
// 覆盖原生count查询优化逻辑
if (page.optimizeCountSql()) {
String countSql = "SELECT COUNT(*) FROM (" + boundSql.getSql() + ") temp";
page.setCountId(countSql);
}
}
});
return interceptor;
}
}
分页查询时的性能优化建议:
- 大数据量表避免使用
select * - 超过10万条记录时考虑使用
page.setSearchCount(false)禁用总数统计 - 联表查询时手动编写countSql
4.2 代码生成器定制开发
标准代码生成器配置示例:
java复制FastAutoGenerator.create("jdbc:mysql://localhost:3306/demo", "root", "123456")
.globalConfig(builder -> {
builder.author("dev") // 设置作者
.outputDir(System.getProperty("user.dir") + "/src/main/java"); // 输出目录
})
.packageConfig(builder -> {
builder.parent("com.example") // 父包名
.moduleName("system") // 模块名
.entity("entity") // 实体类包名
.service("service")
.serviceImpl("service.impl");
})
.strategyConfig(builder -> {
builder.addInclude("sys_user", "sys_role") // 包含的表名
.entityBuilder()
.enableLombok() // 启用Lombok
.logicDeleteColumnName("deleted") // 逻辑删除字段
.serviceBuilder()
.formatServiceFileName("%sService"); // 服务类命名规则
})
.templateConfig(builder -> {
builder.disable(TemplateType.CONTROLLER); // 禁用控制器生成
})
.execute();
个性化定制建议:
- 通过重写
FreemarkerTemplateEngine修改模板文件 - 对特定字段添加
@TableField注解的定制逻辑 - 生成DTO、VO等扩展类时使用自定义模板
5. 生产环境最佳实践
5.1 多数据源配置方案
在微服务架构中,有时需要同时访问多个数据源。推荐使用dynamic-datasource-spring-boot-starter实现:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("master", masterDataSource());
dataSourceMap.put("slave", slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
}
使用@DS注解切换数据源:
java复制@Service
public class UserServiceImpl implements UserService {
@DS("master")
public void addUser(User user) {
// 使用主库写入
}
@DS("slave")
public User getUserById(Long id) {
// 从从库读取
}
}
5.2 审计日志与数据安全
实现自动填充审计字段的完整方案:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUsername());
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUsername());
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
private String getCurrentUsername() {
// 从安全上下文获取当前用户
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.orElse("system");
}
}
实体类对应字段配置:
java复制@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
6. 性能优化与疑难排查
6.1 SQL执行性能分析
启用MyBatis-Plus的性能分析插件:
java复制@Bean
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor interceptor = new PerformanceInterceptor();
interceptor.setMaxTime(1000); // SQL执行最大时长(ms)
interceptor.setFormat(true); // 格式化SQL
return interceptor;
}
常见性能问题解决方案:
- N+1查询问题:使用
@TableField(exist = false)标注非表字段,避免自动查询 - 大字段查询:通过
@TableField(select = false)排除不必要字段 - 索引失效:避免在条件构造器中使用函数处理索引字段
6.2 事务管理实践
在Spring Boot中正确使用事务:
java复制@Service
public class OrderServiceImpl implements OrderService {
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 主业务逻辑
orderMapper.insert(dto.toOrder());
// 库存扣减
inventoryService.reduce(dto.getSkuId(), dto.getQuantity());
// 支付记录
paymentService.create(dto.getOrderNo(), dto.getAmount());
}
}
事务传播机制的选择建议:
- 默认使用
REQUIRED传播行为 - 查询方法建议添加
@Transactional(readOnly = true) - 跨服务调用需要考虑分布式事务方案
7. 微服务架构下的特殊考量
在Spring Cloud微服务环境中使用MyBatis-Plus时,有几个关键点需要特别注意:
- 分布式ID生成策略推荐使用
IdType.ASSIGN_ID(默认雪花算法) - 多服务共用实体类时,建议将实体类提取到单独的module中
- Feign客户端调用时,注意DTO的序列化/反序列化问题
一个典型的微服务项目结构示例:
code复制cloud-demo
├── api-common // 公共DTO、常量定义
├── service-user // 用户服务
├── service-order // 订单服务
└── service-gateway // API网关
在分库分表场景下的处理方案:
java复制@TableName("t_order_${index}")
public class Order {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String orderNo;
// 其他字段...
}
// 动态表名处理器
public class DynamicTableNameParser implements IKeyGenerator {
@Override
public String execute(String sql, String table) {
// 根据分片键计算实际表名
int index = Math.abs(orderNo.hashCode()) % 10;
return table.replace("${index}", String.valueOf(index));
}
}
8. 扩展插件开发实践
自定义插件的开发流程示例(实现数据权限过滤):
java复制@Intercepts(@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前用户权限
User user = SecurityUtils.getCurrentUser();
if (user == null || user.isAdmin()) {
return invocation.proceed();
}
// 修改SQL添加数据权限过滤
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = ((MappedStatement)invocation.getArgs[0]).getBoundSql(parameter);
String newSql = boundSql.getSql() + " AND dept_id IN (" + user.getDeptIds() + ")";
// 重置SQL
resetSql(invocation, newSql);
return invocation.proceed();
}
// 其他必要方法...
}
注册自定义插件:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new DataPermissionInterceptor());
return interceptor;
}
在实际项目开发中,我发现合理使用MyBatis-Plus的扩展点可以解决80%以上的定制化需求。比如通过自定义SQL注入器实现批量插入的优化版本,或者通过重写AbstractMethod实现特殊的查询逻辑。关键在于深入理解框架的执行流程和扩展接口。