WHCSRL 技术网

MybatisPlus--CRUD(增删查改)--狂神说

学习视频:https://www.bilibili.com/video/BV17E411N7KN?p=2&spm_id_from=pageDriver

Insert插入。

测试类下MybatisPlusApplicationTests

//插入--测试
@Test
public void testInsert(){
    User user = new User();
    user.setName("fslm-studyMybatisPlus");
    user.setAge(20);
    user.setEmail("2384709456@qq.com");
    //帮我们自动生成id
    int result = userMapper.insert(user);
    //受影响的行数
    System.out.println(result);
    //发现id会自动回填
    System.out.println(user);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

结果
在这里插入图片描述

主键生成策略

数据库插入的id为默认值为:全局的唯一id
详细了解分布式系统唯一id生成的,戳:https://www.cnblogs.com/haoxinyue/p/5208136.hyml
数据库自增长序列或字段
优点:
1、 简单,代码方便,性能可以接受
2、 数字ID天然排序,对分页或者需要排序的结果很有帮助
缺点:
1、 不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理
2、 在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险
3、 在性能达不到要求的情况下,比较难于扩展
4、 如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦
5、 分表分库的时候会有麻烦
UUID
优点:
1、 简单,代码方便
2、 生成ID性能非常好,基本不会有性能问题
3、 全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对
缺点:
1、 没有排序,无法保证趋势递增
2、 UUID往往是使用字符串存储,查询的效率比较低
3、 存储空间比较大,如果是海量数据库,就需要考虑存储量的问题
4、 传输数据量大
5、 不可读
Redis生成ID
当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作INCR和INCRBY来实现。
可以使用Redis集群来获取更高的吞吐量。
优点:
1、 不依赖与数据库,灵活方便,且性能优于数据库
2、 数字ID天然排序,对分页或者需要排序的结果很有帮助
缺点:
1、 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度
2、 需要编码和配置的工作量比较大
Twitter的snowflake算法(雪花算法)
Snowflake是Twiiter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是:
使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心【北京、山东……】,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号,永远是0。可以保证几乎全球唯一
主键生成策略源码理解:
在这里插入图片描述

从mybatis-plus 3.3.0版本起
默认ASSIGN_ID全局唯一id

自增主键

配置自增主键
实体类User中的字段上,也就是上图中的字段上配置这个 @TableId(type = IdType. AUTO)
在这里插入图片描述

要求:数据库字段一定要是自增
在这里插入图片描述

查看数据库中自增id的值
在这里插入图片描述
再次运行插入测试,实现自增
在这里插入图片描述

更新Update

同样在MybatisPlusApplicationTests测试类

//更新--测试
@Test
public void testUpdate(){
    User user = new User();
    user.setName("qiu intelligence quotient");
//通过条件自动拼接动态sql
    user.setId(5L);//修改主键为5的字段
user.setAge(21);
    user.setEmail("2384709456@qq.com"); 
    int u = userMapper.updateById(user);
    //受影响的行数
    System.out.println(u);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在User类中,修改主键类型
在这里插入图片描述
运行
在这里插入图片描述
查看数据库
在这里插入图片描述

自动填充

创建时间、修改时间。这些个操作都是自动化完成的,我们不希望手动更新。
阿里巴巴开发手册规定:所有的数据库表:gmt_create、gmt_modified几乎所有的表都要配置上,而且需要自动化。
方式一、数据库级别(实际工作中不允许修改数据库)
1、 在表中新增字段create_time,update_time
在这里插入图片描述

2、 再次运行插入方法,pojo实体类同步更新数据,即在User实体类里面添加这两个字段

private Data createTime;
private Data updateTime;
  • 1
  • 2

在这里插入图片描述

3、 结果
在这里插入图片描述

方式二、代码级别
1、 删除数据库的默认值,更新操作!
在这里插入图片描述

2、 在实体类User.java上的字段属性上要增加注解

//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Data createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Data updateTime;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、 编写处理器来处理这个注解即可
写法一、新建一个handler包,然后新建MyMetaObjectHandler

package com.mybatisplus.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component //处理器别忘了加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill......");
        //点进源码去看,把这三个参数给拿出来 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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

写法二,看哪个不报错就选哪个

package com.mybatisplus.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component //处理器别忘了加到IOC容器中Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill......");
        boolean hasSetter = metaObject.hasSetter("createTime");
        if (hasSetter) {
            this.setFieldValByName("createTime", new Date(), metaObject);
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill......");
        Object val = getFieldValByName("updateTime", metaObject);
        if (val == null) {
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

User类中,将IDType改成自增,也就是改成:@TableId(type = IdType.AUTO)
在这里插入图片描述

测试插入,出现错误:Type handler was null on parameter mapping for property 'createTime'. It was either not specified and/or could not be found for the javaType (lombok.Data) : jdbcType (null) combination. -->(在属性'createTime'的参数映射上,类型处理程序为空。 没有指定和/或找不到javaType (lombok.Data): jdbcType (null)组合。 )
在这里插入图片描述

原因就是strictInsertFillstrictUpdateFill 方法,用该方法时如果 没有将将Date类型改为LocalDateTime会导致填充的时间为null。详细了解,戳
https://blog.csdn.net/m0_58704133/article/details/120393185

修改,在User,MyMetaObjectHandler中修改
User
在这里插入图片描述

MyMetaObjectHandler

package com.mybatisplus.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Date;
@Slf4j
@Component //处理器别忘了加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill......");
        /**
         * strictInsertFill 和 strictUpdateFill 方法,用该方法时
         * 如果没有将Date类型改为LocalDateTime会导致填充的时间为null
         */
        /*修改为*/
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill......");
        /*修改为*/
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

再次测试插入,成功
在这里插入图片描述

测试更新,查看数据库,注意看最后一格,修改的时间
在这里插入图片描述

乐观锁

面试过程中,会问到乐观锁、悲观锁。
乐观锁:顾名思义,十分乐观,总认为不会出现问题,无论干什么都不去上锁!若出现了问题,再次更新值测试。
悲观锁:顾名思义,十分悲观,总认为总会出现问题,无论干什么都会上锁,再去操作。
乐观锁:
乐观锁实现方式:

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

在这里插入图片描述
在这里插入图片描述

测试MybatisPlus的乐观锁插件
1、给数据库增加version字段
在这里插入图片描述

将version这一列的版本改为1,然后去idea里面刷新即可看到版本号这一列全为1
在这里插入图片描述

2、User实体类上同步上version字段
在这里插入图片描述

3、注册组件
新建config包,在里面建一个MybatisPlusConfig类
MybatisPlusConfig

package com.mybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor; 
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
//扫描mapper文件夹
@MapperScan("com.mybatisplus.mapper")
@EnableTransactionManagement //自动管理事务
@Configuration //配置类
public class MybatisPlusConfig {
    //注册乐观锁插件-->可以去官网上直接复制过来
    //Interceptor拦截器,在执行操作的时候去进行帮我们自动化处理
   // 新版本的写法
   /* @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }*/
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

注意:上面的扫描文件夹注解是在主启动类上剪切过来的,让spring这个这里帮我们自动做扫描。
4、测试类上MybatisPlusApplicationTests

//测试--乐观锁成功
@Test
public void testOptimisticLocker(){
    //1、查询用户信息 查询id为4的那一条数据
    User user = userMapper.selectById(4L);
    //2、修改用户信息
    user.setName("On October 31st");
    user.setEmail("847475198@qq.com");
    //3、执行更新操作
    userMapper.updateById(user);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

运行发现报错,原因是mybatis-plus版本升级造成老版本配置方式不生效。
在这里插入图片描述

MybatisPlusConfig上,修改成这个内容即可

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

改过来之后运行成功,返回结果,version的值变为了2
在这里插入图片描述

5、测试类上

//测试--乐观锁失败  多线程下
@Test
public void testOptimisticLocker2(){
    /**
     * 线程一在执行的时候,还没有更新,另外一个线程前线更新完成
     * 然后虽然线程一虽然设置了值,但是由于并发情况下被另外一个线程插队了,更新可能会失败
     * 如果没有乐观锁,那么这个线程会覆盖那个抢更新完成的线程(插队线程)的值
     */
   /*线程一*/
    //1、查询用户信息  查询id为4的那一条数据
    User user = userMapper.selectById(4L);
    //2、修改用户信息
    user.setName("xiancheng->first");
    user.setEmail("847475198@qq.com");
    /*线程二   模拟另外一个线程执行了插队操作  */
    //1、查询用户信息  查询id为4的那一条数据
    User user2 = userMapper.selectById(4L);
    //2、修改用户信息
    user2.setName("xiancheng->second");
    user2.setEmail("847475198@qq.com");
    //3、执行更新操作
    userMapper.updateById(user);//如果没有乐观锁就会覆盖插队线程的值
    userMapper.updateById(user2);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

运行结果
在这里插入图片描述

查询操作

测试类中

//测试--查询单条数据
@Test
public void testSelectById(){
    User user = userMapper.selectById(4L);
    System.out.println(user);
}
//测试--查询多条数据
@Test
public void testSelectByBatchId(){
	//查询前三条数据
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);
}
//测试--条件查询
@Test
public void testSelectByBatchIds(){
    HashMap<String,Object> map = new HashMap<>();
    //自定义要查询的内容
    //查询名字为Jackfslm-studyMybatisPlus的数据,但是这个是只查询名字,那么结果就会只有名字
    map.put("name","fslm-studyMybatisPlus");
    List<User> users = userMapper.selectByMap(map);
    userMapper.selectByMap(map);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

结果:单条数据
在这里插入图片描述

多条数据结果
在这里插入图片描述

条件查询结果
在这里插入图片描述

分页查询
1、 原始的limit进行分页
2、 pageHelper第三方插件
3、 MybatisPlus也内置了乐观锁
MybatisPlusConfig

//分页插件 -拦截器
@Bean
public PaginationInterceptor paginationInterceptor(){
    return new PaginationInterceptor();
}
测试类中
//测试--分页查询
@Test
public void testPage(){
    //参数一:当前页    参数二,页面大小
    Page<User> page = new Page<>(1,5);
    //queryWrapper:高级查询
    userMapper.selectPage(page,null); 
    page.getRecords().forEach(System.out::println);
//查询总数
System.out.println(page.getTotal());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

查询结果
在这里插入图片描述

删除操作

基本的删除操作
测试类中写上这些基本的删除操作,结果就不演示了

//测试--删除
@Test
public void testDeleteById(){
    //根据id删除用户
    userMapper.deleteById(1432251946370142210L);
}
//测试--批量删除
@Test
public void testDeleteBatchId(){
    //删除id为 1432232563404996610,1432251946370142211的数据
    userMapper.deleteBatchIds(Arrays.asList(1432232563404996610L,1432251946370142211L));
}
//测试--条件删除
@Test
public void testDeleteMap(){
    Map<String,Object> map = new HashMap<>();
    //删除名字为qiu intelligence quotient的数据
    map.put("name","qiu intelligence quotient");
    userMapper.deleteByMap(map);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在工作中会遇到一些逻辑删除的问题。
逻辑删除
1、 物理删除:从数据库中直接移除
2、 逻辑删除:在数据库中没有被移除,而是通过一个变量来让它失效。delete = 0 => delete = 1
(正常的用户的delete = 0,删除完之后让delete = 1,即让delete = 0 变成 delete = 1)
管理员可以查看被删除的记录,放置数据的丢失,类似于回收站。
测试:
1、在数据表中增加一个deleted的字段
在这里插入图片描述

2、 User实体类中增加属性
在这里插入图片描述

3、配置
在配置文件application.properties中添加

#配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1  #删除了的值 让它为1
mybatis-plus.global-config.db-config.logic-not-delete-value=0  #没有删除的值 为0
  • 1
  • 2
  • 3

注意:在版本3.1.1开始不需要在配置类里面再配置逻辑删除组件了。
4、 测试—在测试类中

//测试--逻辑删除
@Test
public void testLogicDelete() {
    //删除id为5的数据
    int count = userMapper.deleteById(5L);
    System.out.println("受影响的行数:"+count);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试返回结果,数据并没有被删除,deleted字段的值由0变成了1。记录依旧还在数据库,但是值已经变化
在这里插入图片描述

逻辑删除的本质走的是更新操作并不是删除操作。
在这里插入图片描述
再测试一下查询,看能不能查询出来
在这里插入图片描述

因插件版本问题,戳https://www.cnblogs.com/123-shen/p/14074672.html

条件构造器Wrapper

写一些复杂的sql就可以使用它来替代
新建一个test
在这里插入图片描述

WrapperTest

package com.mybatisplus;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mybatisplus.mapper.UserMapper;
import com.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class WrapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void contextLoads() {
        //查询name不为空的用户,并且邮箱不为null的用户,年龄>=12的
        QueryWrapper<User> wrapper = new QueryWrapper<>(); 
        wrapper
                .isNotNull("name")
                .isNotNull("email")
                .ge("age",12);
        userMapper.selectList(wrapper).forEach(System.out::println);//跟map作对比
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

运行结果。记住查看输出的SQL进行分析
在这里插入图片描述

//查询一个用户
@Test
void test2(){
    //查询名字为 Tom 的用户
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name","Tom");
    //引入自个建的user类
    User user = userMapper.selectOne(wrapper);
    System.out.println(user);
}
@Test
void test3(){
    //查询年龄在20-30之间的用户
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //eq 相等
    wrapper.between("age",20,30);//闭区间的
    //查询一个用户,想要出现多个结果用 list或者 map
    Integer count = userMapper.selectCount(wrapper);
    System.out.println(count);//5条数据
}
//模糊查询
@Test
void test4(){
    //查询名字
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //左和右,就是%%在左边(%%xxx)或者右边(xxx%%)。如果是两边都要匹配就两边加 %%xxx%%
    wrapper
            .notLike("name","f") //查询名字不包括f的字段
            .likeRight("email","2");//查询邮箱是2开头的数据
    List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
    userMapper.selectMaps(wrapper).forEach(System.out::println);
}
//联接查询(内查询)
@Test
void test5(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //id在子查询中查出来   查询id>3的数据
    wrapper.inSql("id","select id from user where id>3");
    List<Object> objects = userMapper.selectObjs(wrapper);
    objects.forEach(System.out::println);
}
//排序
@Test
void test6(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //通过id进行排序  desc:降序,asc:升序
    wrapper.orderByDesc("id");
    List<Object> objects = userMapper.selectObjs(wrapper);
    objects.forEach(System.out::println);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

这里就不测试结果了。
想了解更多的话移步去官方文档,https://mp.baomidou.com/guide/wrapper.html#abstractwrapper

推荐阅读