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.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.4.1version>
dependency>

配置数据库

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为`Asia/Shanghai
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=Asia/Shanghai
    username: root
    password: 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 {

	@Autowired
	private UserDao userDao;
	@Test
	public 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容器初始化后,框架会生成代理对象完成注入
     */
    @Autowired
    private UserMapper userMapper;

    /**
     * int insert(T t)
     */
    @Test
    void testSave() {
        User user = User.builder()
                .name("时帆")
                .age(18)
                .password("111")
                .tel("13511111111")
                .build();
        userMapper.insert(user);
    }

删除

int deleteById(Serializable id)

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

修改

int updateById(T t)

    /**
     * int updateById(T t)
     */
    @Test
    void 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)
     */
    @Test
    void testSelectById(){
        User user = userMapper.selectById(5L);
        System.out.println("user = " + user);
    }

查询所有

List selectList(Wrapper queryWrapper)

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

分页查询

IPage selectPage(IPage page,Wrapper queryWrapper)

步骤1:调用方法传入参数获取返回值
    /**
     * IPage selectPage(IPage page , Wrapper queryWrapper)
     */
    @Test
    void 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 {
    
    @Bean
    public 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.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为`Asia/Shanghai
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=Asia/Shanghai
    username: root
    password: root
  main:
    #关闭springboot启动log
    banner-mode: off

mybatis-plus:
  configuration:
    # 开启mp日志输出到控制台,影响性能,调试用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    #关闭mp启动log打印
    banner: off
引入依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        <dependency>
            <groupId>com.mysqlgroupId>
            <artifactId>mysql-connector-jartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.2.16version>
        dependency>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.1version>
        dependency>
    dependencies>
logback.xml

<configuration>

configuration>

条件查询

  • Wrapper
    • QueryWrapper
    • LambdaQueryWrapper

    @Autowired
    private UserMapper userMapper;

    /**
     * 测试构建条件查询
     */
    @Test
    void testGetAll() {
        /*
        方式一:
        lt表示小于,即条件为查询年龄小于30的数据
        缺点:字段名出错难以察觉
         */
        /*
        QueryWrapper qw = new QueryWrapper();
        qw.lt("age",30);
        List users = userMapper.selectList(qw);
        System.out.println("users = " + users);
        */

        /*
        方式二:
        必须指定泛型,调用lambda()方法开启lambda表达式的使用
        缺点:多了一层.lambda()调用
         */
        /*
        QueryWrapper qw = new QueryWrapper();
        qw.lambda().lt(User::getAge,30);
        List 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值判定


    /**
     * 测试构建多条件查询
     */
    @Test
    void 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()
    /**
     * 查询投影
     */
    @Test
    void testGetAll2() {
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        //需求:查询指定字段:id,name,age
        /*
        lqw.select(User::getId,User::getName,User::getAge);
        List 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表达式只支持实体类属性字段
     */
    @Test
    void 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);
    }

    @Test
    void 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:相当于=
     */
    @Test
    void 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:相当于>=
     */
    @Test
    void 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%
     */
    @Test
    void 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等方法
     */
    @Test
    void 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类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
     * 综上:根据实际情况选择
     */
    @Test
    void testInsert(){
        User user = User.builder()
                .name("xiaoming")
                .age(23)
                .tel("12345678345")
                .password("333")
                .build();
        userMapper.insert(user);
    }

多记录操作

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

测试如下:


    /**
     * 测试批量删除
     */
    @Test
    void testDeleteByIds(){
        List<Long> list = new ArrayList<>();
        list.add(1647447562652774401L);
        list.add(1647452538326233089L);
        list.add(1647452707453181954L);
        userMapper.deleteBatchIds(list);
    }

    /**
     * 测试批量查询
     */
    @Test
    void 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
      # 逻辑删除字面值:未删除为0
      logic-not-delete-value: 0
      # 逻辑删除字面值:删除为1
      logic-delete-value: 1
测试

    /**
     * 测试逻辑删除
     * 用途:解决物理删除(直接删除表中数据,delete操作)会对数据造成伤害的问题
     * 步骤:在表中添加一个字段(添加默认值属性),用于标识当前数据是否被删除
     * 在实体类中添加对应的字段并使用@TableLogic注解标识该字段,给定value和delval属性的值即可
     * 逻辑删除本质上执行的是更新语句,配置了逻辑删除字段后,查询也会自动带上该字段,只查询未被删除的数据
     * 若需要查询已被删除的数据则需要自己手写实现
     */
    @Test
    void 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.更改数据值,更新数据
     */
    @Test
    void 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);
    }

    /**
     * 模拟多线程情况修改同一条数据
     */
    @Test
    void testOptimisticLock1(){
        //先查询需要修改的数据
        User user = userMapper.selectById(3L);//取得的version值为2
        User user1 = userMapper.selectById(3L);//取得的version值为2

        user1.setName("user1");
        userMapper.updateById(user1);//修改成功,version->3

        user.setName("user");
        userMapper.updateById(user);//修改失败,此时version值已被修改为3
    }

你可能感兴趣的:(Java,java,mybatis)