1. MyBatis-Plus 快速入门与核心用法详解
作为一名长期奋战在Java开发一线的老兵,我深知ORM框架在项目中的重要性。今天要分享的是MyBatis-Plus(简称MP)这个让无数开发者"真香"的框架。不同于传统的MyBatis需要编写大量重复的CRUD代码,MP在保持MyBatis灵活性的同时,通过智能化的自动生成机制,让单表操作变得异常简单。下面我将结合自己多个微服务项目的实战经验,带你全面掌握MP的核心用法。
2. 环境准备与基础配置
2.1 数据库连接配置
在Spring Boot项目中,我们首先需要在application.yml中配置数据源。这里有个细节需要注意:MySQL 8.0+的驱动类和连接参数与5.x版本有所不同。
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: your_password
hikari:
maximum-pool-size: 20
minimum-idle: 5
提示:生产环境务必使用连接池,HikariCP是Spring Boot默认的也是性能最好的选择。timezone参数对于处理日期时间字段特别重要,否则可能遇到时区转换问题。
2.2 依赖引入
MP的starter已经包含了MyBatis的核心依赖,所以可以直接替换掉原来的mybatis-spring-boot-starter:
xml复制<dependencies>
<!-- MyBatis-Plus核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 其他必要依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3. 快速开始:从零搭建MP项目
3.1 实体类定义
MP通过实体类自动映射数据库表,这是它的核心魔法之一。我们先定义一个用户实体:
java复制@Data
@TableName("sys_user") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
@TableField("username") // 字段映射
private String name;
private Integer age;
@TableField(exist = false) // 非数据库字段
private String tempInfo;
}
这里有几个关键点:
- @Data是Lombok注解,自动生成getter/setter
- @TableName指定表名,默认是类名转下划线
- @TableId声明主键,IdType.AUTO表示数据库自增
- @TableField用于字段特殊映射
3.2 Mapper接口开发
传统MyBatis需要写XML或注解SQL,而MP只需要继承BaseMapper:
java复制public interface UserMapper extends BaseMapper<User> {
// 这里可以添加自定义方法
User selectByUsername(@Param("name") String name);
}
对应的XML文件(如果需要复杂SQL):
xml复制<!-- resources/mapper/UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByUsername" resultType="User">
SELECT * FROM sys_user WHERE username = #{name}
</select>
</mapper>
4. 核心注解深度解析
4.1 @TableName详解
这个注解用于处理表名映射的特殊情况:
java复制@TableName(value = "sys_user", schema = "business", keepGlobalPrefix = true)
public class User {
// ...
}
- value:实际表名
- schema:数据库schema(多租户场景有用)
- keepGlobalPrefix:是否保持全局表前缀
4.2 @TableId主键策略
MP支持多种主键生成策略,这是实际项目中最容易踩坑的地方之一:
java复制@TableId(type = IdType.ASSIGN_ID)
private Long id;
常用策略对比:
| 策略类型 | 说明 | 适用场景 |
|---|---|---|
| AUTO | 数据库自增 | MySQL等支持自增的数据库 |
| INPUT | 手动输入 | 需要业务控制ID的场景 |
| ASSIGN_ID | 雪花算法 | 分布式系统(默认) |
| ASSIGN_UUID | UUID | 需要字符串主键的场景 |
经验:分布式系统推荐使用ASSIGN_ID,单机可以用AUTO。注意ASSIGN_ID生成的是Long类型,前端处理大数时可能需要转为字符串。
4.3 @TableField高级用法
这个注解功能非常丰富,下面列举几个实用场景:
- 字段名映射
java复制@TableField("email_address")
private String email;
- 非表字段标记
java复制@TableField(exist = false)
private List<Role> roles;
- 条件构造时忽略
java复制@TableField(condition = SqlCondition.LIKE)
private String name;
- 自动填充策略
java复制@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
5. 全局配置与最佳实践
5.1 全局配置示例
在application.yml中添加MP配置:
yaml复制mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.example.entity
global-config:
db-config:
id-type: assign_id # 全局主键策略
logic-delete-field: deleted # 逻辑删除字段
logic-delete-value: 1 # 删除值
logic-not-delete-value: 0 # 未删除值
banner: false # 关闭启动banner
5.2 分页插件配置
分页是企业应用的刚需,MP提供了优雅的实现:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
使用示例:
java复制Page<User> page = new Page<>(1, 10); // 第1页,每页10条
Page<User> result = userMapper.selectPage(page, null);
5.3 性能优化建议
- 批量操作使用Service的saveBatch方法,比循环insert效率高很多
- 复杂查询建议使用QueryWrapper而不是LambdaQueryWrapper,后者有反射开销
- 实体类尽量使用包装类型而非基本类型,便于处理NULL值
- 逻辑删除字段记得在所有查询条件中自动带上"deleted=0"
6. 复杂查询与条件构造器
6.1 QueryWrapper基础用法
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "age")
.like("name", "张")
.between("age", 20, 30)
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
6.2 Lambda表达式写法
Java8+推荐使用Lambda方式,避免硬编码字段名:
java复制LambdaQueryWrapper<User> lambdaQuery = Wrappers.lambdaQuery();
lambdaQuery.select(User::getId, User::getName)
.like(User::getName, "王")
.gt(User::getAge, 25);
6.3 复杂条件组合
java复制wrapper.and(w -> w.eq("dept_id", 1).or().eq("dept_id", 2))
.or()
.apply("date_format(create_time,'%Y-%m')={0}", "2023-01");
7. 高级特性与实战技巧
7.1 自动填充功能
实现MetaObjectHandler接口来处理创建时间、更新时间等通用字段:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
7.2 逻辑删除实现
配置逻辑删除后,MP会自动在所有查询中加上删除标记条件:
java复制@TableLogic
private Integer deleted;
删除操作会变成更新:
sql复制UPDATE user SET deleted=1 WHERE id=? AND deleted=0
7.3 多租户支持
SAAS系统常用功能,通过TenantLineInnerInterceptor实现:
java复制interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
return new LongValue(1L); // 实际从上下文中获取
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
return !Arrays.asList("user", "order").contains(tableName);
}
}));
8. 常见问题排查与性能优化
8.1 典型问题解决方案
- ID类型不匹配:ASSIGN_ID生成的是19位Long,前端JS可能精度丢失,需要转为字符串
- 字段映射失败:检查@TableField注解,数据库字段是否存在
- 分页不生效:确认是否配置了分页插件
- 逻辑删除无效:检查全局配置和实体类注解是否一致
8.2 SQL性能监控
开启MP的SQL日志分析:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
对于生产环境,建议使用P6Spy进行详细SQL监控:
xml复制<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
配置spy.properties:
properties复制module.log=com.p6spy.engine.logging.P6LogFactory
driverlist=com.mysql.cj.jdbc.Driver
dateformat=yyyy-MM-dd HH:mm:ss
outagedetection=true
outagedetectioninterval=30
8.3 缓存整合策略
虽然MP本身不提供缓存,但可以很好与Spring Cache或Redis集成:
java复制@CacheConfig(cacheNames = "user")
public interface UserMapper extends BaseMapper<User> {
@Cacheable(key = "#id")
@Override
User selectById(Serializable id);
@CacheEvict(allEntries = true)
default void clearCache() {}
}
9. 微服务中的实践建议
在Spring Cloud微服务架构中使用MP时,有几个特别需要注意的点:
- 分布式ID冲突:确保每个服务配置不同的workerId
- Feign调用序列化:自定义ObjectMapper处理Long类型
- 多数据源配置:使用@DS注解实现动态数据源切换
- 分库分表整合:结合ShardingSphere等中间件
多数据源配置示例:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.order", sqlSessionTemplateRef = "orderSqlSessionTemplate")
public class OrderDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.order")
public DataSource orderDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory orderSqlSessionFactory(@Qualifier("orderDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/order/*.xml"));
return factory.getObject();
}
@Bean
public SqlSessionTemplate orderSqlSessionTemplate(
@Qualifier("orderSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
在微服务实践中,我建议将MP的通用配置提取到单独的starter模块中,各服务通过引入该模块来保持配置一致,避免重复劳动。同时要注意控制实体类的膨胀,避免将DTO、VO等概念与实体类混为一谈。
MP虽然强大,但也不是银弹。对于特别复杂的查询场景,或者需要高度优化的SQL,还是应该回归到原生MyBatis的方式,直接编写XML映射文件。在项目初期就做好架构设计,明确哪些场景使用MP的自动CRUD,哪些场景需要手写SQL,这样才能发挥框架的最大价值。