7.mybatisplus学习-条件构造器、插件、通用枚举、多数据源环境、MybatisX

条件构造器

MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。

QueryWrapper & UpdateWrapper

java 复制代码
package com.light.mybatisplus;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.light.mybatisplus.mapper.StudentMapper;
import com.light.mybatisplus.pojo.Student;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class TestWrapper {
    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testWrapperQuery(){
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        //这里的第一个参数应该是数据库表字段,而非映射实体类属性
        queryWrapper.like("name", 'l')
                //也可以使用between()
                .ge("age", 12)
                .le("age", 100)
                .isNull("email");
        List<Student> list = studentMapper.selectList(queryWrapper);
        System.out.println(list);
    }

    @Test
    public void testWrapperQueryOrder(){
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        // 先使用age升序排序
        queryWrapper.orderByAsc("age")
                //如果年龄相同,按照id降序排列
                .orderByDesc("id");
        List<Student> students = studentMapper.selectList(queryWrapper);
        students.forEach(System.out::println);
    }

    @Test
    public void testWrapperDel(){
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("email");
        System.out.println(studentMapper.delete(queryWrapper));
    }

    @Test
    public void testWrapperUpdate(){
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.like("name", 'z')
                .gt("age", 12)
                .or()
                .isNotNull("email");
        Student student = new Student();
        student.setEmail("test@101100.com");
        int update = studentMapper.update(student, wrapper);
        System.out.println(update);
    }

    @Test
    public void testWrapperUpdateConditionLevel(){
        // 将name中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.like("name", 'a')
                //这里查看and的参数是Consumer类型,所以这里是使用lambda表达式,w就表示我们当前的wrapper
                .and(w->w.gt("age", 12)
                        .or()
                        .isNull("email"));
        Student student = new Student();
        student.setEmail("test@101100.com");
        int update = studentMapper.update(student, wrapper);
        System.out.println(update);
        // UPDATE student SET email=? WHERE is_delete='0' AND (name LIKE ? AND (age > ? OR email IS NULL))
    }

    @Test
    public void testWrapperSelectSubField(){
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name", "age", "email").gt("age", 18);
        List<Student> students = studentMapper.selectList(queryWrapper);
        students.forEach(System.out::println);
        // SELECT name,age,email FROM student WHERE is_delete='0' AND (age > ?)
    }

    @Test
    public void testWrapperSelectSubSelect(){
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        // 查询d小于等于100的用户信息
        queryWrapper.inSql("id", "select id from student where id < 100");
        List<Student> students = studentMapper.selectList(queryWrapper);
        students.forEach(System.out::println);
        // SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (id IN (select id from student where id < 100))
    }

    @Test
    public void testUpdateWrapper(){
        // UpdateWrapper 可以设置查询条件,也可以设置更改的数据
        UpdateWrapper<Student> wrapper = new UpdateWrapper<>();
        wrapper.like("name", 'a')
                //这里查看and的参数是Consumer类型,所以这里是使用lambda表达式,w就表示我们当前的wrapper
                .and(w->w.gt("age", 12)
                        .or()
                        .isNull("email"));
        wrapper.set("name", "小黑").set("email", "xiaohei@163.com");
        int update = studentMapper.update(null, wrapper);
        System.out.println("结果:"+update);
        // UPDATE student SET name=?,email=? WHERE is_delete='0' AND (name LIKE ? AND (age > ? OR email IS NULL))
    }

    @Test
    public void testSelectCondition1(){
        String name = "a";
        Integer ageStart = null;
        Integer ageEnd = 30;

        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        // isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
        if (StringUtils.isNotBlank(name)){
            wrapper.eq("name", "张小花");
        }

        if (ageStart != null && ageStart > 0){
            wrapper.ge("age", ageStart);
        }

        if (ageEnd != null && ageEnd > 0){
            wrapper.le("age", ageEnd);
        }

        List<Student> students = studentMapper.selectList(wrapper);
        students.forEach(System.out::println);
        // SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (name = ? AND age <= ?)
    }

    @Test
    public void testSelectCondition2(){
        String name = "a";
        Integer ageStart = null;
        Integer ageEnd = 30;

        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        // isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
        // eq、ge、le等方法第一个参数都是条件语句
        wrapper.eq(StringUtils.isNotBlank(name), "name", "张小花")
                .ge(ageStart != null && ageStart > 0, "age", ageStart)
                .le(ageEnd != null && ageEnd > 0, "age", ageEnd);

        List<Student> students = studentMapper.selectList(wrapper);
        students.forEach(System.out::println);
        // SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (name = ? AND age <= ?)
    }

    

}

LambdaQueryWrapper & LambdaUpdateWrapper

java 复制代码
package com.light.mybatisplus;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.light.mybatisplus.mapper.StudentMapper;
import com.light.mybatisplus.pojo.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class TestLambdaWrapper {
    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testLambdaQueryWrapper(){
        String name = "a";
        Integer ageStart = null;
        Integer ageEnd = 30;

        LambdaQueryWrapper<Student> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //其实区别是lambda主要通过Student::getName各种形式获取数据库字段名
        lambdaQueryWrapper.eq(StringUtils.isNotBlank(name), Student::getName, name)
                .ge(ageStart != null && ageStart > 0, Student::getAge, ageStart)
                .le(ageEnd != null && ageEnd > 0, Student::getAge, ageEnd);
        List<Student> students = studentMapper.selectList(lambdaQueryWrapper);
        students.forEach(System.out::println);
        //SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (name = ? AND age <= ?)
    }

    @Test
    public void testLambdaUpdateWrapper(){
        // UpdateWrapper 可以设置查询条件,也可以设置更改的数据
        LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
        wrapper.like(Student::getName, 'a')
                //这里查看and的参数是Consumer类型,所以这里是使用lambda表达式,w就表示我们当前的wrapper
                .and(w->w.gt(Student::getAge, 12)
                        .or()
                        .isNull(Student::getEmail));
        wrapper.set(Student::getName, "小黑").set(Student::getEmail, "xiaohei@163.com");
        int update = studentMapper.update(null, wrapper);
        System.out.println("结果:"+update);
        // UPDATE student SET name=?,email=? WHERE is_delete='0' AND (name LIKE ? AND (age > ? OR email IS NULL))
    }
}

插件

MyBatis-Plus 提供了一系列强大的插件来增强 MyBatis 的功能,这些插件通过 MybatisPlusInterceptor 来实现对 MyBatis 执行过程的拦截和增强。

MybatisPlusInterceptor 是 MyBatis-Plus 的核心插件,它代理了 MyBatis 的 Executor#query、Executor#update 和 StatementHandler#prepare 方法,允许在这些方法执行前后插入自定义逻辑。

MybatisPlusInterceptor 有一个关键属性 interceptors,它是一个 List<InnerInterceptor> 类型的集合,用于存储所有要应用的内部拦截器。

步骤:

  • 首先在spring boot中添加一个配置类
  • 然后在配置类中编写添加插件

分页插件

MyBatisPlus自带分页插件,只要简单的配置即可实现分页功能。下面是添加插件:

java 复制代码
package com.light.mybatisplus.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
// @ComponentScan("com.light.mybatisplus.mapper") //扫描带 @Component/@Service/@Repository 的类
@MapperScan("com.light.mybatisplus.mapper") // 扫描指定包下的接口,生成 MyBatis 的动态代理并注册为 Spring Bean
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //创建拦截器对象
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页拦截器 - 标记为mysql类型数据库
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

PaginationInnerInterceptor 提供了以下属性来定制分页行为:

属性名 类型 默认值 描述
overflow boolean false 溢出总页数后是否进行处理
maxLimit Long 单页分页条数限制
dbType DbType 数据库类型
dialect IDialect 方言实现类

建议单一数据库类型的均设置 dbType

使用示例:

java 复制代码
public class TestPlugin {
    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testPagePlugin(){
        Page<Student> page = new Page<>(2, 3);
        studentMapper.selectList(page, null);
        System.out.println(page);
        //当前查询列表
        System.out.println(page.getRecords());
        //总页数
        System.out.println(page.getPages());
        //总记录数
        System.out.println(page.getTotal());
        //是否有上一页
        System.out.println(page.hasNext());
        //是否有下一页
        System.out.println(page.hasPrevious());
    }
}

输出结果:

TEXT 复制代码
==>  Preparing: SELECT COUNT(*) AS total FROM student WHERE is_delete = '0'
==> Parameters: 
<==    Columns: total
<==        Row: 13
<==      Total: 1
==>  Preparing: SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' LIMIT ?,?
==> Parameters: 3(Long), 3(Long)
<==    Columns: id, name, age, email, is_delete
<==        Row: 2102299891458961409, light0, 30, null, 0
<==        Row: 2102299891660288001, light1, 31, null, 0
<==        Row: 2102299891660288002, light2, 32, null, 0
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@48cb2d73]
com.baomidou.mybatisplus.extension.plugins.pagination.Page@7e94e331
[]
5
13
true
true

Page 类继承了 IPage 类,实现了简单分页模型。如果你需要实现自己的分页模型,可以继承 Page 类或实现 IPage 类。

属性名 类型 默认值 描述
records List emptyList 查询数据列表
total Long 0 查询列表总记录数
size Long 10 每页显示条数,默认 10
current Long 1 当前页
orders List emptyList 排序字段信息
optimizeCountSql boolean true 自动优化 COUNT SQL
optimizeJoinOfCountSql boolean true 自动优化 COUNT SQL 是否把 join 查询部分移除
searchCount boolean true 是否进行 count 查询
maxLimit Long 单页分页条数限制
countId String XML 自定义 count 查询的 statementId

乐观锁

问题: A和B同一时间点获取某一个数据去更改它,A先更改,B后更改。B覆盖A,此时A以为自己改成功了,其实被B覆盖了。

乐观锁是一种并发控制机制,用于确保在更新记录时,该记录未被其他事务修改。

乐观锁的实现原理

乐观锁的实现通常包括以下步骤:

  1. 读取记录时,获取当前的版本号(version)。
  2. 在更新记录时,将这个版本号一同传递。
  3. 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion。
  4. 如果版本号不匹配,则更新失败。

MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。

修改bean对象

java 复制代码
package com.light.mybatisplus.pojo;


import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@TableName("student")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableLogic // 软删除
    private String isDelete;
    @Version //标记乐观锁版本号
    private Integer version;

    public Student(Long id, String name, Integer age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

然后直接修改就可以了,每次更改都会更改版本

通用枚举

给数据库表中添加 sex字段 int类型

添加枚举类

java 复制代码
package com.light.mybatisplus.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum SexEnum {
    MALE(1, "男"),
    FEMALE(2, "女");

    @EnumValue  // 注意标记这个字段的值写入数据库
    private Integer sex;
    private String sexName;

    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}

注意一定要标注这个@EnumValue注解 修改实体类

java 复制代码
package com.light.mybatisplus.pojo;


import com.baomidou.mybatisplus.annotation.*;
import com.light.mybatisplus.enums.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@TableName("student")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private SexEnum sex;
    @TableLogic // 软删除
    private String isDelete;
    @Version //标记乐观锁版本号
    private Integer version;

    public Student(Long id, String name, Integer age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

多数据源环境

适用于多种场景:纯粹多库、读写分离、一主多从、混合模式等

dynamic-datasource

dynamic-datasource 是一个开源的 Spring Boot 多数据源启动器,提供了丰富的功能,包括数据源分组、敏感信息加密、独立初始化表结构等

特性

  • 数据源分组:适用于多种场景,如读写分离、一主多从等。
  • 敏感信息加密 :使用 ENC() 加密数据库配置信息。
  • 独立初始化:支持每个数据库独立初始化表结构和数据库。
  • 自定义注解 :支持自定义注解,需继承 DS。
  • 简化集成:提供对 Druid、HikariCP 等连接池的快速集成。
  • 组件集成:支持 Mybatis-Plus、Quartz 等组件的集成方案。
  • 动态数据源:支持项目启动后动态增加或移除数据源。
  • 分布式事务:提供基于 Seata 的分布式事务方案。

约定

  • 本框架专注于数据源切换,不限制具体操作。
  • 配置文件中以下划线 _ 分割的数据源首部为组名。
  • 切换数据源可以是组名或具体数据源名。
  • 默认数据源名为 master,可通过 spring.datasource.dynamic.primary 修改。
  • 方法上的注解优先于类上的注解。

引入依赖

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
    <version>${version}</version>
</dependency>

配置

yml 复制代码
spring:
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认false, true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: ENC(xxxxx)
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver

操作指定数据源

java 复制代码
@DS("master") //指定所操作的数据源
@Service
public class UserSeryiceImpl extends ServiceImpl<UserMapper, User> implements Userservice{
}

MybatisX

这样就可以快速创建Pojo、Service、ServiceImpl、Mapper、MapperImpl包及文件。

快速创建curd方法

选择提示后,然后alt+enter:

生成代码

自动生成的xml

xml 复制代码
<insert id="insertSelective">
    insert into login
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">id,</if>
        <if test="username != null">username,</if>
        <if test="password != null">password,</if>
        <if test="nickname != null">nickname,</if>
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">#{id,jdbcType=NUMERIC},</if>
        <if test="username != null">#{username,jdbcType=VARCHAR},</if>
        <if test="password != null">#{password,jdbcType=VARCHAR},</if>
        <if test="nickname != null">#{nickname,jdbcType=VARCHAR},</if>
    </trim>
</insert>
相关推荐
ee又momo1 小时前
MyBatis-Plus 用了就回不去,这些功能真香
mybatis
大漠孤烟s6 天前
4.Mybatis学习-动态sql、缓存、逆向工程
mybatis
毕业设计7038 天前
(免费领源码) SpringBoot 游戏交易平台17600-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
java·spring boot·mysql·决策树·mybatis·idea·推荐算法
程序员JerrySUN8 天前
Jetson Edge AI 实战01:Nano、Xavier、Orin 怎么选?Jetson 硬件选型详解【视频讲解】
java·数据库·redis·安全·mybatis
linmengmeng_13148 天前
【总结】MyBatis-Plus批量插入与清表的正确姿势
java·spring boot·mysql·mybatis
vx_BS813309 天前
【项目编号:project51381】Spring Boot 婚纱摄影管理系统:套餐预约、在线咨询、支付与客片展示的一站式业务实现
java·spring boot·eclipse·tomcat·mybatis
jaysee-sjc10 天前
【苍穹外卖】Day01:从零认识企业级项目开发
java·开发语言·数据库·mysql·spring·intellij-idea·mybatis
程序猿乐锅10 天前
【黑马点评 | 第二篇】Redis 缓存更新策略与商铺缓存实现
java·网络·redis·spring·mybatis
萧瑟余晖10 天前
MyBatis 核心原理详解
架构·mybatis