作为一名长期使用MyBatis的开发者,第一次接触MyBatisPlus时的感受可以用"相见恨晚"来形容。这个基于MyBatis的增强工具包,确实能大幅提升日常开发效率。下面我将结合自己多年的实战经验,带大家深入掌握这个利器。
我们先从最基础的数据库搭建开始。建议使用UTF-8编码以避免中文乱码问题:
sql复制CREATE DATABASE IF NOT EXISTS mybatisplus_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE mybatisplus_db;
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32) NOT NULL COMMENT '用户名',
password VARCHAR(64) NOT NULL COMMENT '加密后的密码',
age INT NOT NULL COMMENT '年龄',
tel VARCHAR(20) NOT NULL COMMENT '电话号码',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标记'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 初始化测试数据
INSERT INTO user VALUES
(1,'Tom','$2a$10$xVCH4IAZwQcLzYf8pB9WXe',23,'13800138000',NOW(),NOW(),0),
(2,'Jerry','$2a$10$yH8eJZ5kzPqR2nW7pB9WXe',25,'13900139000',NOW(),NOW(),0);
注意:实际项目中密码应该存储加密后的值,这里使用BCrypt加密示例
使用IDEA创建SpringBoot工程时,我习惯这样选择依赖:
创建完成后,需要手动添加MyBatisPlus的依赖:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
application.yml的配置我通常这样写:
yaml复制spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: yourpassword
druid:
initial-size: 5
min-idle: 5
max-active: 20
test-on-borrow: true
validation-query: SELECT 1
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境开启SQL日志
map-underscore-to-camel-case: true # 自动转换下划线命名到驼峰命名
global-config:
db-config:
id-type: auto # 主键策略
logic-delete-field: deleted # 逻辑删除字段
logic-not-delete-value: 0
logic-delete-value: 1
使用Lombok简化实体类:
java复制@Data
@TableName("user") // 可省略,默认表名与类名相同
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic
private Integer deleted;
}
Mapper接口只需要简单继承BaseMapper:
java复制@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
// 所有基础CRUD方法都已包含
}
编写测试类验证基础功能:
java复制@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testInsert() {
User user = new User();
user.setName("测试用户");
user.setAge(28);
user.setTel("18888888888");
user.setPassword(BCrypt.hashpw("123456", BCrypt.gensalt()));
int result = userMapper.insert(user);
assertEquals(1, result);
assertNotNull(user.getId());
}
@Test
void testSelect() {
List<User> users = userMapper.selectList(null);
assertFalse(users.isEmpty());
users.forEach(System.out::println);
}
}
MyBatisPlus的所有增强功能都是在MyBatis基础上进行的扩展,不会对现有工程产生影响。这意味着:
BaseMapper中内置了常用的单表操作方法:
| 方法名 | 功能描述 | 对应SQL |
|---|---|---|
| insert | 插入一条记录 | INSERT INTO... |
| deleteById | 根据ID删除 | DELETE FROM...WHERE id=# |
| updateById | 根据ID更新 | UPDATE...WHERE id=# |
| selectById | 根据ID查询 | SELECT...WHERE id=# |
| selectList | 查询所有记录 | SELECT... |
| selectPage | 分页查询 | SELECT...LIMIT... |
MyBatisPlus支持多种主键生成策略:
java复制public enum IdType {
AUTO(0), // 数据库ID自增
NONE(1), // 无状态,需要手动设置
INPUT(2), // 用户输入ID
ASSIGN_ID(3), // 分配ID(默认雪花算法)
ASSIGN_UUID(4); // 分配UUID
//...
}
建议:
对于创建时间、更新时间等通用字段,可以使用自动填充:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
java复制@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
通过简单配置即可实现逻辑删除:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段
logic-not-delete-value: 0 # 未删除值
logic-delete-value: 1 # 删除值
或者在实体类字段上直接注解:
java复制@TableLogic
private Integer deleted;
当调用deleteById等方法时,实际执行的是UPDATE语句而非DELETE。
MyBatisPlus提供了强大的条件构造器,支持链式调用:
java复制// 查询年龄大于20且小于30的用户
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
query.gt(User::getAge, 20)
.lt(User::getAge, 30)
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(query);
java复制// 查询(年龄小于20或大于30)且姓张的用户
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
query.nested(q -> q.lt(User::getAge, 20).or().gt(User::getAge, 30))
.likeRight(User::getName, "张");
List<User> users = userMapper.selectList(query);
生成的SQL:
sql复制SELECT * FROM user
WHERE (age < 20 OR age > 30)
AND name LIKE '张%'
java复制public List<User> queryUsers(String name, Integer minAge, Integer maxAge) {
return userMapper.selectList(new LambdaQueryWrapper<User>()
.like(StringUtils.isNotBlank(name), User::getName, name)
.ge(minAge != null, User::getAge, minAge)
.le(maxAge != null, User::getAge, maxAge)
);
}
MyBatisPlus提供了完善的分页支持:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 使用示例
@Test
void testPageQuery() {
Page<User> page = new Page<>(1, 5); // 当前页,每页大小
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>()
.orderByDesc(User::getCreateTime);
Page<User> result = userMapper.selectPage(page, query);
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页数据:" + result.getRecords());
}
对于复杂SQL,可以自定义分页查询:
java复制@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE age > #{param1} ORDER BY create_time DESC")
Page<User> selectByAgePage(Page<User> page, @Param("age") int age);
}
java复制Page<User> page = new Page<>(1, 10);
Page<User> result = userMapper.selectByAgePage(page, 20);
java复制// 只查询需要的字段
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>()
.select(User::getId, User::getName, User::getAge);
java复制// 确保条件字段有索引
query.eq(User::getName, "张三")
.orderByAsc(User::getCreateTime);
java复制// 批量插入
List<User> userList = ...;
userMapper.insertBatchSomeColumn(userList); // 需要安装mybatis-plus-extension
yaml复制mybatis-plus:
configuration:
cache-enabled: true
java复制@CacheNamespace // 在Mapper接口上添加
public interface UserMapper extends BaseMapper<User> {
// ...
}
解决方案:
java复制@TableField("db_column_name") // 指定数据库字段名
private String javaFieldName;
解决方案:
java复制@TableField(exist = false) // 标记为非数据库字段
private String tempField;
解决方案:
java复制@TableId(type = IdType.ASSIGN_ID) // 使用雪花算法
private Long id;
java复制// 如果需要查询包含已删除的数据
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
query.eq(User::getDeleted, 1); // 明确指定查询条件
sql复制-- 自定义SQL时记得加上删除条件
SELECT u.*, d.name
FROM user u LEFT JOIN department d ON u.dept_id = d.id
WHERE u.deleted = 0 AND d.deleted = 0
对于复杂的多表查询,建议:
java复制@Select("<script>" +
"SELECT u.*, d.name as deptName FROM user u " +
"LEFT JOIN department d ON u.dept_id = d.id " +
"WHERE 1=1 " +
"<if test='name != null'> AND u.name LIKE CONCAT('%',#{name},'%') </if>" +
"<if test='deptId != null'> AND u.dept_id = #{deptId} </if>" +
"</script>")
List<UserDTO> selectUserList(@Param("name") String name, @Param("deptId") Long deptId);
MyBatisPlus提供了强大的代码生成器,可以快速生成Entity、Mapper、Service、Controller等代码:
java复制public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
gc.setAuthor("YourName");
gc.setOpen(false);
gc.setSwagger2(true);
generator.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?useSSL=false");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("yourpassword");
generator.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.example");
pc.setModuleName("system");
generator.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("user", "department"); // 要生成的表
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
generator.setStrategy(strategy);
generator.execute();
}
}
对于SaaS系统,可以通过MyBatisPlus的租户处理器实现多租户隔离:
java复制@Component
public class MyTenantLineHandler implements TenantLineHandler {
@Override
public Expression getTenantId() {
// 从当前上下文中获取租户ID
return new LongValue(SecurityUtils.getTenantId());
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要租户隔离的表
return "tenant".equals(tableName)
|| "common_config".equals(tableName);
}
}
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler()));
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
对于创建人、创建时间等审计字段,可以通过MetaObjectHandler实现自动填充:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "createBy", String.class, SecurityUtils.getCurrentUsername());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateBy", String.class, SecurityUtils.getCurrentUsername());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
this.strictUpdateFill(metaObject, "updateBy", String.class, SecurityUtils.getCurrentUsername());
}
}
经过多个项目的实践验证,我总结了以下MyBatisPlus的最佳实践:
分层清晰:
合理使用Wrapper:
性能优化:
代码规范:
安全考虑:
测试覆盖:
在实际项目中,MyBatisPlus确实能大幅提升开发效率,但也需要注意不要过度依赖其自动化功能,对于复杂业务场景,适当结合原生MyBatis的特性会获得更好的灵活性和性能表现。