简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

愿景

我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗中的 1P、2P,基友搭配,效率翻倍。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 – Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作


框架结构


快速入门案例

软件工具

  • IDEA
  • java 17
  • maven 3.9.6
  • mysql 8
  • spring boot 3.0.2
  • mybatis-plus 3.5.7


建库建表

CREATE TABLE `money` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `basic` int DEFAULT NULL COMMENT '基本工资',
  `reward` int DEFAULT NULL COMMENT '奖金',
  `punishment` int DEFAULT NULL COMMENT '惩罚金',
  `version` int DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `did` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


初始化Spring Boot工程

pom.xml

   <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>com.github.yulichang</groupId>
            <artifactId>mybatis-plus-join-boot-starter</artifactId>
            <version>1.4.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

配置数据库连接

在application.properties文件中添加:(也可以使用application.yml文件)

# 应用服务 WEB 访问端口
server.port=8080

# mysql 5 驱动不同 com.mysql.jdbc.Driver
# mysql 8 驱动不同com.mysql.cj.jdbc.Driver、需要增加时区的配置 serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.url=jdbc:mysql://localhost:3306/erp1?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:/mapper/*.xml

#延迟加载
#启用懒加载。这意味着在查询主对象时,不会立即加载关联对象,只有在真正访问关联对象时才会进行加载。
# 这样可以减少数据库的查询次数,提高性能。
mybatis-plus.configuration.lazy-loading-enabled=true
#设置非激进懒加载。激进懒加载是指在访问主对象时,会立即加载所有关联对象。
# 非激进懒加载则是在访问关联对象时才会进行加载,这样可以更灵活地控制加载时机。
mybatis-plus.configuration.aggressive-lazy-loading=false

# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto

mybatis-plus.configuration.cache-enabled=true

编写代码

实体类

创建 money实体类

@Data
//@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Money implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 基本工资
     */
    private Integer basic;

    /**
     * 奖金
     */
    private Integer reward;

    /**
     * 惩罚金
     */
    private Integer punishment;

    @Version
    @TableField(fill = FieldFill.INSERT) // 设置默认值
    private Integer version;

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;


}

mapper接口继承BaseMapper

创建 mapper 包和 UserMapper 接口,并让它继承 BaseMapper<User>

public interface MoneyMapper extends BaseMapper<Money> {

}

启动类上添加 :@MapperScan(“com.example.mybatisplus.mapper”)

通用Service

创建service接口和实现类

public interface MoneyService extends IService<Money> {

}
@Service
public class MoneyServiceImpl extends ServiceImpl<MoneyMapper, Money> implements MoneyService{

}

测试

@SpringBootTest
class MybatisplusApplicationTests {

@Autowired
MoneyServiceImpl moneyService;
@Test
void contextLoads() {
System.out.println("moneyService.getById(1) = " + moneyService.getById(1));
}

}

输出:

JDBC Connection [HikariProxyConnection@450728160 wrapping com.mysql.cj.jdbc.ConnectionImpl@af96ac9] will not be managed by Spring
==>  Preparing: SELECT id,basic,reward,punishment,version,create_time,update_time FROM money WHERE id=?
==> Parameters: 1(Integer)
<==    Columns: id, basic, reward, punishment, version, create_time, update_time
<==        Row: 1, 13400, 25, 25, 1, 2025-04-26 17:16:15, 2025-04-26 17:16:15
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3c8dea0b]
moneyService.getById(1) = Money(id=1, basic=13400, reward=25, punishment=25, version=1, createTime=Sat Apr 26 17:16:15 CST 2025, updateTime=Sat Apr 26 17:16:15 CST 2025)

CRUD操作

一、Insert

1、插入操作

    @Test
    void addMoney() {
        Money money = new Money();

        money.setBasic(100);
        money.setPunishment(100);
        boolean b = moneyService.save(money);
        System.out.println("b = " + b);
    }

2、主键策略

参考博客:https://www.cnblogs.com/haoxinyue/p/5208136.html


系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。下面就介绍一些常见的ID生成策略。

(1)数据库自增长序列或字段

最常见的方式。利用数据库,全数据库唯一。

优点:

  • 1)简单,代码方便,性能可以接受。
  • 2)数字ID天然排序,对分页或者需要排序的结果很有帮助。

缺点:

  • 1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。
  • 2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
  • 3)在性能达不到要求的情况下,比较难于扩展。
  • 4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。
  • 5)分表分库的时候会有麻烦。

优化方案:

1)针对主库单点,如果有多个 Master 库,则每个 Master 库设置的起始数字不一样,步长一样,可以是 Master 的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载。

(2)UUID

常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。

优点:

  • 1)简单,代码方便。
  • 2)生成 ID 性能非常好,基本不会有性能问题。
  • 3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。

缺点:

  • 1)没有排序,无法保证趋势递增。
  • 2)UUID往往是使用字符串存储,查询的效率比较低。
  • 3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
  • 4)传输数据量大
  • 5)不可读。

(3)Redis生成ID

当使用数据库来生成 ID 性能不够要求的时候,我们可以尝试使用 Redis 来生成 ID。这主要依赖于Redis 是单线程的,所以也可以用生成全局唯一的ID。可以用 Redis 的原子操作 INCR 和 INCRBY 来实现。

可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:

  • A:1,6,11,16,21
  • B:2,7,12,17,22
  • C:3,8,13,18,23
  • D:4,9,14,19,24
  • E:5,10,15,20,25

这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的 ID。但是步长和初始值一定需要事先需要了。使用 Redis 集群也可以方式单点故障的问题。

另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。

优点:

  • 1)不依赖于数据库,灵活方便,且性能优于数据库。
  • 2)数字ID天然排序,对分页或者需要排序的结果很有帮助。

缺点:

  • 1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
  • 2)需要编码和配置的工作量比较大。

(4)snowflake算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake

snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。

优点:

  • 1)不依赖于数据库,灵活方便,且性能优于数据库。
  • 2)ID按照时间在单机上是递增的。

缺点:

  • 1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。

3、MP主键设置

在Money类中的id变量上面添加注解@TableId,关于IdType的值:

  • AUTO: 自动增长
  • UUID(过时): 每次随机生成唯一值,推荐使用ASSIGN_UUID
  • INPUT: 自己设置id值
  • ID_WORKER_STR(过时): mp自带策略,雪花算法,生成19位值,适用于字符串类型,推荐使用ASSIGN_ID
  • ID_WORKER(过时): mp自带策略,雪花算法,生成19位值,适用于数字类型,比如Long,推荐使用ASSIGN_ID

全局主键配置

要想影响所有实体的配置,可以设置全局主键配置

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto

二、Update

1、自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。 我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

(1)数据表中添加字段

在数据库表中添加 datetime 类型的新字段 create_time、update_time

(2)实体类添加@TableField注解

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

(3)实现元对象处理器接口

@Slf4j
@Component // 一定不要忘记把处理器加到IOC容器中!
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill....."+new Date());
// setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}

// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill.....");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}

2、根据id更新


    @Test
    void updateMoney() {
        Money money = new Money();
        money.setId(1);
        money.setBasic(100);
        boolean b = moneyService.updateById(money);
        System.out.println("b = " + b);
    }

3、乐观锁

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

(1)数据库中添加version字段

ALTER TABLE money ADD COLUMN version INT;

(2)实体类添加@Version注解

@Version
@TableField(fill = FieldFill.INSERT) // 设置默认值
private Integer version;

说明:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!

(3)元对象处理器接口添加version的insert默认值

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill....."+new Date());
        // setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        // 设置版本号默认为1
        this.setFieldValByName("version", 1, metaObject);
    }

(4)创建配置类,添加乐观锁插件

创建config包和MpConfig配置类,把启动类头上的@MapperScan注解剪切到自定义配置类上面

@EnableTransactionManagement//开启事务
@Configuration // 配置类
@MapperScan("com.example.mybatisplus.mapper")
public class MyBatisPlusConfig {

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

        return interceptor;
    }
}

(5)测试乐观锁

    @Test
    void updateMoney() {
        Money money = new Money();
        money.setId(1);
        money.setBasic(100);
        boolean b = moneyService.updateById(money);
        System.out.println("b = " + b);
    }

效果是version值变为1

三、Select

根据id查询

    @Test
    void contextLoads1() {
        Money money = moneyMapper.selectById(1);
        System.out.println(money);
    }

通过多个id批量查询

完成了动态 sql 的 foreach 的功能

    @Test
    void contextLoads1() {
        List<Money> monies = moneyMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        System.out.println(monies);
    }

分页查询

MyBatis-Plus 自带分页插件,只要简单的配置即可使用。

(1)配置类添加分页插件

@EnableTransactionManagement
@Configuration // 配置类
@MapperScan("com.example.mybatisplus.mapper")
public class MyBatisPlusConfig {

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

        return interceptor;
    }
    
}

(2)测试使用

 @Test
    public void testSelectPage() {
        // 创建page对象,传入两个参数:当前页和每页显示记录数
        Page<Money> page = new Page<>(1, 3);
        // 调用MP分页查询的方法,分页所有数据封装到page对象里面
        moneyMapper.selectPage(page, null);
        // 通过page对象获取分页数据
        System.out.println("当前页:" + page.getCurrent());// 1
        System.out.println("每页数据list集合:" + page.getRecords());// [User(id=1, name=Jone, age=18, email=...
        System.out.println("每页显示记录数:" + page.getSize());// 3
        System.out.println("总记录数:" + page.getTotal());// 6
        System.out.println("总页数:" + page.getPages());// 2
        System.out.println("是否有下一页:" + page.hasNext());// true
        System.out.println("是否有上一页:" + page.hasPrevious());// false
    }

四、Delete

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据。
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。

物理删除

根据id删除

@Test
public void testDelete() {
    userMapper.deleteById(1381855104893751298L);
}

逻辑删除

(1)数据库中添加deleted字段

ALTER TABLE `money` ADD COLUMN `deleted` tinyint(1);

(2)实体类添加@TableLogic注解

@TableLogic
@TableField(fill = FieldFill.INSERT) // 设置默认值
private Integer deleted;

(3)元对象处理器接口添加deleted的insert默认值

   @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill....."+new Date());
        // setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        // 设置版本号默认为1
        this.setFieldValByName("version", 1, metaObject);
        // 设置deleted默认插入值为0
        this.setFieldValByName("deleted", 0, metaObject);
    }

(4)修改配置文件

# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

(5)测试

    @Test
    public void testLogicDelete() {
        int i = moneyMapper.deleteById(1);
        System.out.println("受影响的行数:" + i);
    }
JDBC Connection [HikariProxyConnection@872422493 wrapping com.mysql.cj.jdbc.ConnectionImpl@34d3409d] will not be managed by Spring
==>  Preparing: UPDATE money SET update_time=?, deleted=1 WHERE id=? AND deleted=0
==> Parameters: 2025-05-24 13:23:09.067(Timestamp), 1(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d0f7bcf]
受影响的行数:1

五、Wrapper条件构造器

  • Wrapper : 条件构造抽象类,最顶端父类
  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
  • QueryWrapper : Entity 对象封装操作类,不是用 lambda 语法
  • UpdateWrapper : Update 条件封装,用于 Entity 对象更新操作
  • AbstractLambdaWrapper : Lambda 语法使用 Wrapper 统一处理解析 lambda 获取 column。
  • LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
  • LambdaUpdateWrapper : Lambda 更新封装Wrapper

ge/gt/le/lt/eq/ne/isNull/isNotNull

  • ge:>=
  • gt: >
  • le: <=
  • lt: <
  • eq:=
  • ne:<> 或 !=
  • isNull: is null
  • isNotNull: is not null
    @Test
    public void testWrapper() {
        QueryWrapper<Money> wrapper = new QueryWrapper<>();
        wrapper.eq("basic", "100").ge("reward", 80).isNotNull("version");
        List<Money> users = moneyMapper.selectList(wrapper);
        System.out.println(users);
    }
JDBC Connection [HikariProxyConnection@1547946770 wrapping com.mysql.cj.jdbc.ConnectionImpl@7607340f] will not be managed by Spring
==>  Preparing: SELECT id,basic,reward,punishment,version,create_time,update_time,deleted FROM money WHERE deleted=0 AND (basic = ? AND reward >= ? AND version IS NOT NULL)
==> Parameters: 100(String), 80(Integer)
<==    Columns: id, basic, reward, punishment, version, create_time, update_time, deleted
<==        Row: 7, 100, 20000, 100, 1, 2025-04-25 14:16:25, 2025-04-25 16:39:21, 0
<==        Row: 8, 100, 2000, 100, 1, 2025-04-25 14:17:50, 2025-04-25 16:39:21, 0
<==      Total: 2

between/notBetween

包含大小边界

allEq

同时满足条件


like/notLike/likeLeft/likeRight

in/notIn/inSql/notInSql/exists/notExists

or/and

    @Test
    public void testUpdate1() { //修改值
        Money money = new Money();
        money.setBasic(99);
        money.setReward(99);
        //修改条件
        UpdateWrapper<Money> moneyUpdateWrapper = new UpdateWrapper<>();
        moneyUpdateWrapper
                .like("version", 1)
                .or()
                .between("basic", 3000, 500);
        int result = moneyMapper.update(money, moneyUpdateWrapper);
        System.out.println(result);
    }

orderBy/orderByDesc/orderByAsc

指定要查询的列

    @Test
    public void testSelectListColumn() {
        QueryWrapper<Money> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "basic");
        List<Money> users = moneyMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

扩展:

pom.xml中引入了mybatis-plus-join-boot-starter包,他是做多表联合查询的,自己做测试


0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注