> 文章列表 > MybatisPlus

MybatisPlus

MybatisPlus

目录

  • MybatisPlus
    • 入门案例
      • 步骤
      • 代码实现
        • 数据库及表
        • MybatisPlus的Maven坐标
        • 配置数据
        • 创建实体类
          • User
        • Mapper接口
          • UserMapper
        • 引导类
        • 测试类
    • 简介
    • 使用MP做标准数据层开发
      • 标准CRUD
        • 新增
        • 删除
        • 修改
        • 根据id查询
        • 查询所有
        • 分页查询
          • 步骤1:调用方法传入参数获取返回值
          • 步骤2:设置分页拦截器
      • DQL编程控制
        • 准备环境
          • application.yml
          • 引入依赖
          • logback.xml
        • 条件查询
        • 查询投影
        • 查询条件
      • 映射匹配兼容性
      • DML编程控制
        • id生成策略
        • 简化配置
        • 测试
        • 多记录操作
        • 逻辑删除
          • 测试
        • 乐观锁
          • 概念
          • 实现思路
          • 步骤

MybatisPlus

入门案例

步骤

  1. 创建数据库及表,导入数据
  2. 创建springboot项目,勾选实用技术,引入依赖
  3. 添加相关配置信息
  4. 编写实体类,创建mapper层接口
  5. 引导类添加mapper层接口扫描注解
  6. 编写测试类

代码实现

数据库及表

create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (id bigint(20) primary key auto_increment,name varchar(32) not null,password  varchar(32) not null,age int(3) not null ,tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'传智播客','itcast',15,'4006184000');

MybatisPlus的Maven坐标

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version>
</dependency>

配置数据库

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为`Asia/Shanghaiurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=Asia/Shanghaiusername: rootpassword: root

创建实体类

User
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {private Long id;private String name;private String password;private Integer age;private String tel;private Integer online;
}

Mapper接口

UserMapper
public interface UserMapper extends BaseMapper<User> {
}

引导类

@SpringBootApplication
@MapperScan("com.shifan.mapper")//自动扫描该包下的所有接口
public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}}

测试类

@SpringBootTest
class MpApplicationTests {@Autowiredprivate UserDao userDao;@Testpublic void testGetAll() {List<User> userList = userDao.selectList(null);System.out.println(userList);}
}

简介

MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率

官网:MybatisPlus

使用MP做标准数据层开发

标准CRUD

新增

int insert(T t)

    /*tips:爆红是因为UserMapper为接口,无法实例化实现注入服务器启动IOC容器初始化后,框架会生成代理对象完成注入*/@Autowiredprivate UserMapper userMapper;/*** int insert(T t)*/@Testvoid testSave() {User user = User.builder().name("时帆").age(18).password("111").tel("13511111111").build();userMapper.insert(user);}

删除

int deleteById(Serializable id)

    /*** int deleteById(Serializable id)*/@Testvoid testDeleteById(){userMapper.deleteById(2L);}

修改

int updateById(T t)

    /*** int updateById(T t)*/@Testvoid testUpdateById(){User user = User.builder().name("voracity").age(20).id(3L).password("333").build();userMapper.updateById(user);}

根据id查询

T selectById(Serializable id)

    /*** T selectById(Serializable id)*/@Testvoid testSelectById(){User user = userMapper.selectById(5L);System.out.println("user = " + user);}

查询所有

List selectList(Wrapper queryWrapper)

    /*** List<T> selectList(Wrapper<T> queryWrapper)*/@Testvoid testSelectList(){List<User> users = userMapper.selectList(null);System.out.println("users = " + users);}

分页查询

IPage selectPage(IPage page,Wrapper queryWrapper)

步骤1:调用方法传入参数获取返回值
    /*** IPage<T> selectPage(IPage<T> page , Wrapper<T> queryWrapper)*/@Testvoid testSelectPage(){//创建分页对象,设置分页参数,1为第一页,3为显示记录数IPage<User> page = new Page<>(1,3);userMapper.selectPage(page, null);//获取分页结果System.out.println("当前页码值:"+page.getCurrent());System.out.println("每页显示条数:"+page.getSize());System.out.println("一共多少页:"+page.getPages());System.out.println("一共多少条数据:"+page.getTotal());System.out.println("数据:"+page.getRecords());}
步骤2:设置分页拦截器

这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。

@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//1 创建MybatisPlusInterceptor拦截器对象MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();//2 添加分页拦截器mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mpInterceptor;}
}

DQL编程控制

准备环境

application.yml
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为`Asia/Shanghaiurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=Asia/Shanghaiusername: rootpassword: rootmain:#关闭springboot启动logbanner-mode: offmybatis-plus:configuration:# 开启mp日志输出到控制台,影响性能,调试用log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:#关闭mp启动log打印banner: off
引入依赖
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></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><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.16</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency></dependencies>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--取消初始化spring日志打印,resources目录下添加logback.xml,名称固定-->
</configuration>

条件查询

  • Wrapper
    • QueryWrapper
    • LambdaQueryWrapper
@Autowiredprivate UserMapper userMapper;/*** 测试构建条件查询*/@Testvoid testGetAll() {/*方式一:lt表示小于,即条件为查询年龄小于30的数据缺点:字段名出错难以察觉*//*QueryWrapper qw = new QueryWrapper();qw.lt("age",30);List<User> users = userMapper.selectList(qw);System.out.println("users = " + users);*//*方式二:必须指定泛型,调用lambda()方法开启lambda表达式的使用缺点:多了一层.lambda()调用*//*QueryWrapper<User> qw = new QueryWrapper();qw.lambda().lt(User::getAge,30);List<User> users = userMapper.selectList(qw);System.out.println("users = " + users);*//*方式三:使用LambdaQueryWrapper类,不在需要调用lambda()*/LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.lt(User::getAge,30);List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
  • 多条件构建:or()

  • null值判定

/*** 测试构建多条件查询*/@Testvoid testGetAll1() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();//需求:查询年龄在10岁到30岁之间的用户信息//lqw.lt(User::getAge,30);//lqw.gt(User::getAge,10);//支持链式编程//lqw.lt(User::getAge,30).gt(User::getAge,10);//需求:查询年龄小于10或年龄大于30的数据//lqw.lt(User::getAge,10).or().gt(User::getAge,30);/*null值判定,多条件查询时,可能存在条件传递为null的情况需求:根据输入年龄范围来查询符合条件的记录(年龄上限,下限,可能不传递数据,即为null)传统解决方案:使用if语句判断*//*QueryUser qu = new QueryUser();qu.setAge(10);if (qu.getAge()!=null){lqw.gt(User::getAge,qu.getAge());}if (qu.getAge1()!=null){lqw.lt(User::getAge,qu.getAge1());}*//*MybatisPlus解决方案:*/QueryUser qu = new QueryUser();qu.setAge(10);lqw.gt(null!=qu.getAge(),User::getAge,qu.getAge());lqw.lt(null!=qu.getAge1(),User::getAge,qu.getAge1());List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}

查询投影

  • 查询指定字段:select()
    /*** 查询投影*/@Testvoid testGetAll2() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();//需求:查询指定字段:id,name,age/*lqw.select(User::getId,User::getName,User::getAge);List<User> users = userMapper.selectList(lqw);*///若不使用lambda表达式,则需要手动设置字段QueryWrapper<User> qw = new QueryWrapper<>();qw.select("id","name","age");List<User> users = userMapper.selectList(qw);System.out.println("users = " + users);}
  • 聚合查询:eg:select(“count(id) count”)

  • 分组查询:eg:groupBy(User::getTel)

/*** 聚合函数查询* 聚合函数不支持lambda表达式,lambda表达式只支持实体类属性字段*/@Testvoid testGetAll3() {QueryWrapper<User> qw = new QueryWrapper<>();//qw.select("count(id) count");//加上分组条件qw.select("count(id) count ,tel").groupBy("tel");List<Map<String, Object>> maps = userMapper.selectMaps(qw);System.out.println("maps = " + maps);}@Testvoid test(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.select(User::getTel);lqw.groupBy(User::getTel);List<Map<String, Object>> users = userMapper.selectMaps(lqw);System.out.println("users = " + users);}

查询条件

  • 等值查询:eq()
    /*** 等值查询* 需求:根据用户名和密码查询用户信息* eq:相当于=*/@Testvoid testGetOne() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.eq(User::getName,"John");lqw.eq(User::getPassword,"shifan");User user = userMapper.selectOne(lqw);System.out.println("user = " + user);}
  • 范围查询:lt(),le(),gt(),ge(),between()
    /*** 范围查询* 需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询* le:相当于<=* ge:相当于>=*/@Testvoid testGetAll4(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.between(User::getAge,10,20);List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
  • 模糊查询:like(),likeLeft(),likeRight()
    /*** 模糊查询* 需求:查询表中name属性的值以`J`开头的用户信息,使用like进行模糊查询* like():在属性值字段的左右都加%,eg:lqw.like(User::getName,"J") ==> %J%* likeLeft():在属性值字段左边加%,eg:%J* likeRight():在属性值字段的右边加%,eg:J%*/@Testvoid testGetAll5() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.likeRight(User::getName,"J");List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
  • 排序查询:orderBy(),orderByAsc(),orderByDesc()
    /*** 排序查询* 需求:查询所有数据,然后按照id降序* orderBy(boolean condition, boolean isAsc, R... columns):* 第一个参数:是否排序,第二个参数:是否升序,true升序,false降序,第三个参数:字段名,可设置一个或多个* - orderByAsc/Desc(单个column):按照指定字段进行升序/降序* - orderByAsc/Desc(多个column):按照多个字段进行升序/降序* - orderByAsc/Desc*   - condition:条件,true添加排序,false不添加排序*   - 多个columns:按照多个字段进行排序* 此外还有isNull,isNotNull,in,notIn等方法*/@Testvoid testGetAll6() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.orderBy(true,false,User::getId);List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}

映射匹配兼容性

  • @TableField(value=“表字段名”)

  • @TableField(select = false)

  • @TableField(exist = false)

  • @TableName(“表名”)

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")//映射数据库表名,用于数据库表名和实体类名不一致情况
public class User {private Long id;private String name;@TableField(value = "pwd",select = false)//字段映射,value属性为数据库表中字段名,selcet属性用于设置该字段是否被查询private String password;private Integer age;private String tel;@TableField(exist = false)//exist属性用于表明该字段在数据库表中是否存在private Integer online;
}

DML编程控制

id生成策略

  • @TableId(type = IdType.NONE)

NONE表示不使用id生成策略,与INPUT类似,需要手动注入id

  • @TableId(type = IdType.AUTO)

AUTO表示使用数据库的主键id自增,前提是数据库的主键id设置了自增属性

  • @TableId(type = IdType.INPUT)

INPUT表示id使用手动注入的方式

  • @TableId(type = IdType.ASSIGN_ID)

ASSIGN_ID表示使用MP的雪花算法生成id

  • @TableId(type = IdType.ASSIGN_UUID)

ASSIGN_UUID表示使用UUID生成id

实际使用案例:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
//@TableName("tb_user")//映射数据库表名,用于数据库表名和实体类名不一致情况
public class User {//@TableId(type = IdType.NONE)// 不使用主键生成策略,类似Input//@TableId(type= IdType.AUTO)// 使用数据库主键自增,需要保证数据库主键字段有自增属性//@TableId(type = IdType.Input)// 用户手动输入,需要去除数据库主键自增属性,若不给定id值会报错//@TableId(type = IdType.ASSIGN_UUID)// 使用UUID生成主键id,主键类型应为字符串,数据库表中id长度至少32位,长度过小会插入失败//@TableId(type = IdType.ASSIGN_ID)// 使用雪花算法生成主键id(可兼容数值型与字符串型),若所传递的id为null,默认使用此策略,若传递了值则使用传递的值private Long id;private String name;@TableField(value = "pwd",select = false)//字段映射,value属性为数据库表中字段名,selcet属性用于设置该字段是否被查询private String password;private Integer age;private String tel;@TableField(exist = false)//exist属性用于表明该字段在数据库表中是否存在private Integer online;}

简化配置

在配置文件中添加如下配置即可实现全局使用雪花算法生成id

mybatis-plus:global-config:db-config:id-type: assign_id

以下配置用于配置全局表名前缀

mybatis-plus:global-config:db-config:table-prefix: tbl_

测试

/*** 测试主键自增策略* 拓展:分布式id* 当数据量足够大时就需要将数据存储在多台数据库服务器上* 例如订单表存储在不同服务器上,而此时主键若使用自增策略就可能出现冲突* 这时就需要一个全局唯一的id,称为分布式id** 雪花算法:* 雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。* 其生成的结果是一个64bit大小整数,它的结构分为四部分,如下:* 0-00000000 00000000 00000000 00000000 00000000 0-00000000 00-00000000 0000* 第一部分: 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。* 第二部分: 41bit-时间戳,用来记录时间戳,毫秒级* 第三部分: 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点* 第四部分: 序列号占用12bit,每个节点每毫秒从0开始不断累加,最多可以累加到4095,每毫秒内一共可以产生4096个ID* id生成策略对比:* - NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂* - AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用* - ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢* - ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键* 综上:根据实际情况选择*/@Testvoid testInsert(){User user = User.builder().name("xiaoming").age(23).tel("12345678345").password("333").build();userMapper.insert(user);}

多记录操作

需求:批量删除用户数据,批量查询用户数据

测试如下:

/*** 测试批量删除*/@Testvoid testDeleteByIds(){List<Long> list = new ArrayList<>();list.add(1647447562652774401L);list.add(1647452538326233089L);list.add(1647452707453181954L);userMapper.deleteBatchIds(list);}/*** 测试批量查询*/@Testvoid testSelectByIds(){List<Long> list = new ArrayList<>();list.add(1L);list.add(3L);list.add(4L);List<User> users = userMapper.selectBatchIds(list);System.out.println("users = " + users);}

逻辑删除

  • @TableLogic

实现步骤:

  1. 在数据库表中添加标识逻辑删除的字段
  2. 在实体类中添加对应的标识字段,并在此字段上添加@TableLogic注解
  3. 设置@TableLogic的value和delval属性值,value属性表示当前数据为正常数据,delval属性表示当前数据为
    已被删除的数据

实际应用:

    //@TableLogic(value = "0",delval = "1")//逻辑删除字段,标记当前数据是否被删除,value表示正常数据,delval表示该数据已被删除private Integer deleted;

全局配置:

mybatis-plus:global-config:db-config:# 逻辑删除字段名logic-delete-field: deleted# 逻辑删除字面值:未删除为0logic-not-delete-value: 0# 逻辑删除字面值:删除为1logic-delete-value: 1
测试
/*** 测试逻辑删除* 用途:解决物理删除(直接删除表中数据,delete操作)会对数据造成伤害的问题* 步骤:在表中添加一个字段(添加默认值属性),用于标识当前数据是否被删除* 在实体类中添加对应的字段并使用@TableLogic注解标识该字段,给定value和delval属性的值即可* 逻辑删除本质上执行的是更新语句,配置了逻辑删除字段后,查询也会自动带上该字段,只查询未被删除的数据* 若需要查询已被删除的数据则需要自己手写实现*/@Testvoid testLogicDelete(){userMapper.deleteById(1L);List<User> users = userMapper.selectList(null);System.out.println("users = " + users);}

乐观锁

概念

主要用于解决修改数据时,期望当前数据未被其他人修改

实现思路
步骤
/*** 乐观锁测试* 原理:* 给数据库表添加字段,如version默认值为1* 当两个线程同时修该一条数据时,需要先获取表中数据,拿到version的值* eg:线程A和B同时来修改id为1的用户的信息* 线程A查询id为1的用户信息,得到此时用户的version字段值为1* 线程B同样查询到id为1的用户信息,此时用户的version字段值为1* 假设线程B先于线程A修改了id为1的用户的信息,并将version的值加了1* 即:update user set name = ? , age = ? ,version = version + 1 where id = 1 and version = 1* 此时线程A再去修改用户信息时就会修改失败,因其修改语句为:* update user set name = ? , age = ? ,version = version + 1 where id = 1 and version = 1* 而version值已被修改为2,故修改失败* 实现步骤:* 1.在数据库表中添加标识字段,并设置默认值,eg:version字段,默认值1* 2.在实体类中添加对应的属性字段,并加上注解@Version* 3.添加乐观锁拦截器* 4.查询需要修改的数据,获取version值* 5.更改数据值,更新数据*/@Testvoid testOptimisticLock(){//没有设置version值,无法实现乐观锁/*User user = User.builder().id(3L).name("mp").password("399").tel("322").age(10).build();*///先查询需要修改的数据User user = userMapper.selectById(3L);//修改数据user.setName("mp");user.setAge(10);user.setPassword("3");//更新数据userMapper.updateById(user);}/*** 模拟多线程情况修改同一条数据*/@Testvoid testOptimisticLock1(){//先查询需要修改的数据User user = userMapper.selectById(3L);//取得的version值为2User user1 = userMapper.selectById(3L);//取得的version值为2user1.setName("user1");userMapper.updateById(user1);//修改成功,version->3user.setName("user");userMapper.updateById(user);//修改失败,此时version值已被修改为3}