第一次接触MyBatis-Plus时,我被它简洁的API设计震撼到了。记得当时接手一个老项目,看到DAO层密密麻麻的XML文件和几乎雷同的接口方法,改个字段名要动七八个地方。后来引入MyBatis-Plus后,同样的功能代码量减少了60%以上。今天我就带大家系统掌握这个"MyBatis增强工具包"的查询体系,从最基础的CRUD到Lambda表达式的高级玩法。
MyBatis-Plus的核心价值在于:它既保留了MyBatis对SQL的精细控制能力,又通过智能化的默认行为消除了大部分模板代码。对于Java后端开发者来说,这意味着你可以用更少的代码完成更多的工作,同时还能享受编译期类型检查带来的安全性。下面我们就从最基础的BaseMapper开始,逐步深入它的查询体系。
在传统MyBatis开发中,我们经常遇到这样的场景:每新建一个实体类,就要配套编写对应的Mapper接口和XML文件,哪怕只是最简单的增删改查操作。我曾经维护过一个有50多个实体类的项目,光是基础的CRUD方法就写了近千行重复代码。这种开发模式存在几个明显问题:
MyBatis-Plus通过BaseMapper接口完美解决了这些问题。它内置了17个通用CRUD方法,覆盖了90%以上的单表操作场景。我们只需要让自己的Mapper接口继承BaseMapper,就能立即获得这些方法,无需任何额外配置。
BaseMapper提供了完整的CRUD操作方法,我们可以将其分为几个类别:
java复制int insert(T entity); // 插入一条记录
这个方法会自动将实体对象插入到对应的数据库表中。需要注意的是:
java复制int deleteById(Serializable id); // 根据主键删除
int deleteByMap(@Param("cm") Map<String, Object> columnMap); // 根据columnMap条件删除
int delete(@Param("ew") Wrapper<T> wrapper); // 根据entity条件删除
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList); // 批量删除
实际项目中,deleteById和deleteBatchIds使用频率最高。特别是deleteBatchIds,它生成的SQL是DELETE FROM table WHERE id IN (?,?,?),比循环调用deleteById效率高得多。
java复制int updateById(@Param("et") T entity); // 根据ID修改
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper); // 根据条件更新
updateById是最常用的更新方法,它会根据实体类的主键值更新对应记录。而带Wrapper参数的update方法则可以实现更复杂的更新逻辑。
java复制T selectById(Serializable id); // 根据主键查询
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList); // 批量查询
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap); // 根据columnMap查询
T selectOne(@Param("ew") Wrapper<T> queryWrapper); // 查询一条记录
Integer selectCount(@Param("ew") Wrapper<T> queryWrapper); // 查询总数
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper); // 查询列表
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper); // 查询map列表
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper); // 查询特定字段
查询方法是BaseMapper中最丰富的部分,从简单的ID查询到复杂的条件查询一应俱全。其中selectOne需要特别注意:如果查询结果有多条记录,它会抛出异常而不是返回第一条。
要让BaseMapper正常工作,我们需要正确配置实体类与数据库表的映射关系。以下是一个完整的实体类示例:
java复制@Data
@TableName("sys_user") // 指定表名
public class User {
@TableId(value = "id", type = IdType.AUTO) // 主键配置
private Long id;
@TableField("username") // 字段映射
private String name;
private Integer age;
@TableField(exist = false) // 非表字段
private String tempValue;
}
关键注解说明:
@TableName:指定实体类对应的表名,默认是类名转下划线@TableId:标识主键字段,可配置主键生成策略@TableField:配置字段映射关系,常用属性:
value:数据库字段名exist:是否为数据库字段(false表示不参与SQL)fill:字段自动填充策略(后面会详细讲解)注意:当实体类属性名与数据库字段名遵循驼峰转下划线规则时(如userName对应user_name),可以省略@TableField注解。
QueryWrapper是MyBatis-Plus提供的条件构造器,它允许我们以面向对象的方式构建查询条件。先看一个简单例子:
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age", 20)
.like("name", "张")
.between("create_time", startDate, endDate)
.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
这段代码生成的SQL相当于:
sql复制SELECT * FROM user
WHERE age = 20
AND name LIKE '%张%'
AND create_time BETWEEN ? AND ?
ORDER BY id DESC
QueryWrapper常用的条件方法包括:
| 方法名 | 说明 | SQL等价 |
|---|---|---|
| eq | 等于 | = |
| ne | 不等于 | <> |
| gt | 大于 | > |
| ge | 大于等于 | >= |
| lt | 小于 | < |
| le | 小于等于 | <= |
| between | BETWEEN值1 AND值2 | BETWEEN...AND... |
| like | LIKE '%值%' | LIKE |
| likeLeft | LIKE '%值' | LIKE |
| likeRight | LIKE '值%' | LIKE |
| in | 字段IN (值1,值2,...) | IN |
| groupBy | 分组 | GROUP BY |
| orderByAsc | 升序排序 | ORDER BY...ASC |
| orderByDesc | 降序排序 | ORDER BY...DESC |
QueryWrapper虽然方便,但它使用字符串表示字段名,容易拼写错误且IDE无法智能提示。LambdaQueryWrapper通过Lambda表达式解决了这个问题:
java复制LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getAge, 20)
.like(User::getName, "张")
.between(User::getCreateTime, startDate, endDate);
这种写法的优势非常明显:
实际开发中,我强烈推荐使用LambdaQueryWrapper。虽然写法稍微复杂一点,但带来的安全性和可维护性提升是值得的。
让我们看几个实际项目中的复杂查询示例:
案例1:多条件动态查询
java复制public List<User> searchUsers(String name, Integer minAge, Integer maxAge, Integer status) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(name)) {
wrapper.like(User::getName, name);
}
if (minAge != null) {
wrapper.ge(User::getAge, minAge);
}
if (maxAge != null) {
wrapper.le(User::getAge, maxAge);
}
if (status != null) {
wrapper.eq(User::getStatus, status);
}
return userMapper.selectList(wrapper);
}
案例2:子查询
java复制LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.inSql(User::getId, "SELECT user_id FROM user_role WHERE role_id = 1");
案例3:SELECT指定字段
java复制LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getName)
.eq(User::getStatus, 1);
要使用MyBatis-Plus的分页功能,需要先配置分页插件:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
配置好插件后,就可以使用Page对象进行分页查询了:
java复制// 第一页,每页10条
Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.orderByDesc(User::getCreateTime);
IPage<User> userPage = userMapper.selectPage(page, wrapper);
System.out.println("总页数:" + userPage.getPages());
System.out.println("总记录数:" + userPage.getTotal());
List<User> records = userPage.getRecords();
对于复杂的多表关联查询,我们可以自定义SQL并配合Page对象使用:
Mapper接口:
java复制@Select("SELECT u.* FROM user u LEFT JOIN department d ON u.dept_id = d.id ${ew.customSqlSegment}")
IPage<User> selectUserPage(Page<User> page, @Param("ew") LambdaQueryWrapper<User> wrapper);
调用方式:
java复制Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.like(User::getName, "张")
.orderByDesc(User::getCreateTime);
IPage<User> userPage = userMapper.selectUserPage(page, wrapper);
select *saveBatch,批量删除使用deleteBatchIdscount(1),可以考虑缓存总数问题1:更新操作没有生效
问题2:LambdaQueryWrapper编译错误
问题3:分页结果不正确
经过多个项目的实践验证,MyBatis-Plus确实能显著提升开发效率。特别是在快速迭代的业务场景中,它让我们可以更专注于业务逻辑而不是重复的CRUD代码。对于新项目,我会毫不犹豫地选择MyBatis-Plus作为持久层框架;对于老项目,也可以逐步引入,先从简单的查询开始替换,逐步享受它带来的便利。