1. MyBatis-Plus 入门与核心特性解析
MyBatis-Plus(简称MP)是国内开发者基于MyBatis二次开发的增强工具包,它在保留MyBatis所有特性的基础上,进一步简化了CRUD操作和条件构造。作为一个常年混迹Java后端开发的程序员,我第一次接触MP是在2018年一个电商项目中,当时被它简洁的API设计和强大的Wrapper条件构造器所吸引。相比原生MyBatis需要手动编写大量XML和接口方法,MP通过通用Mapper和Lambda表达式,让单表操作变得异常简单。
MP的核心优势主要体现在三个方面:首先,它内置了通用Mapper,只需让实体类继承BaseMapper接口,就能自动获得18种基础CRUD方法;其次,它提供了强大的条件构造器Wrapper,支持链式调用和Lambda表达式,让动态SQL编写变得直观;最后,MP拥有丰富的插件体系,从分页到乐观锁,从性能分析到多租户,几乎覆盖了企业级应用的所有常见需求。
提示:虽然MP功能强大,但要注意它并非要完全替代MyBatis。对于复杂多表关联查询,仍然建议使用原生MyBatis的XML映射方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与基础配置
2.1 Spring Boot 3集成MyBatis-Plus
在Spring Boot 3项目中集成MP非常简单。首先在pom.xml中添加最新依赖(截至2023年7月,最新稳定版为3.5.3.1):
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
然后在application.yml中配置基本参数:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启SQL日志
global-config:
db-config:
id-type: auto # 主键自增策略
logic-delete-field: deleted # 逻辑删除字段
logic-not-delete-value: 0
logic-delete-value: 1
2.2 实体类与Mapper配置
MP通过实体类注解实现ORM映射。以下是一个用户实体示例:
java复制@Data
@TableName("sys_user") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String username;
@TableField("real_name") // 字段映射
private String realName;
@TableField(exist = false) // 非表字段
private String tempData;
@Version // 乐观锁版本字段
private Integer version;
}
对应的Mapper接口只需继承BaseMapper:
java复制public interface UserMapper extends BaseMapper<User> {
// 可自定义复杂SQL方法
@Select("select * from sys_user where username like #{prefix}%")
List<User> findByUsernamePrefix(String prefix);
}
3. 核心功能深度解析
3.1 条件构造器Wrapper实战
Wrapper是MP最强大的特性之一,它提供了四种实现类:
- QueryWrapper:用于SELECT条件构造
- UpdateWrapper:用于UPDATE条件构造
- LambdaQueryWrapper:Lambda语法形式
- LambdaUpdateWrapper:Lambda更新形式
基本查询示例:
java复制// 传统方式
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "username")
.like("username", "张")
.between("age", 20, 30)
.orderByDesc("create_time");
// Lambda方式
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.select(User::getId, User::getUsername)
.like(User::getUsername, "张")
.between(User::getAge, 20, 30)
.orderByDesc(User::getCreateTime);
List<User> userList = userMapper.selectList(lambdaWrapper);
复杂条件示例:
java复制wrapper.nested(w -> w.eq("dept_id", 10).or().eq("dept_id", 20))
.and(w -> w.ge("salary", 5000))
.apply("date_format(create_time,'%Y-%m')={0}", "2023-07");
3.2 分页查询最佳实践
MP的分页功能需要先配置分页插件:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
实际分页查询时,创建Page对象并传入mapper方法:
java复制// 构造分页参数(当前页, 每页数量)
Page<User> page = new Page<>(1, 10);
// 可以添加查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge, 18);
// 执行分页查询
Page<User> result = userMapper.selectPage(page, wrapper);
// 获取分页数据
List<User> records = result.getRecords(); // 当前页数据
long total = result.getTotal(); // 总记录数
注意:当使用SQL Server时,分页语法与其他数据库不同。如热词中提到的
order by dict_id asc offset 0 rows fetch next 10 row就是SQL Server特有的分页语法,MP会自动根据配置的DbType生成正确的分页SQL。
4. 高级特性与实战技巧
4.1 枚举类型映射
MP支持将Java枚举与数据库字段自动转换,这比传统的@Enumerated注解更灵活。首先定义枚举:
java复制public enum GenderEnum {
MALE(1, "男"),
FEMALE(2, "女");
@EnumValue // 标记存储到数据库的值
private final int code;
private final String desc;
// constructor/getter省略
}
在实体类中使用:
java复制public class User {
// 其他字段...
private GenderEnum gender;
}
MP会自动将枚举的@EnumValue值存入数据库,查询时又会自动转换为对应枚举。这种方式比JPA的@Enumerated(EnumType.ORDINAL)更安全,因为使用的是明确指定的code值而非枚举顺序。
4.2 字段加解密拦截器
对于敏感数据如手机号、身份证号等,可以使用MP的拦截器实现自动加解密。首先实现自定义类型处理器:
java复制public class EncryptTypeHandler extends BaseTypeHandler<String> {
private final Encryptor encryptor = new AESEncryptor("密钥");
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) {
ps.setString(i, encryptor.encrypt(parameter));
}
// 其他方法实现解密逻辑...
}
在实体字段上指定处理器:
java复制@TableField(typeHandler = EncryptTypeHandler.class)
private String idCardNumber;
这样在存入数据库时会自动加密,查询时会自动解密,业务代码无需关心加解密细节。
4.3 多数据源配置
MP支持通过@DS注解实现多数据源切换。首先配置多个数据源:
java复制@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();
}
}
然后在Mapper方法或类上使用@DS注解:
java复制@DS("slave") // 类级别注解
public interface UserMapper extends BaseMapper<User> {
@DS("master") // 方法级别注解,优先级更高
User selectById(Long id);
}
5. 性能优化与问题排查
5.1 解决单页500条限制
MP默认的单页最大记录数为500,这是为了防止内存溢出。如果需要调整:
java复制// 方式1:全局配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pagination = new PaginationInnerInterceptor();
pagination.setMaxLimit(1000L); // 修改为1000
interceptor.addInnerInterceptor(pagination);
return interceptor;
}
// 方式2:单次查询覆盖
Page<User> page = new Page<>(1, 1000);
page.setMaxLimit(1000L); // 临时修改
5.2 SQL日志打印控制
开发阶段建议开启SQL日志,但要注意生产环境应关闭:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印
# 或使用SLF4J
# log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
对于特定Mapper方法的SQL日志,可以使用@Sql注解:
java复制@Sql(statement = "SELECT * FROM user WHERE age > ?",
logging = @Logging(level = LogLevel.DEBUG))
List<User> selectByAge(Integer age);
5.3 常见问题解决方案
问题1:Wrapper条件不生效
检查是否在Update操作中误用了QueryWrapper,应该使用UpdateWrapper。同时注意条件构造后是否真正传入了mapper方法。
问题2:乐观锁冲突
乐观锁需要满足三个条件:
- 实体类添加@Version字段
- 数据库有对应版本字段
- 先查询再更新(不能直接updateById)
问题3:逻辑删除异常
确保全局配置了logic-delete-field,且实体类有对应字段。注意逻辑删除后该记录将自动从查询结果中排除。
6. 扩展应用与整合实践
6.1 与Kafka集成记录变更日志
结合Kafka可以实现数据变更的实时通知:
java复制@Component
public class EntityChangeListener {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@TransactionalEventListener(fallbackExecution = true)
public void handleSaveEvent(PostInsertEvent event) {
Object entity = event.getEntity();
if (entity instanceof User) {
kafkaTemplate.send("user-topic",
"INSERT:" + JSON.toJSONString(entity));
}
}
// 类似处理Update和Delete事件
}
6.2 行为树与规则引擎整合
对于复杂业务规则,可以结合规则引擎:
java复制public class UserService {
@Autowired
private RuleEngine ruleEngine;
public void complexOperation(User user) {
// 1. 准备事实数据
Facts facts = new Facts();
facts.put("user", user);
facts.put("creditScore", getCreditScore(user.getId()));
// 2. 执行规则
ruleEngine.fire("user-rules", facts);
// 3. 根据规则结果更新
if (facts.get("result") != null) {
userMapper.updateById(user);
}
}
}
6.3 动态表名处理
对于多租户或分表场景,可以实现动态表名处理器:
java复制public class DynamicTableNameHandler implements TableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
String tenantId = TenantContext.getCurrentTenant();
if ("sys_user".equals(tableName)) {
return "sys_user_" + tenantId;
}
return tableName;
}
}
在配置中添加该拦截器:
java复制interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor(
new DynamicTableNameHandler()));
在实际项目中,我发现MP的最佳实践是:80%的单表操作使用MP简化开发,20%的复杂查询仍用原生MyBatis。这种组合既能提高开发效率,又能应对各种复杂场景。特别是在快速迭代的业务初期,MP的条件构造器可以大大减少因需求变更导致的代码修改量。
