Skip to content

Mybatis Plus 学习


框架搭建

引入MybatisPlus的依赖

MybatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus中的所有功能,并且实现了自动装配效果,因此我们可以用MyBatisPlus的starter代替Mybatis的starter

    <!-- MyBatis-Plus 核心依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3</version>
    </dependency>

定义Mapper

自定义的Mapper继承MybatisPlus提供的BaseMapper接口:

public interface UserMapper extends BaseMapper<User>{ 
}

alt text

常见的注解

MybatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息

alt text

@TableName:用来指定表名

@TableId:用来指定表中的主键字段信息。

alt text

@TableId(type = IdType.AUTO)
private Long id;

@TableField:用来指定表中的普通字段信息。

使用场景:

  • 成员变量与数据库字段名不一致
  • 成员变量名以is开头,且是布尔值
  • 成员变量与数据库关键字冲突
  • 成员变量不是数据库字段 举例:@TableField(exist = false)

条件构造器

MybatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求

alt text

Wrapper 体系介绍

alt text

  • Wrapper:条件构造抽象类,最顶端父类
    • AbstractWrapper:用于查询条件封装,生成sql的where条件
      • QueryWrapper:查询条件封装
      • UpdateWrapper:update条件封装
      • AbstractLambdaWrapper:使用Lambda语法
        • LambdaQueryWrapper:用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper:用于Lambda语法更新Wrapper

构造器常用方法

函数名说明说明/例子
eq等于=例:eq(“name”,“zhangsan”) —> name = ‘zhangsan’
ne不等于<>例:ne(“name”,“zhangsan”) —> name <> ‘zhangsan’
gt大于>例:gt(“age”,18) —> age > 18
ge大于等于>=例:ge(“age”,18) —> age >= 18
lt小于<例:lt(“age”,18) —> age < 18
le小于等于<=例:le(“age”,18) —> age <= 18
betweenbetween 值1 and 值2例:between(“age”,10,20) —> age between 10 and 20
notBetweennot between 值1 and 值2例:notBetween(“age”,10,20) —> age not between 10 and 20
likelike ‘%值%’例:like(“name”,“强”) —> name like ‘%强%’
notLikenot like ‘%值%’例:notLike(“name”,“强”) —> name not like ‘%强%’
likeLeftlike ‘%值’例:likeLeft(“name”,“飞”) —> name like ‘%强’
likeRightlike ‘值%’例:likeRight(“age”,18) —> age <= 18
isNull字段 is null例:isNull(“emal”) —> email is null
isNotNull字段 is not null例:isNotNull(“emal”) —> email is not null
in字段 in (值1,值2…)例:in(“age”,{10,18,30}) —> age in (10,18,30)
notIn字段 not in (值1,值2…)例:notIn(“age”,{10,18,30}) —> age not in (10,18,30)
inSql字段 in ( sql语句 )inSql(“id”, “select id from table where name like ‘%J%’”)—> id in (select id from table where name like ‘%J%’)
notInSql字段 not in ( sql语句 )notInSql(“id”, “select id from table where name like ‘%J%’”)—> id not in (select id from table where name like ‘%J%’)
orderBy排序 ordery by 字段,…例:orderBy(true,true,“id”,“name”) —> order by id asc,name asc
orderByDesc降排序 order by 字段,… desc例:orderByDesc(“id”,“name”) —> order by id desc,name desc
havinghaving (sql语句)having(“sum(age) > {0}”,18) —> having sum(age) > 18
or拼接or例:eq(“id”,1).or().eq(“name”,“老王”) —> id =1 or name = ‘老王’
andand 嵌套例:and(i -> i.eq(“name”,“李白”).ne(“status”,“活着”))—> and (name = ’李白‘ and status <> ‘活着’)
exists拼接exists (sql语句)例:exists(“select id from table where age = 1”)
not exists拼接not exists (sql语句)例:not exists(“select id from table where age = 1”)

举例

  • QueryWrapper 使用
void testQueryWrapper(){
        //构建查询条件
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<User>()
                .select("id","username","info","balance")
                .like("username","o")
                .ge("balance",1000);
        //查询
        List<User> users = userMapper.selectList(userQueryWrapper);
        users.forEach(System.out::println);
    }
void testUpdateByQueryWrapper(){
        //要更新的数据
        User user = new User();
        user.setBalance(2000);
        //更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
        //更新
        userMapper.update(user, wrapper);
    }
  • LambdaQueryWrapper 的使用
void testLambdaQueryWrapper(){
        //构建查询条件
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>()
                .select(User::getId,User::getUsername,User::getInfo,User::getBalance)
                .like(User::getUsername,"o")
                .ge(User::getBalance,1000);
        //查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        users.forEach(System.out::println);
    }

自定义SQL

我们可以利用MyBatisPlus的Wrapper来构建复杂的where条件,然后自己定义SQL语句中剩下的部分。

为什么需要自定义SQL

我们的方法是在Service层实现,一些复杂的数据库操作(除where条件部分外的操作,比如set)在方法中需要用SQL语句来实现,但是在实际开发中,SQL语句需要在mapper中实现,这样不符合开发规范

alt text

解决方案:我们可以利用MyBatisPlus的Wrapper来构建复杂的where条件,然后自己定义SQL语句中剩下的部分

  • 首先,基于Wrapper构建where条件
void testCustomSqlUpdate(){
        //更新条件
        List<Long> ids = List.of(1L, 2L, 4L);
        int amount = 200;
        //定义条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
        //调用自定义SQL方法
        userMapper.updateBalanceByIds(wrapper, amount);
    }
  • 然后,在mapper方法参数中用Param注解声明wrapper变量名称,必须是 ew
void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper, @Param("amount") int amount);
  • 最后,自定义SQL,并使用Wrapper条件
<update id="updateBalanceByIds">
        update user
        set balance = balance - #{amount} ${ew.customSqlSegment}
</update>

Service接口

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法

alt text

alt text

使用流程

  • 自定义Service 接口继承IService接口
public interface IUserService extends IService<User>{
}
  • 自定义Service实现类,实现自定义接口并继承ServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

IService的Lambda查询

上面在进行复杂条件查询时,是通过Wrapper条件构造器和自定义sql来实现的,还可以使用Iservice中的Lambda方法进一步优化代码

  • 使用lambdaQuery查询
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance){
    return lambdaQuery()
            .like(name != null, User::getUserName, name)
            .eq(status != null, User::getStatus, status)
            .ge(minBalance != null, User::getMinBalance, minBalance)
            .le(maxBalance != null, User::getMaxBalance, maxBalance)
            .list();  // .one():最多1个结果  .list():返回集合结果 .count():返回计数结果
}

或者这种格式(引用别的Mapper)

List<Client> clientList = clientMapper.selectList(Wrappers.<ClientModel>lambdaQuery()
            .eq(status != null, User::getStatus, status)
            .ge(minBalance != null, User::getMinBalance, minBalance)
            .le(maxBalance != null, User::getMaxBalance, maxBalance));
  • 使用lambdaQuery更新
@Override
@Transactional
public void deductBalance(Long id, Integer money){
    User user = getById(id);
    int remainBalance = user.getBalance() - money;
    lambdaUpdate()
            .set(User::getBalance, remainBalance)
            .set(remainBalance == 0, User::getStatus, 2)
            .eq(User::getId, id)
            .eq(User::getBalance, user.getBalance())  // 乐观锁
            .update();
}

IService 批量更新

需求:批量插入10万条用户数据,并作出对比

  • 普通的for循环插入
for (int i = 1; i <= 100000; i++) {
    userService.save(buildUser(i));   // buildUser为自定义构建User对象的方法
}

普通的for循环逐条插入速度极差,不推荐

  • MP的批量新增
// 每次批量插入1000条数据,插入100次即10万条数据
// 1、准备一个容量为1000的集合
List<User> list = new ArrayList<>(1000);
for (int i = 0; i < 100000; i++) {
    //2、添加一个User
    list.add(buildUser);    // buildUser为自定义构建User对象的方法
    //3、每1000条批量插入一次
    if (i % 1000 == 0) {
        userService.saveBatch(list);
        // 4、清空集合,准备下一条数据
        list.clear();
    }
}

MP的批量新增,机遇预编译的批处理,性能不错;与数据库建立的连接数会大幅减少,但是执行新增sql是一条一条执行,所有性能有待提升

  • 开启 rewriteBatchedStatements=true 配置(性能最好)

开启该配置的效果为

INSERT INTO question (exam_id, content) VALUES (?, ?), (?, ?), (?, ?);

枚举处理器

User 类中有一个用户状态字段需要设置为枚举类型,但是数据库中存储该字段为int类型,如何将用户类中的枚举类型和数据库中的字段对应上?

  • 使用@EnumValue注解,将该字段与数据库中字段对应上;当后端需要返回数据给前端时,由于类中存在枚举类,所以默认返回的是枚举类的名称,如果需要自定返回枚举类中对应的code字段值或者是value的值,可以使用@JsonValue 注解实现 alt text

alt text

  • 在application.yml中配置全局枚举处理器 alt text

Json 处理器

和上面的处理器类似,就是用来处理Json转换的处理器

alt text

我们要将user表中的JSON格式转换成对象

alt text

但是mybatis不能自动将对象和json数据进行转换 我们要在要进行转换的元素加入注解@TableField,这个处理器mp没有提供在application文件配置的方法,多个元素要分别加上这个注解,而且如图所示也出现了对象的嵌套,我们就要定义复杂的resultMap,如果我们不想定义resultMap就要在类上加入@TabelName注解;就可以帮我们实现对象和json数据的转换了

alt text

插件功能

分页插件

首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件

@Configuration
 
public class MyBatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //1,创建分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        //设置查询最大上限
        paginationInnerInterceptor.setMaxLimit(1000L);
        //2.添加分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
 
    }
}

接着,就可以使用分页的API了

alt text

实例

alt text

通用分页实体

  • 创建分页实体类
@ApiModel(description = "页码查询条件实体")
@Data
public class pageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo;
    @ApiModelProperty("页数")
    private Integer pageSize;
    @ApiModelProperty("排序字段")
    private Integer pageBy;
    @ApiModelProperty("是否升序")
    private Integer isAsc;
}
  • 需要分页的实体类继承分页实体类

alt text

  • 创建一个分页结果实体类,是dto类型,这里的dto是返回给前端数据封装的实体类

alt text

  • 查询实例

alt text

总结使用流程图

alt text

Last updated: