1. 为什么选择MybatisPlus?
作为Java开发者,我们经常需要与数据库打交道。传统的JDBC操作繁琐且容易出错,而MyBatis虽然简化了数据库操作,但仍然需要编写大量SQL和映射文件。这时MybatisPlus(简称MP)应运而生,它是在MyBatis基础上进行扩展的增强工具,保留了MyBatis的所有特性,同时提供了更多便捷功能。
我在实际项目中使用MybatisPlus已有三年多时间,最初是从一个老项目迁移过来的。当时团队对是否引入这个新工具存在争议,但经过对比测试后,我们发现开发效率提升了至少40%。特别是对于常规的CRUD操作,从原来的每个表都需要写接口、写实现、写XML,变成了现在几行代码就能搞定。
提示:MybatisPlus不是要替代MyBatis,而是在其基础上提供更多开箱即用的功能,两者可以无缝集成。
1.1 MybatisPlus的核心优势
- 无侵入性:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本CRUD,性能基本无损耗
- 强大的CRUD操作:内置通用Mapper、Service,少量配置即可实现单表CRUD操作
- 支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件
- 支持主键自动生成:支持多达4种主键策略
- 内置分页插件:基于MyBatis物理分页,开发者无需关心具体实现
1.2 适用场景分析
根据我的经验,MybatisPlus特别适合以下场景:
- 新项目开发,特别是需要快速迭代的项目
- 需要大量基础CRUD操作的管理系统
- 团队开发规范不统一,需要统一数据访问层标准
- 需要动态SQL但又不希望写太多XML配置
不过也要注意,对于特别复杂的多表关联查询,或者需要高度定制化的SQL,可能还是需要结合原生MyBatis的方式来实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础配置
2.1 创建SpringBoot项目
我推荐使用IDEA的Spring Initializr来创建项目,这是最方便的方式。以下是具体步骤:
- 打开IDEA,选择File > New > Project
- 选择Spring Initializr,填写项目基本信息
- 在Dependencies中添加:
- Spring Web (用于构建web应用)
- MyBatis Framework (基础框架)
- MySQL Driver (数据库驱动)
创建完成后,我们需要手动添加MybatisPlus的依赖。在pom.xml中添加:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
注意:版本号建议使用最新稳定版,可以通过Maven中央仓库查询最新版本。
2.2 数据库配置
在application.properties或application.yml中配置数据库连接信息。我更喜欢使用yml格式,因为结构更清晰:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=UTC
username: root
password: 123456
2.3 基本配置类
MybatisPlus需要一些基本配置才能正常工作。创建一个配置类:
java复制@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
这个配置类做了两件重要的事情:
- 通过@MapperScan指定Mapper接口的扫描路径
- 添加了分页插件,这是MP非常实用的功能之一
3. 快速开始:基础CRUD操作
3.1 创建实体类
假设我们有一个用户表user,对应的实体类如下:
java复制@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
这里有几个关键注解:
- @TableName:指定表名
- @TableId:指定主键,IdType.AUTO表示数据库自增
- @Data:Lombok注解,自动生成getter/setter等方法
3.2 创建Mapper接口
与传统MyBatis不同,MP的Mapper接口只需要继承BaseMapper即可获得基本CRUD方法:
java复制public interface UserMapper extends BaseMapper<User> {
// 不需要写任何方法,基本的CRUD操作已经由BaseMapper提供
}
3.3 Service层实现
MP还提供了强大的Service封装,我们可以这样使用:
java复制public interface IUserService extends IService<User> {
// 可以在这里定义额外的方法
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
// 实现类也非常简洁
}
3.4 基础CRUD示例
现在我们可以进行各种CRUD操作了:
java复制@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
// 新增
@PostMapping
public boolean saveUser(@RequestBody User user) {
return userService.save(user);
}
// 删除
@DeleteMapping("/{id}")
public boolean removeUser(@PathVariable Long id) {
return userService.removeById(id);
}
// 修改
@PutMapping
public boolean updateUser(@RequestBody User user) {
return userService.updateById(user);
}
// 查询单个
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.getById(id);
}
// 分页查询
@GetMapping("/page")
public IPage<User> page(@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size) {
Page<User> page = new Page<>(current, size);
return userService.page(page);
}
}
这些代码已经实现了完整的CRUD功能,而且非常简洁。MP会自动生成对应的SQL语句,我们不需要编写任何XML映射文件。
4. 高级特性与实战技巧
4.1 条件构造器
MP提供了强大的条件构造器,可以方便地构建查询条件。以下是一些常用示例:
java复制// 查询年龄大于18且名字包含"张"的用户
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.gt(User::getAge, 18)
.like(User::getName, "张");
List<User> users = userService.list(queryWrapper);
// 更新操作
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(User::getName, "张三")
.set(User::getAge, 20);
userService.update(updateWrapper);
Lambda表达式的方式避免了硬编码字段名,更加安全可靠。
4.2 自动填充功能
在实际项目中,我们经常需要自动填充创建时间、更新时间等字段。MP提供了@TableField注解来实现:
java复制@Data
@TableName("user")
public class User {
// ...其他字段
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
然后需要实现MetaObjectHandler接口:
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());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
4.3 分页查询优化
MP的分页功能非常强大,但在实际使用中有几个优化点:
- 性能优化:对于大数据量表,可以关闭count查询
java复制Page<User> page = new Page<>(current, size);
page.setSearchCount(false); // 不进行count查询
- 自定义分页SQL:对于复杂查询,可以自定义分页SQL
java复制@Select("select * from user where age > #{age}")
IPage<User> selectPageByAge(IPage<User> page, @Param("age") Integer age);
- 多表联查分页:可以使用JOIN查询配合自定义SQL实现
4.4 逻辑删除
在实际项目中,我们通常不会真正删除数据,而是使用逻辑删除。MP对此有很好的支持:
- 在表中添加deleted字段
- 在实体类中添加:
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 # 逻辑未删除值
配置完成后,所有的删除操作都会自动变成更新操作,将deleted字段设置为1。查询时也会自动过滤掉已删除的记录。
4.5 性能分析插件
在开发阶段,我们可以启用性能分析插件来监控SQL执行情况:
java复制@Bean
@Profile({"dev", "test"}) // 只在开发和测试环境生效
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor interceptor = new PerformanceInterceptor();
interceptor.setMaxTime(1000); // SQL执行最大时长,超过自动停止运行
interceptor.setFormat(true); // 是否格式化SQL
return interceptor;
}
这个插件会输出每条SQL的执行时间,帮助我们发现性能问题。
5. 常见问题与解决方案
5.1 字段映射问题
在实际开发中,经常会遇到数据库字段名和实体类属性名不一致的情况。MP提供了几种解决方案:
- 使用@TableField注解指定数据库字段名:
java复制@TableField("db_column_name")
private String javaFieldName;
- 全局配置下划线转驼峰命名:
yaml复制mybatis-plus:
configuration:
map-underscore-to-camel-case: true
- 实现自己的命名策略(高级用法)
5.2 批量操作性能
MP默认的批量插入实际上是循环执行单条插入,性能不高。对于真正的批量插入,可以采用以下方案:
- 使用原生MyBatis的批量操作:
java复制@Autowired
private SqlSessionFactory sqlSessionFactory;
public void batchInsert(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
session.commit();
}
}
- 使用MP的saveBatch方法,并合理设置batchSize:
java复制userService.saveBatch(users, 1000); // 每1000条提交一次
5.3 多数据源支持
在微服务架构中,我们经常需要连接多个数据库。MP对多数据源有很好的支持:
- 添加dynamic-datasource-spring-boot-starter依赖
- 配置多个数据源:
yaml复制spring:
datasource:
dynamic:
primary: master # 设置默认数据源
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1
username: root
password: 123456
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2
username: root
password: 123456
- 在Service或Mapper上使用@DS注解指定数据源:
java复制@DS("slave") // 指定使用slave数据源
public interface UserMapper extends BaseMapper<User> {
}
5.4 枚举类型处理
MP支持将数据库字段映射为Java枚举类型。假设我们有一个用户状态枚举:
java复制public enum UserStatus {
NORMAL(1, "正常"),
LOCKED(2, "锁定");
private final int code;
private final String desc;
// 构造方法、getter等
}
在实体类中可以这样使用:
java复制@TableField("status")
private UserStatus status;
为了让MP知道如何转换枚举值,我们需要配置枚举处理器:
java复制@Bean
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
return properties -> {
GlobalConfig globalConfig = properties.getGlobalConfig();
globalConfig.setEnumTypeHandler(MybatisEnumTypeHandler.class);
};
}
5.5 乐观锁实现
在高并发场景下,我们需要使用乐观锁来防止数据冲突。MP提供了@Version注解来实现乐观锁:
- 在表中添加version字段
- 在实体类中添加:
java复制@Version
private Integer version;
- 在配置中启用乐观锁插件:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
使用乐观锁后,更新操作会自动检查version值,如果与数据库中的不一致,就会抛出异常。
6. 最佳实践与项目经验
6.1 项目结构规范
经过多个项目的实践,我总结出一个比较合理的项目结构:
code复制src/main/java
├── com.example
│ ├── config # 配置类
│ ├── controller # 控制器
│ ├── entity # 实体类
│ ├── enums # 枚举类
│ ├── mapper # Mapper接口
│ ├── service # 服务接口
│ │ ├── impl # 服务实现
│ ├── util # 工具类
│ └── vo # 视图对象
这种结构清晰明了,适合大多数中小型项目。对于大型项目,可以考虑按功能模块进一步划分。
6.2 代码生成器
MP提供了一个强大的代码生成器,可以快速生成实体类、Mapper、Service等代码。配置示例如下:
java复制FastAutoGenerator.create("jdbc:mysql://localhost:3306/mp_demo", "root", "123456")
.globalConfig(builder -> {
builder.author("author") // 设置作者
.outputDir("D://code"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.example") // 设置父包名
.moduleName("system") // 设置模块名
.entity("entity") // 实体类包名
.service("service") // Service包名
.mapper("mapper"); // Mapper包名
})
.strategyConfig(builder -> {
builder.addInclude("user", "role") // 设置需要生成的表名
.entityBuilder()
.enableLombok() // 启用Lombok
.serviceBuilder()
.formatServiceFileName("%sService"); // 服务接口命名规则
})
.execute();
这个生成器可以节省大量重复劳动,特别是在新项目开始时。我通常会根据项目需求对其进行定制,比如添加自定义注释模板等。
6.3 多租户实现
在SAAS系统中,多租户是一个常见需求。MP提供了多租户插件支持:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从上下文中获取租户ID
return new LongValue(1L);
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 指定哪些表不需要过滤租户
return !Arrays.asList("user", "role").contains(tableName);
}
}));
return interceptor;
}
配置完成后,所有的SQL操作都会自动加上租户过滤条件。
6.4 审计功能
对于需要记录操作人、操作时间的系统,MP提供了审计功能:
- 在实体类中添加审计字段:
java复制@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
- 实现AuditorAware接口获取当前用户:
java复制@Component
public class MyAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// 从安全上下文中获取当前用户名
return Optional.of(SecurityUtils.getCurrentUsername());
}
}
- 在配置类中启用审计功能:
java复制@Bean
public MybatisPlusAuditInterceptor mybatisPlusAuditInterceptor() {
return new MybatisPlusAuditInterceptor();
}
6.5 性能优化建议
- 合理使用索引:虽然MP简化了开发,但数据库索引仍然需要手动优化
- 避免N+1查询:使用@TableField(exist = false)标记非数据库字段,避免自动查询
- 批量操作:尽量使用批量操作方法,减少数据库交互次数
- 缓存策略:结合Spring Cache或Redis实现二级缓存
- SQL监控:使用MP的性能分析插件或第三方工具监控SQL性能
在实际项目中,我通常会结合Arthas等工具进行性能分析,找出真正的瓶颈所在。
