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>
相关推荐
云絮.1 天前
mybatis(二)
java·数据库·mybatis
夕除2 天前
redis--主从还是集群,内存
java·spring·mybatis
驭渊的小故事2 天前
MyBatis-Plus 实战指南:从基础 CRUD 到条件构造器与自定义 SQL(二)
数据库·sql·mybatis
毕业设计7033 天前
(免费领源码)基于node的学生公寓管理系统06412- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·mysql·node.js·maven·mybatis·express·b/s
驭渊的小故事3 天前
MyBatis XML 开发实战:从配置到 CRUD 全解析
xml·java·mybatis
云絮.4 天前
Mybatis(一)
java·数据库·mybatis
她的男孩6 天前
权限通了、列表有数,一点分页就炸Forge Admin 用 COUNT(1) 扛住复杂待办
java·spring boot·mybatis
paopaokaka_luck7 天前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
大牧师7 天前
Mybatis 框架教程
java·数据库·mybatis·数据持久化·orm
代码什么用7 天前
MyBatis使用PageHelper && MyBatis的注解式开发
mybatis