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,修改失败
    }

//并发操作可以进行控制
相关推荐
你的微笑,乱了夏天34 分钟前
linux centos 7 安装 mongodb7
数据库·mongodb
工业甲酰苯胺1 小时前
分布式系统架构:服务容错
数据库·架构
独行soc2 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain2 小时前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship2 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站2 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶2 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
梦想平凡4 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
TianyaOAO4 小时前
mysql的事务控制和数据库的备份和恢复
数据库·mysql
Ewen Seong4 小时前
mysql系列5—Innodb的缓存
数据库·mysql·缓存