MyBatis-Plus知识总结

1. MP前瞻


官网:https://baomidou.com/

1、MyBatis-Plus是什么:MyBatis-Plus(简称MP)是一个MyBatis的增强工具,它在MyBatis的基础上只做增强不做改变,为简化开发、提供效率而生。并且MP内部提供了丰富的 API,可以解放单表CRUD方法的编写,使开发者更专注于业务逻辑的实现。

image-20240728055756064

2、MP的特性:无侵入损耗小,提供强大的CRUD操作,支持Lambda形式调用,支持 ActiveRecord 形式调用,支持主键生成,内置分页插件、代码生成器和性能分析插件等等。

3、支持的数据库:有MySQL、PostgreSQL、MariaDB、Oracle、OceanBase、SQLite、SQLServer、H2、DB2、达梦、人大金仓、虚谷等等。

4、SpringBoot整合MP,需要引入的依赖:

<!--SpringBoot2-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>
<!--SpringBoot3-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>

image-20240728180619627


2. MP快速入门


1、执行sql脚本:

CREATE DATABASE `mp_db` CHARACTER SET utf8mb4;

use `mp_db`;

CREATE TABLE `user`
(
    `id` BIGINT NOT NULL COMMENT '主键ID',
    `name` VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    `age` INT NULL DEFAULT NULL COMMENT '年龄',
    `email` VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

INSERT INTO `user` (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'JackMa', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

2、创建SpringBoot工程

3、引入mp相关的依赖坐标:

<dependencies>
  <!--mp-->
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
  </dependency>
    <!--mysql驱动-->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
    <!--lombok简化实体开发-->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
  </dependency>
  <!--单元测试起步依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
  </dependency>
</dependencies>

4、在 application.yml中,配置数据库的连接信息:

#DataSource Config
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mp_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456


#MybatisPlus Config
mybatis-plus:
  configuration:
    #开启SQL日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

5、编写实体类:

package cn.aopmin.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 用户实体
 *
 * @author 白豆五
 * @since 2024/7/28
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")//指定要绑定的表名
public class User implements Serializable {
    @TableId(type = IdType.AUTO, value = "id")// 设置主键生成策略
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

实体类与表名规则要求:

  • MP默认情况下,要求:实体类和表名 要符合 下划线与驼峰的映射规则;
  • 如果不符合:就需要在实体类上加@TableName("表名")

实体类里属性与字段的规则要求:

  • 默认情况下,要求每个属性都必须有一个对应的字段,属性名和字段名要符合 驼峰与下划线映射规则;

  • 非主键字段:

    • 如果一个属性名,和字段名不对应,怎么办?加@TableField(value="字段名")
    • 如果一个属性,没有对应的字段存在,怎么办?加@TableField(exists=false)
    • 如果一个属性,不想查询对应字段的值,怎么办?加@TableField(select=false)
  • 主键字段:

    • 要使用@TableId(value=“字段名”, type=主键生成策略)
    • 主键生成策略,设置的方式:实体类里主键属性上加 @TableId(type=IdType.策略名)
      • NONE:不设置,跟随全局
      • AUTO:主键值自增,前提是原有的数据库主键支持自增
      • INPUT:由我们的代码设置主键值,不让MP生成主键值
      • ASSIGN_ID:雪花算法。根据时间戳+机器码+序列号生成最终的Long类型的数字,特点是单调递增的
      • ASSIGN_UUID:UUID算法。不推荐,因为UUID值是乱序的,会影响主键字段上的索引

6、编写mapper接口:

package cn.aopmin.mapper;

import cn.aopmin.domain.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * 定义一个Mapper接口,继承BaseMapper<实体类> 
 *
 * @author 白豆五
 * @since 2024/7/28
 */
public interface UserMapper extends BaseMapper<User> {
}

7、在启动类上加上@MapperScan注解:

package cn.aopmin;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("cn.aopmin.mapper") // 扫描mapper接口
@SpringBootApplication
public class MPApplication {
    public static void main(String[] args) {
        SpringApplication.run(MPApplication.class, args);
    }
}

8、测试:

package cn.aopmin;

import cn.aopmin.domain.User;
import cn.aopmin.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

/**
 * mp快速入门
 *
 * @author 白豆五
 * @since 2024/7/28
 */
@SpringBootTest
public class Demo01MpTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test() {
        List<User> list = userMapper.selectList(null);
        list.forEach(System.out::println);
    }

}

image-20240728174849025


3. mapper内置方法


文档地址:https://baomidou.com/guides/data-interface/#mapper-interface

MP内置了强大的BaseMapper,它已经提供好了单表CURD功能:只要我们的Mapper接口继承了BaseMapper,然后泛型设置表名对应的实体类,就可以直接使用整套的单表CURD功能了。

Mapper接口常用方法:

  • 新增:int insert(T entity)
  • 根据id修改:int updateById(T entity)
  • 根据id删除:int deleteById(Serializable id)
  • 根据id查询:T selectById(Serializable id)
  • (根据条件) 查询一条:T selectOne(Wrapper<T> w)
  • (根据条件) 查询列表:List<T> selectList(Wrapper<T> w)
  • (根据条件) 查询数量: Integer selectCount(Wrapper<T> w)
  • (根据条件) 分页查询: IPage selectPage(IPage page, Wrapper<T> w)

4. service内置方法


文档地址:https://baomidou.com/guides/data-interface/#service-interface

同时MP也提供了通用Service接口,如果Service层也想操作,只需继承IService 接口就可以得到整套单表CURD功能。

Service接口常用方法:

  • 新增:boolean save(T entity)
  • 根据id修改:boolean updateById(T entity)
  • 根据id删除:boolean removeById(Serializable id)
  • 根据id查询:getById(Serializable id)
  • (根据条件) 查询一条:T getOne(Wrapper<T> queryWrapper)
  • (根据条件) 查询列表:List<T> list(Wrapper<T> queryWrapper)
  • (根据条件) 查询数量: int count(Wrapper<T> w)
  • (根据条件) 分页查询: IPage page(IPage page, Wrapper<T> w)

5.wrapper条件构造器


文档地址:https://baomidou.com/guides/wrapper/

MyBatis-Plus 的 Wrapper 类是构建查询和更新条件的核心工具,它允许开发者以链式编程方式构造where条件,从而简化SQL语句的编写、降低SQL注入的风险。

Wrapper 常用子类:

  • QueryWrapper、UpdateWrapper:使用时容易写错,但使用更灵活。
  • LambdaQueryWrapper、LambdaUpdateWrapper:没有QueryWrapper、UpdateWrapper灵活,但是不易写错(拼接条件不用指定表字段,而是对应属性名),使用的更多一些。

image-20240728190926812

QueryWrapper的用法:

//直接new的方式。
QueryWrapper<实体类> wrapper = new QueryWrapper<实体类>()
    .select("字段1,字段2,字段3 as 别名, ifnull(...) as xx")
    //条件方法:eq, ne, gt, ge, lt, le, like, notLike, in, notIn, isNull, isNotNull....
    .条件方法(字段名,) 
    .条件方法(是否要拼接此条件, 字段名,)
    .orderByAsc("字段名...").orderByDesc("字段名...")
    
//直接Wrappers的静态方法方式。
QueryWrapper<实体类> wrapper = Wrappers.<实体类>query()
    .select("字段1,字段2,字段3 as 别名, ifnull(...) as xx")
    //条件方法:eq, ne, gt, ge, lt, le, like, notLike, in, notIn, isNull, isNotNull....
    .条件方法(字段名,) 
    .条件方法(是否要拼接此条件, 字段名,)
    .orderByAsc("字段名...").orderByDesc("字段名...")    
    
List list = xxxMapper.selectList(wrapper);
  • 条件方法:
    • eq(“字段名”, 值): 等于。
    • ne(“字段名”, 值):不等于。
    • gt(“字段名”, 值): 大于。
    • ge(“字段名”, 值): 大于等于。
    • lt(“字段名”, 值): 小于。
    • le(“字段名”, 值): 小于等于。
    • like(“字段名”, 值): 模糊匹配,使用 % 作为通配符。
    • notLike(“字段名”, 值): 不模糊匹配,使用 % 作为通配符。
    • in(“字段名”, 值列表): 在某个集合中。
    • notIn(“字段名”, 值列表): 不在某个集合中。
    • isNull(“字段名”): 为空。
    • isNotNull(“字段名”): 不为空。
    • orderByAsc(“字段名”): 按字段名升序排序。
    • orderByDesc(“字段名”): 按字段名降序排序。

LambdaQueryWrapper的用法:

//直接new
LambdaQueryWrapper<实体类> wrapper = new LambdaQueryWrapper<实体类>()
    .select(实体类::get属性, ....)
    .条件方法(实体类::get属性,)
    .条件方法(是否要拼接此条件, 实体类::get属性,)
    .orderByAsc(实体类::get属性,..).orderByDesc(实体类::get属性,...)

//使用Wrappers的静态方法
LambdaQueryWrapper<实体类> wrapper = Wrappers.<实体类>lambdaQuery()
    .select(实体类::get属性, ....)
    .条件方法(实体类::get属性,)
    .条件方法(是否要拼接此条件, 实体类::get属性,)
    .orderByAsc(实体类::get属性,..).orderByDesc(实体类::get属性,...)

UpdateWrapper的用法:

// 直接new的方式。  
UpdateWrapper<实体类> wrapper = new UpdateWrapper<实体类>()  
    // 设置更新字段  
    .set("字段名",)   
    // 设置更新条件,使用与QueryWrapper相同的条件方法  
    .条件方法("字段名",)int result = userMapper.update(entity, wrapper);  
    

// 使用Wrappers的静态方法 
UpdateWrapper<实体类> wrapper = Wrappers.<实体类>update()  
    // 设置更新字段  
    .set("字段名",)   
    // 设置更新条件,使用与QueryWrapper相同的条件方法  
    .条件方法("字段名",)// 执行更新操作  
int result = xxxMapper.update(entity, wrapper);

LambdaUpdateWrapper的用法:

// 直接new的方式。  
LambdaUpdateWrapper<实体类> wrapper = new LambdaUpdateWrapper<实体类>()  
    // 设置更新字段  
    .set("字段名",)   
    // 设置更新条件
    .条件方法("字段名",)int result = userMapper.update(entity, wrapper);  


// 使用Wrappers的静态方法 
LambdaUpdateWrapper<实体类> wrapper = Wrappers.<实体类>lambdaUpdate()  
    // 设置更新字段  
    .set("字段名",)   
    // 设置更新条件
    .条件方法("字段名",)// 执行更新操作  
int result = userMapper.update(entity, wrapper);   

6. MP分页查询


文档地址:https://baomidou.com/plugins/pagination/

MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库,使得分页查询变得简单高效。

使用步骤:

1、在配置类或启动类中配置 分页插件

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
    return interceptor;
}

2、调用mapper接口的selectPage方法进行分页查询

Page<实体类> page = xxxMapper.selectPage( new Page(页码,每页几条), wrapper对象 );

page.getTotal();  //获取总数量
page.getPages();  //获取总页数
page.getRecords();//获取数据列表

分页API介绍:

MP的Mapper接口提供的分页查询方法是:IPage selectPage(IPage page, Wrapper wrapper)

  • 参数page:用于封装分页条件,包括页码和查询数量

  • 参数wrapper:用于封装查询条件,实现条件查询并分页

  • 返回值Page:分页查询的结果

IPage:是一个接口;Page是它的实现类,表示分页信息对象。

  • 在执行分页查询前,把分页参数封装成Page对象
  • 当执行分页查询后,MP会把查询结果封装到这个Page对象中。

Page对象常用方法:

  • new Page(pageNumber, pageSize):创建分页信息Page对象
  • getCurrent():获取当前页码
  • getPages():获取总页数
  • getSize():获取每页几条
  • getTotal():获取总数量

示例:

/ 创建分页对象,当前页为1,每页显示10条记录  
IPage<User> page = new Page<>(1, 10);  

// 创建查询条件  
QueryWrapper<User> wrapper = new QueryWrapper<>();  
wrapper.eq("status", 1); // 查询条件:状态为1  

// 执行分页查询  
IPage<User> resultPage = userMapper.selectPage(page, wrapper);  

// 获取分页信息  
List<User> userList = resultPage.getRecords(); // 当前页的记录  
long total = resultPage.getTotal(); // 总记录数  
long pages = resultPage.getPages(); // 总页数  

7. 自定义SQL


mp本身是兼容mybatis的,当mp无法满足特定需求时(比如 多表操作),我们也可以像mybatis一样,通过注解或 xml方式手写sql进行扩展。

参考:https://cloud.tencent.com/developer/article/1531517


8. 逻辑删除


在开发中,删除一条数据通常有两种方案可以实现:

  • 物理删除:直接从数据库中删数据。
  • 逻辑删除:并不是真正的删除数据,而是把数据的状态字段值设置为“已删除”状态;当查询数据时,不查询“已删除”状态的数据。

如何实现逻辑删除?

  1. 修改表,增加一个状态字段,用于存储数据是否删除的状态
use mp_db;
alter table user add deleted int default 0;
  1. 修改实体类,增加对应的属性,并给属性上加@TableLogic
@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String userName;
    private String password;
    private Integer age;
    private String tel;
    private String sex;
    //增加@TableLogic注解,这个字段被声明为 逻辑删除状态字段
    @TableLogic
    private int deleted;
}
  1. 修改配置文件,告诉MP,状态字段值为几的时候是已删除,状态是几的时候是未删除
mybatis-plus:
  global-config:
    db-config:
      #logic-delete-field: deleted #全局的默认逻辑删除字段名,即 状态字段名。
      logic-delete-value: 1 #已删除状态的值
      logic-not-delete-value: 0 #未删除状态的值

9. 自动填充字段


文档地址:https://baomidou.com/guides/auto-fill-field/

MP提供了一个便捷的自动填充功能,用于在新增或修改数据时自动填充指定字段,比如 创建时间、更新时间、创建人和修改人等。


10. 多数据源


文档地址:https://baomidou.com/guides/dynamic-datasource/

随着项目规模的扩大和业务需求的复杂化,单一数据源已经不能满足实际开发中的需求。在许多情况下,我们需要同时操作多个数据库,或者需要将不同类型的数据存储在不同的数据库中。这时,多数据源场景成为必不可少的解决方案。

市面上常见的多数据源实现方案如下:

方案1:基于Spring框架提供的AbstractRoutingDataSource
- 优点:简单易用,支持动态切换数据源,适用于少量数据源情况。
- 场景:适用于需要动态切换数据源,且数据库较少的情况。
- 文档地址:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/utils/AbstractRoutingDataSource.html


方案2:使用MP提供的Dynamic-datasource多数据源框架
- 优点:提供了简单的API,支持动态切换和明确定义数据源。
- 文档地址:https://baomidou.com/guides/dynamic-datasource/#dynamic-datasource

 方案3:通过自定义注解在方法或类上指定数据源
- 优点:灵活性高,能够精确地控制数据源切换;在代码中直观明了。
- 场景:适用于需要在代码层面进行数据源切换,并对数据源切换有精细要求的情况。

 方案4:使用动态代理技术,在运行时动态切换数据源,实现多数据源的切换。
- 优点:灵活性高,支持在运行时动态切换数据源;适合对数据源切换的逻辑有特殊需求的情况。
- 场景:适用于需要在运行时动态决定数据源切换策略的情况。

👉传送门


11. 乐观锁


文档地址:https://baomidou.com/plugins/optimistic-locker/

悲观锁:

  • 在自己操作资源数据期间,认为总有其它线程来干扰,所以要添加排他性的、独占性的锁。

    在我加锁操作期间,其它所有线程都要阻塞排队。直到我释放锁,其他线程才可以再抢锁操作。

  • 特点:

    • 安全性高,因为把多线程并发变成了串行
    • 性能较低,因为同一时间只有一个线程能操作,其它线程都是阻塞排队状态
  • 适合:写多读少的情况

  • 技术:synchronized、Lock对象…

乐观锁:

  • 在自己操作资源数据期间,其它线程很少来干扰,所以并不需要真正添加锁,可以有更好的性能

    自己在每次操作数据时,都先校验感知一下,数据是否被其它线程修改过了;如果被其它线程修改过了,就放弃操作或者报错;如果没有被其它线程修改,就直接执行操作。

  • 特点:

    • 安全性足够
    • 性能比较好。因为没有真正加锁,在当前线程操作期间,可以有其它线程执行读操作,不会有任何影响
  • 适合:读多写少的情况

  • 具体实现:

    • CAS思想:Compare And Swap 对比并交换设置。比如:Java里的AtomicInteger底层使用了CAS思想,并不需要真正加锁,也能实现多线程操作时的线程安全性。
    • 版本号:给数据设置版本号,每次变动数据时都要把版本号+1。修改数据就可以感知,数据是否被其它线程修改了

MP的乐观锁,采用的是版本号方式,使用步骤如下:

  1. 给表里增加一个版本号字段
use mp_db;
alter table user add version int default 0;
  1. 给实体类里增加对应的属性,并添加@Version
@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String userName;
    private String password;
    private Integer age;
    private String tel;
    private String sex;
    @TableLogic
    private int deleted;
    
    //增加对应的属性,并添加@Version注解
    @Version
    private int version;  
}
  1. 给配置类或引导类里,配置 乐观锁插件
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //创建拦截器对象MybatisPlusInterceptor
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        //添加分页插件PaginationInnerInterceptor,注意数据库的类型。如果数据库是MySQL,就设置DbType.MYSQL
        //interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        
        //添加乐观锁插件OptimisticLockerInnerInterceptor
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

12. MP其他功能


  • 代码生成器
  • 动态表名插件
  • SQL分析和打印
  • 防全表更新和删除插件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白豆五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值