MybatisPlus

文章目录

简介

MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率

特征

  • 无入侵
  • 强大的CRUD
  • 支持lambda
  • 支持主键自动生成
  • 内置分页插件

快速入门

简单程序

boot3的pom

xml 复制代码
<!--        <dependency>-->
<!--            <groupId>org.mybatis.spring.boot</groupId>-->
<!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!--            <version>3.0.3</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.7</version>
        </dependency>

mapper继承mp提供的类

java 复制代码
// 即制定了实体类,又制定了表的名字就是book 可以通过注解指定 @TableName()
public interface BookMapper extends BaseMapper<Book> {
}

测试案例

java 复制代码
List<Book> books =
                bookMapper.selectList(null);
        System.out.println(books);

常用注解

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

实体类转换数据库表的规则(约定):

  • 类名驼峰转下划线作为表名
  • 名为id的字段作为主键
  • 变量名驼峰转下划线作为表的字段名

常见注解

  • @TableName:用来指定表名

  • @TableField:指定表的普通字段信息

    • 成员变量和数据库字段不一致
    • 成员变量以is开头,且是布尔值
    • 成员变量和数据库字段关键字冲突
    • 成员变量不是数据库字段
  • @TableId:指定表中主键字段信息

    • type:设置主键属性的生成策略,参照IdType枚举,默认为雪花算法生成的id

    • value:设置数据库主键的名称

常见配置

yml 复制代码
    # mybatis-plus继承了mybaits原生配置和一些自己独有的配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml # Mapper.xml的文件地址 默认值
  type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
  configuration:
    map-underscore-to-camel-case: true # 是否开启驼峰到下划线的映射,默认值
    cache-enabled: false # 是否开启二级缓存,默认值
  global-config:
    db-config:
      id-type: assign_id # id为雪花算法生成,默认值
      update-strategy: not_null # 更新策略:只更新非空字段,默认值
      table-prefix: # 表的前缀 比如指定了为tb_ 我们实体类为User 那么查询的数据库的表就是tb_user
      # 逻辑删除配置
      logic-delete-value: xxx # 指定代表逻辑删除的字段名
      logic-not-delete-value: 0 # 0代表没有被删除
      logic-delete-field: 1 # 1代表被删除

核心功能

条件构造器

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

java 复制代码
//查询名字带着o,并且存款大于1000的
       LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
       lqw.select(User::getId,User::getUsername,User::getInfo,User::getBalance).
               like(User::getUsername,"o").
               ge(User::getBalance,1000);
       List<User> users = userMapper.selectList(lqw);
       users.forEach(System.out::println);

//将用户id为1 2 4的余额扣除200
       LambdaUpdateWrapper<User> luw = new LambdaUpdateWrapper<>();
       luw.setSql("balance = balance - 200").in(User::getId,1L,2L,4L);
       userMapper.update(null,luw);

/*
QueryWrapper和LambdaQueryWrapper的区别就是后者使用Lambda语法,防止硬编码
原理还是利用反射机制获取对应的字段名
*/

自定义SQL

什么时候使用:where条件之外的部分使用mp不方便直接实现,需要拼接这样违背的企业开发的规范

利用mp的wrapper来构建复杂的where条件,然后自己定义SQL语句的剩下的部分

where条件交付给mp,其他的sql我们来自定义

操作步骤:

1、基于wrapper构建where条件

2、自定义SQL方法调用

java 复制代码
		LambdaUpdateWrapper<User> lqw = new LambdaUpdateWrapper<User>().in(User::getId,1L,2L,4L);
        int amount = 200;
        userMapper.updateBalanceByIds(lqw,amount);

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

java 复制代码
    void updateBalanceByIds(@Param("ew") LambdaUpdateWrapper<User> lqw, @Param("amount") int amount);

4、书写sql语句,使用wrapper条件

xml 复制代码
    <update id="updateBalanceByIds">
        update user
        <set>
            balance = balance - #{amount}
        </set>
        ${ew.customSqlSegment}
    </update>

Service接口

基本的使用

  1. 自定义接口继承IService接口
  2. 自定义接口实现继承ServiceImpl
  3. 注意:指定泛型(mapper、entity)
java 复制代码
/*接口继承IService
需要指定一个泛型:操作的实体类
*/
public interface IUserService extends IService<User> {
}

/*实体类继续ServiceImpl
指定两个泛型
第一个:需要操作的mapper
第二个:对应的实体类
 */
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

Lambda操作

java 复制代码
    //lambda的查找操作
	public List<User> queryUsers(UserQuery query) {
        return lambdaQuery().like(query.getName() != null, User::getUsername, query.getName())
                .eq(query.getStatus() != null, User::getStatus, query.getStatus())
                .gt(query.getMinBalance() != null, User::getBalance, query.getMinBalance())
                .lt(query.getMaxBalance() != null, User::getBalance, query.getMaxBalance())
                .list();
    }

	//lambda的更新操作
         int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(remainBalance==0,User::getStatus,2)
                .set(User::getBalance,remainBalance)
                .eq(User::getId,id)
                .eq(User::getBalance,user.getBalance())//防止并发安全问题
                .update();

批量新增

要在yml文件中增加参数rewriteBatchedStatements=true

yml 复制代码
jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

扩展功能

代码生成

代码

模板:MyBatisPlus提供

数据库相关配置:读取数据库获取信息

开发者自定义配置:手工配置

导入依赖

xml 复制代码
        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--模板技术引擎-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>


插件

mybatisplus

静态工具

和IService相同,但是需要多传一个class字节码

使用场景:需要service之间相互注入使用Db,防止循环注入

比如

java 复制代码
        lambdaUpdate()
                .set(remainBalance==0,User::getStatus,2)
                .set(User::getBalance,remainBalance)
                .eq(User::getId,id)
                .eq(User::getBalance,user.getBalance())//防止并发安全问题
                .update();

//=>
	Db.lambdaUpdate(User.class)
                .set(remainBalance==0,User::getStatus,2)
                .set(User::getBalance,remainBalance)
                .eq(User::getId,id)
                .eq(User::getBalance,user.getBalance())//防止并发安全问题
                .update();

逻辑删除

逻辑删除就是基于代码的逻辑模拟删除效果,并不是真正删除数据

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为1
  • 查询时只查询标记为0的数据

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

yml 复制代码
mybatis-plus:
  global-config:
    db-config:
      # 逻辑删除配置
      logic-delete-value: deleted # 指定代表逻辑删除的字段名,类型可以时Boolean或者Integer
      logic-not-delete-value: 0 # 0代表没有被删除(默认为0)
      logic-delete-field: 1 # 1代表被删除(默认为1)

逻辑删除也存在一些问题:

  • 逻辑删除会导致数据库数据越来越多,影响查询效率
  • SQL中全都需要对逻辑删除字段做判断,影响查询效率

因此,不太推荐采用逻辑删除功能。如果数据不能删除,可以采用把数据迁移到其它表的办法

枚举处理器

1、配置枚举处理器

yml 复制代码
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

2、配置枚举

java 复制代码
public enum UserStatus {
    NORMAL(1,"正常"),
    FROZEN(2,"冻结"),
    ;

    @EnumValue //代表value属性和数据库对应
    @JsonValue //设置给前端返回的值,默认时枚举的名字
    private final Integer value;
    private final String desc;

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

json处理器

数据库保存的为json,但是我们java没有对应的json类型,我们希望自定义一个对象,让数据库的json自动对应,这就使用到json处理器

java 复制代码
package com.itheima.mp.domain.po;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.itheima.mp.enums.UserStatus;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName(value = "user",autoResultMap = true)//自动结果集映射
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String phone;
	//json和对象的转换
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;
    private UserStatus status;
    private Integer balance;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

插件功能

分页插件

1、配置插件

java 复制代码
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1、初始化核心插件
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //2、配置分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setMaxLimit(1000L);//设置分页上限
        //3、添加分页插件
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
        //4、返回核心插件
        return mybatisPlusInterceptor;
    }

2、使用分页插件

java 复制代码
    //简单的分页查询 
	void testPage(){
        Page<User> page = Page.of(1, 2);//页码 每页2个
        userService.page(page);
        System.out.println(page.getPages());//多少页
        System.out.println(page.getTotal());//多少数据
        page.getRecords().forEach(System.out::println);//数据
    }
	//带过滤条件的分页查询 使用lambdaQuery

对于 PageQuery和 PageDTO 的封装

java 复制代码
@Data
public class PageQuery {
    private Integer pageNo;
    private Integer pageSize;
    private String sortBy;
    private Boolean isAsc;

    public <T>  Page<T> toMpPage(OrderItem ... orders){
        // 1.分页条件
        Page<T> p = Page.of(pageNo, pageSize);
        // 2.排序条件
        // 2.1.先看前端有没有传排序字段
        if (sortBy != null) {
            p.addOrder(new OrderItem(sortBy, isAsc));
            return p;
        }
        // 2.2.再看有没有手动指定排序字段
        if(orders != null){
            p.addOrder(orders);
        }
        return p;
    }

    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
        return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
    }

    public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
        return toMpPage("create_time", false);
    }

    public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
        return toMpPage("update_time", false);
    }
}
java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
    private Long total;
    private Long pages;
    private List<V> list;

    /**
     * 返回空分页结果
     * @param p MybatisPlus的分页结果
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> empty(Page<P> p){
        return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
    }

    /**
     * 将MybatisPlus分页结果转为 VO分页结果
     * @param p MybatisPlus的分页结果
     * @param voClass 目标VO类型的字节码
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
        // 1.非空校验
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            // 无数据,返回空结果
            return empty(p);
        }
        // 2.数据转换
        List<V> vos = BeanUtil.copyToList(records, voClass);
        // 3.封装返回
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }

    /**
     * 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
     * @param p MybatisPlus的分页结果
     * @param convertor PO到VO的转换函数
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
        // 1.非空校验
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            // 无数据,返回空结果
            return empty(p);
        }
        // 2.数据转换
        List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
        // 3.封装返回
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }
}

乐观锁

拦截器

java 复制代码
    @Bean
    MybatisPlusInterceptor mpInterceptor(){
        //定义mp拦截器
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //添加乐观锁的拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }

属性配置

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("book")
public class Book {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    //乐观锁 需要加拦截器
    @Version
    private Integer version;
    @TableField(select = false)
    private Integer online;
}

测试

java 复制代码
    @Test
    void testS(){
        Book user1 = bookMapper.selectById(1);//version=1
        Book user2 = bookMapper.selectById(1);//version=1

        user1.setName("平凡的世界  111");
        bookMapper.updateById(user1); //version=2

        user2.setName("平凡的世界  222");
        bookMapper.updateById(user2); //此时的version=1,修改失败
    }

//并发操作可以进行控制
相关推荐
enjoy嚣士1 小时前
mybatis-plus之使用lombok的@Builder注解之后的坑
mybatis·lombok
小高不明1 小时前
仿 RabbitMQ 的消息队列3(实战项目)
java·开发语言·spring·rabbitmq·mybatis
兩尛1 小时前
订单状态定时处理、来单提醒和客户催单(day10)
java·前端·数据库
web2u1 小时前
MySQL 中如何进行 SQL 调优?
java·数据库·后端·sql·mysql·缓存
Elastic 中国社区官方博客2 小时前
使用 Elasticsearch 导航检索增强生成图表
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
小金的学习笔记2 小时前
RedisTemplate和Redisson的使用和区别
数据库·redis·缓存
新知图书2 小时前
MySQL用户授权、收回权限与查看权限
数据库·mysql·安全
文城5213 小时前
Mysql存储过程(学习自用)
数据库·学习·mysql
沉默的煎蛋3 小时前
MyBatis 注解开发详解
java·数据库·mysql·算法·mybatis
呼啦啦啦啦啦啦啦啦3 小时前
【Redis】事务
数据库·redis·缓存