MybatisPlus
本文最后更新于518 天前,其中的信息可能已经过时,如有错误请发送邮件到1770087309@qq.com

MyBatis-Plus(简称 MP)是基于 MyBatis 的增强工具,旨在简化开发流程、提高效率,并且不会对现有项目产生影响。本文将详细介绍 MyBatis-Plus 的核心功能和使用方法,帮助你快速掌握这一强大的持久层框架。

快速入门

入门案例

需求:实现下列基础功能

  • 新增用户功能
  • 根据id查询用户
  • 根据id批量查询用户
  • 根据id更新用户
  • 根据id删除用户

步骤

1.引入MybatisPlus的起步依赖

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

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

2.定义Mapper

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

public interface UserMapper extends BaseMapper<User> {

}

3.调用BaseMapper方法

insert(user) //新增用户

selectById(id) //根据id查询用户

slectBatchIds(ids) //根据id批量查询用户

updateById(id) //根据id更新用户

deleteById(id) //根据id删除用户

常见注解

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

MybatisPlus中比较常用的几个注解如下:
@TableName:用来指定表名
@TableId:用来指定表中的主键字段信息
@TableField:用来指定表中的普通字段信息

@Data
@TableName("tb_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("username")
    private String username;
    @TableField("is_married")
    private Boolean isMarried;
    @TableField("`order`")
    private Integer order;
    @TableField(exist = false)
    private String address;
}

IdType枚举:
•AUTO:数据库自增长
•INPUT:通过set方法自行输入
•ASSIGN_ID:分配 ID,接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法

使用@TableField的常见场景:
•成员变量名与数据库字段名不一致
•成员变量名以is开头,且是布尔值
•成员变量名与数据库关键字冲突
•成员变量不是数据库字段

常见配置

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
  configuration:
    map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
    cache-enabled: false # 是否开启二级缓存
  global-config:
    db-config:
      id-type: assign_id # id为雪花算法生成
      update-strategy: not_null # 更新策略:只更新非空字段

核心功能

条件构造器

条件构造器的用法:
•QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
•UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
•尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

需求:

①查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段

//1.构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
         .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
         .like(User::getUsername, "o")
         .ge(User::getBalance, 1000);
//2.查询
List<User> users = userMapper.selectList(wrapper);

①更新用户名为jack的用户的余额为2000

//1.构建更新条件
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
        .eq(User::getUsername, "jack")
        .set(User::getBalance, 2000);
//2.更新
userMapper.update(null, wrapper);

自定义SQL

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

需求:将id在指定范围的用户(例如1、2、4 )的余额扣减指定值200

1.基于Wrapper构建where条件

List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 1.构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
// 2.自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper, amount);

2.在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew

void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, 
                        @Param("amount") int amount);

3.自定义SQL,并使用Wrapper条件

<update id="updateBalanceByIds">
   UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>

Service接口

MP的Service接口使用流程:

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

IService的Lambda查询

需求:根据名称,用户状态,余额数查询符合用户的集合

lambdaQuery()
 .like(name != null, User::getUsername, name)
 .eq(status != null, User::getStatus, status)
 .ge(minBalance != null, User::getBalance, minBalance)
 .le(maxBalance != null, User::getBalance, maxBalance)
 .list();

IService的Lambda更新

需求:用户下单,更新用户余额
1.完成对用户状态校验
2.完成对用户余额校验
3.如果扣减后余额为0,则将用户status修改为冻结状态

lambdaUpdate()
  .set(User::getBalance, remainBalance)
  .set(remainBalance == 0, User::getStatus, UserStatus.FROZEN) //如果金额为0,将状态设为冻结
  .eq(User::getId, id)
  .eq(User::getBalance, user.getBalance()) // 乐观锁
  .update();

IService批量新增

需求:批量插入10万条用户数据

//1.准备一个容量为1000的数组
List<User> list = new ArrayList<>(1000);
for (int i = 0; i < 100000; i++) {
     //2.添加一个user
     list.add(buildUser(i));
     //3.每一千条批量插入一次
     if (i % 1000 == 0) {
         userService.saveBatch(list);
         //4.清空集合,准备下一批数据
         list.clear();
      }
}

扩展功能

代码生成

基于idea插件Mybatis-X可以快速生成实体类,mapper,Service,ServiceImpl

静态工具

需求:根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址

public UserVO queryUserAndAddressById(Long id) {
    // 1.查询用户
    User user = getById(id);
    if (user == null || user.getStatus() == UserStatus.FROZEN) {
        throw new RuntimeException("用户状态异常!");
    }
    // 2.查询地址
    List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();
    // 3.封装VO
    // 3.1.转User的PO为VO
    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    // 3.2.转地址VO
    if (CollUtil.isNotEmpty(addresses)) {
        userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
     }
     return userVO;
}

逻辑删除

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:
•在表中添加一个字段标记数据是否被删除
•当删除数据时把标记置为1
•查询时只查询标记为0的数据

例如逻辑删除字段为deleted:

  • 删除操作
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
  • 查询操作
SELECT * FROM user WHERE deleted = 0

MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

注意

逻辑删除本身也有自己的问题,比如:
•会导致数据库表垃圾数据越来越多,影响查询效率
•SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

枚举处理器

在application.yml中配置全局枚举处理器:

mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

User类中有一个用户状态字段:

//用户信息
private String info;
//使用状态(1正常 2冻结) 利用enum转换器
private UserStatus status;

枚举类:

@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FROZEN(2, "冻结"),
    ;
    @EnumValue //向数据库中写,会自动使用该字段
    private final int value;
    @JsonValue //查询数据返回给前端,会自动使用该字段,看前端需要选择添加位置
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}

如何实现实体类中的枚举类型变量与数据库字段的转换?
1.给枚举中的与数据库对应value值添加@EnumValue注解
2.在配置文件中配置统一的枚举处理器,实现类型转换

JSON处理器

开启步骤:
1.将实体类字段类型定义为实体类型,并在字段上加上注解@TableField
2.在实体类上设置autoResultMap =true

@TableName(value = "user", autoResultMap = true)
@Data
public class User implements Serializable {
    //用户id
    @TableId(type = IdType.AUTO)
    private Long id;

    // 详细信息 利用json转换器
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;
}

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

插件功能

分页插件

配置类中注册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

int pageNo = 1, pageSize = 10;
// 1.准备分页条件
// 1.1.分页条件  
Page<User> page = Page.of(pageNo, pageSize);
// 1.2.排序条件
page.addOrder(new OrderItem("balance",true)); //优先按第一个添加条件排序
page.addOrder(new OrderItem("id",true));
        
// 2.分页查询
Page<User> p = userService.page(page);
        
// 3.解析
long total = p.getTotal(); //总条数
long pages = p.getPages(); //当前页
List<User> records = userPage.getRecords(); //集合

通用分页实体

需求:遵循下面的接口规范,编写一个UserController接口,实现User的分页查询

//请求体
{
"pageNo" : 1,
"pageSize" : 10,
"sortBy" : "balance",
"isAsc" : false,
"name" : "jack",
"status" : 1
}

//结果集
{
    "total": 1005,
    "pages": 201,
    "list": [
        {
            "id": 1,
            "username": "Jack",
            "info" : {
                "age": 21,
                "gender": "male",
                "intro": "佛系青年"
            },
            "status": "正常",
            "balance": 2000
        },
        {
            "id": 2,
            "username": "Rose",
            "info" : {
                "age": 20,
                "gender": "female",
                "intro": "文艺青年"
            },
            "status": "冻结",
            "balance": 1000
        }
    ]
}

步骤:

1.定义分页通用查询实体

@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo = 1;
    @ApiModelProperty("页码")
    private Integer pageSize = 5;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;
}

2.定义分页通用结果实体

@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;
}

3.查询实体继承分页通用查询实体

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

4.调用api

String name = query.getUsername();
Integer status = query.getStatus();
int pageNo = query.getPageNo(), pageSize = query.getPageSize();
//1.构建分页条件
Page<User> page = Page.of(pageNo, pageSize);

//2.添加排序条件
if (StrUtil.isNotBlank(query.getSortBy())){
    page.addOrder(new OrderItem(query.getSortBy(),query.getIsAsc()));
}else {
    page.addOrder(new OrderItem("update_time",true));
}

//2.分页查询
Page<User> userPage = userService.lambdaQuery()
         .like(User::getUsername, name)
         .eq(User::getStatus, status)
         .page(page);
        
//3.解析数据
List<User> records = userPage.getRecords();
long total = userPage.getTotal();

通用分页实体与MP转换

1.优化分页通用查询实体

@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo = 1;
    @ApiModelProperty("页码")
    private Integer pageSize = 5;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;

    public <T> Page<T> toMpPage(OrderItem ... items){
        // 1.分页条件
        Page<T> page = Page.of(pageNo, pageSize);
        // 2.排序条件
        if(StrUtil.isNotBlank(sortBy)){
            // 不为空
            page.addOrder(new OrderItem(sortBy, isAsc));
        }else if(items != null){
            // 为空,默认排序
            page.addOrder(items);
        }
        return page;
    }
    public <T> Page<T> toMpPage(String defaultSortBy, Boolean defaultAsc){
        return toMpPage(new OrderItem(defaultSortBy, defaultAsc));
    }
    public <T> Page<T> toMpPageDefaultSortByCreateTime(){
        return toMpPage(new OrderItem("create_time", false));
    }
    public <T> Page<T> toMpPageDefaultSortByUpdateTime(){
        return toMpPage(new OrderItem("update_time", false));
    }

}

2.优化分页通用结果实体

@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;

    public static <PO, VO> PageDTO<VO> of(Page<PO> p, Class<VO> clazz){
        PageDTO<VO> dto = new PageDTO<>();
        // 1.总条数
        dto.setTotal(p.getTotal());
        // 2.总页数
        dto.setPages(p.getPages());
        // 3.当前页数据
        List<PO> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        // 4.拷贝到VO
        dto.setList(BeanUtil.copyToList(records, clazz));
        // 5.返回
        return dto;
    }

    public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor){
        PageDTO<VO> dto = new PageDTO<>();
        // 1.总条数
        dto.setTotal(p.getTotal());
        // 2.总页数
        dto.setPages(p.getPages());
        // 3.当前页数据
        List<PO> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        // 4.拷贝到VO
        dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
        // 5.返回
        return dto;
    }
}

3.调用api

public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
        
        // 1.构建分页条件,排序条件
        Page<User> page;
        if (query.getSortBy().isEmpty()) {
            page = query.toMpPageDefaultSortByUpdateTime(); 
        } else {
            page = query.toMpPage(query.getSortBy(), query.getIsAsc()); //排序条件数据库不存在这个字段,则会报错
        }
        
        // 2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装VO结果
        return PageDTO.of(p, user -> {
            // 1.拷贝基础属性
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            // 2.处理特殊逻辑
            vo.setUsername(vo.getUsername().substring(0, vo.getUsername().length() - 2) + "**");
            return vo;
        });
    }

完结啦,MP通关啦!

觉得有帮助可以投喂下博主哦~感谢!
作者:KCH
版权声明: 转载请注明文章地址及作者哦~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇