MyBatis-Plus的基本使用

使用MyBatis-Plus能够带来的好处是什么呢?MyBatis-Plus和MyBatis是一个怎样的关系?

使用MyBatis-Plus好处是可以节省非常多代码量,少写SQL、还可以代码生成、从而加快我们在开发中的效率(拒绝加班、拒绝996MyBatis-Plus是对MyBatis框架进行了更强的封装,核心当然还是MyBatis。相当于在MyBatis的基础上新增了许多在实际开发中经常被使用到的功能
并且完全的简化了这些功能的实现代码,比如:分页、代码生成、乐观锁、逻辑删除等等...更好的帮助像我这种CRUD工程师进行高效率的项目开发

以上描述完全是个人理解,为了小伙伴们更好的学习MyBati-Plus可参考官方文档:https://baomidou.com

继续往下将带大家从Mybati-Plus的入门到常用的核心功能

1.mybatis-Plus快速入门体验

开发环境:SpringBoot 2.3.4+Mybatis-Plus 3.4.1+MySQL 8.0

注:不同的MyBatis-Plus版本可能在某些功能的配置上有所不同

  1. 创建数据库表并插入测试数据
<!--创建表-->
	CREATE TABLE user
	(
		id BIGINT(20) NOT NULL COMMENT '主键ID',
		name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
		age INT(11) NULL DEFAULT NULL COMMENT '年龄',
		email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
		PRIMARY KEY (id)
	);
<!--添加数据-->
	INSERT INTO user (id, name, age, email) VALUES
	(1, 'Jone', 18, 'test1@baomidou.com'),
	(2, 'Jack', 20, 'test2@baomidou.com'),
	(3, 'Tom', 28, 'test3@baomidou.com'),
	(4, 'Sandy', 21, 'test4@baomidou.com'),
	(5, 'Billie', 24, 'test5@baomidou.com');
  1. 新建一个SpringBoot项目并在pom.xml文件中导入所需的Maven依赖
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>
    
     <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
    </dependency>
  1. 在application.yaml文件中配置数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=Asia/Shanghai
    username: root
    password: libo
  1. 创建User实体类
@Data
/*全部参数的构造方法*/
@AllArgsConstructor
/*无参构造*/
@NoArgsConstructor
/*指定数据库表名*/
@TableName(value = "user")
/*实体类*/
public class User {
    
    

    private Long id;

    private String name;

    private Integer age;

    private String email;
}

  1. 创建userMapper接口并继承BaseMapper接口
    别忘了在启动类上加入@MapperScan(value = “com.mybatiPlus.mapper”) //扫描mapper文件
@Repository
/*Mapper接口*/
public interface userMapper extends BaseMapper<User> {
    
    


}
  1. 使用MyBatis-Plus操作数据(这里就测试下查询user表的所有数据)
@SpringBootTest
/*测试类*/
public class mybatisPlusTest {
    
    

    /*注入持久层userMapper接口*/
    @Autowired
    private userMapper userMapper;

    /*查询user表中所有数据*/
    @Test
    public void seleteByList() {
    
    
        /*Wrapper是一个条件构造器,这里暂且先不适用*/
        List<User> userList = userMapper.selectList(null);
        /*打印List*/
        userList.forEach(System.out::println);
    }

控制台打印的结果:
的确是把user表中的所有数据查询出来了,然而我们并没有编写SQL语句对吧,这就是MyBatis-Plus的强大之处。
在这里插入图片描述

这里就有两个问题:1.userMapper下的方法哪里来的?2.SQL是谁写的?
方法来自BaseMapper,因为我们继承了他并且传入了泛型。SQL语句当然是MyBatis-Plus中封装的啦,这只是MyBatis-Plus强大功能中的其中之一

2.配置日志输出

  1. 在application.yaml中加入
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. 再次测试查询所有方法,然后观察控制台与之前多了哪些东西?
    在这里插入图片描述

3.主键生成策略

什么是主键生成策略呢?在数据库中一张表的主键一般就是我们的id字段,但是主键字段的值就有很多种了。
比如常见的id自增,或者说是insert into插入数据的时候手动填入,那在MyBatis-Plus中又给我们带来哪些不同的生成策略呢?

  1. 插入一条数据到user表
    /*添加数据*/
    @Test
    public void insert() {
    
    
        User user = new User();
        /*名字*/
        user.setName("周杰伦");
        /*年龄*/
        user.setAge(18);
        /*email*/
        user.setEmail("[email protected]");
        /*添加数据*/
        userMapper.insert(user);
    }
  1. 查看日志输出,看MyBabtis-Plus默认给我们生成的id是什么?
    在这里插入图片描述
  2. 查看数据库
    在这里插入图片描述

问题:为什么会生成这么长一串id呢?或者说这个id是根据什么生成的呢?下面就介绍MyBatis的主键生成策略

MyBatis-Plus中不同的主键生成策略:

 1. IdType.AUTO:数据库自增id(数据库表也必须设置为自增id)
 2. IdType.NONE:该类型为未设置主键类型(和下面这个差不多)
 3. IdType.INPUT:用户输入ID(自定义id)
 4. IdType.ASSIGN_UUID:生成全局唯一UUID 	注:主键字段为字符串
 5. IdType.ID_WORKER:默认的全局唯一id (雪花算法)	MyBatis-Plus默认策略
 6. IdType.ID_WORKER_STR:是ID_WORKER的字符串表示法 (雪花算法)

注:IdType是一个枚举类

所以我们刚刚插入的数据生成的id就是使用MyBatis-Plus默认的生成策略IdType.ID_WORKER,其中采用了雪花算法。
主键生成策略的详细介绍可参考:分布式系统主键id生成策略

如果想要使用不同的策略需要在实体类的主键字段上加上注解 @TableId(type = IdType.ASSIGN_UUID)
比如我们再测试一个自动生成的UUID,因为UUID包含字母那就要把主键id字段改为String类型,数据库主键字段类型为varchar

    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    private String name;

    private Integer age;

    private String email;

在这里插入图片描述
再次执行我们之前的添加数据方法,查看结果
在这里插入图片描述
在这里插入图片描述
记得将实体类主键类型和数据库表主键字段类型改回来,上面只做一个测试。

4.单表删除操作

    /*根据id删除数据*/
    @Test
    public void delete() {
    
    
        /*根据id删除*/
        userMapper.deleteById(1342413749983252482L);
    }

在这里插入图片描述
另外再补充两个不同的删除方式:

根据id批量删除

    /*根据id批量删除*/
    @Test
    public void deleteByList() {
    
    
        /*批量删除id为1、2的数据*/
        List<Long> longs = Arrays.asList(1L, 2L);
        userMapper.deleteBatchIds(longs);
    }

根据Map条件删除

    /*根据Map条件删除*/
    @Test
    public void deleteByMap() {
    
    
        Map<String, Object> map = new HashMap();
        /*删除name为周杰伦的数据*/
        map.put("name", "周杰伦");
        userMapper.deleteByMap(map);
    }

5.单表逻辑删除

我们之前删除数据是直接从数据库表中删除的,删除之后表里面这条数据就没有了,对吧
那么逻辑删除就是:要删除的数据并不是真正的从表中删除,只是在查询时不展示这条数据即可。
例如就相当于回收站:被删除的数据在回收站,但是回收站并没有清空,我们只是判断它没有作用了,放到一边而已

如何实现:

  1. 数据库表新增一个字段并且给一个默认值
    假设我们设置逻辑删除字段的值默认为0
    在这里插入图片描述
  2. 实体类增加逻辑删除字段配上注解
    加上注解:@TableLogic
@Data
/*全部参数的构造方法*/
@AllArgsConstructor
/*无参构造*/
@NoArgsConstructor
/*指定数据库表名*/
@TableName(value = "user")
public class User {
    
    

    @TableId(type = IdType.ID_WORKER)
    private Long id;

    private String name;

    private Integer age;

    private String email;

    /*逻辑删除*/
    @TableLogic
    private Integer delete;
}
  1. 在application.yaml文件中配置MyBatisPlus逻辑删除组件
mybatis-plus:
  #逻辑删除
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  1. 配置完成之后让我们来测试一个删除方法
    /*根据id删除数据*/
    @Test
    public void delete() {
    
    
        /*根据id删除*/
        userMapper.deleteById(3L);
    }

在这里插入图片描述
逻辑删除其实走的是update方法,把逻辑删除字段的值给修改了,我们设置的是未删除时字段默认为0,逻辑删除后字段为1
然后在查询时加上条件deleted = 0,所以在查询时deleted字段为1的值我们并没有查询出来,只查询了值为0的数据
在这里插入图片描述

6.单表更新操作

现在想把id为1的数据中name改为刘德华,看看应该如何操作?

    /*更新数据*/
    @Test
    public void update() {
    
    
        User user = new User();
        /*id*/
        user.setId(1L);
        /*修改名字*/
        user.setName("刘德华");
        /*根据id修改*/
       userMapper.updateById(user);
    }

查看日志执行的SQL:
在这里插入图片描述
查看数据库表:
在这里插入图片描述
上面只是修改了一个name字段,如果修改多个字段呢?

    /*更新数据*/
    @Test
    public void update() {
    
    
        User user = new User();
        /*id*/
        user.setId(1L);
        /*修改名字*/
        user.setName("刘德华");
        /*修改年龄*/
        user.setAge(20);
        /*修改邮箱*/
        user.setEmail("[email protected]");
        /*根据id修改*/
       userMapper.updateById(user);
    }

注意看执行的SQL语句,和上面修改一个字段相比较,相当于在修改数据时MyBatis-Plus会做一个实体类字段的的非空验证
实体类中不为空的字段就视为要修改的属性。
在这里插入图片描述

7.单表查询操作

    /*查询user表中所有数据*/
    @Test
    public void seleteByTest() {
    
    
        /*Wrapper是一个条件构造器,这里暂且先不使用*/
        List<User> userList = userMapper.selectList(null);
        /*打印List*/
        userList.forEach(System.out::println);
    }

    /*根据id查询一个*/
    @Test
    public void seleteByid() {
    
    
        /*查询id为5的数据*/
        User user = userMapper.selectById(5);
        /*输出*/
        System.out.println(user);
    }


    /*根据id批量查询*/
    @Test
    public void selectBatchIds() {
    
    
        List<Integer> list = Arrays.asList(3, 4, 5);
        /*查询多个id*/
        List<User> users = userMapper.selectBatchIds(list);
        /*输出所有*/
        users.forEach(System.out::println);
    }


    /*条件查询*/
    @Test
    public void selectByMap() {
    
    
        Map<String, Object> map = new HashMap();
        /*查询name为周杰伦的数据*/
        map.put("name", "周杰伦");
        List<User> users = userMapper.selectByMap(map);
        /*输出所有*/
        users.forEach(System.out::println);
    }

8.分页查询

一般分页的几种方式:

  1. 自己创建分页类,然后limit查询进行分页
  2. PageHelper插件分页

MyBatis-Plus这么强大当然也提供了分页插件,那我们就看看在MyBatis-Plus中如何使用分页?

首先在SpringBoot中创建一个配置类,然后添加方法注册一个Bean对象。这个配置类还会配置其他关于MyBatis-Plus的插件

/*配置类*/
@Component
public class myBatisPlusConfig {
    
    

    /*MyBatis-Plus分页插件*/
    @Bean
    public PaginationInterceptor paginationInterceptor() {
    
    
        return new PaginationInterceptor();
    }

}

在测试类中新建一个方法进行对分页的测试:
注:Page 对象中有很多方法,比如:数据总数,总页数等等。

    /*分页查询*/
    @Test
    public void testPage() {
    
    
        /*参数一:多少页(页码) 参数二:多少条数据(页面大小)*/
        Page page = new Page(1, 1);
        page = userMapper.selectPage(page, null);

        /*输出数据*/
        page.getRecords().forEach(System.out::println);
    }

9.自动填充功能

在开发中一张表起码有这样的两个字段吧,一个是数据的插入时间,还有一个就是数据的修改时间。
所谓自动填充就是数据在插入、修改的时候给具体的字段设置值。例如设置当前时间

其实这种实现有两种方式:
注:不管采用什么样的方式实现都必须有这两个字段

  1. 数据库级别(不推荐使用)

数据的创建时间字段默认一栏写入CURRENT_TIMESTAMP,该字段就会在新增数据时自动填入当前时间(创建数据时填入该字段)
在这里插入图片描述

只需要勾选根据当前时间戳更新,该数据在修改时,updateTime字段就会自动填入当前时间(修改数据时更新该字段)

在这里插入图片描述

  1. 代码级别

如果使用第一种方法,我们在开发中不一定能有权限去修改数据库,非专业数据库工程师去操作也不太好
MyBatis-Plus给我们提供了代码级别的填充。

  • 可以先看下这个枚举类:
public enum FieldFill {
    
    
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}
  • 在实体类种创建两个字段:
    /*创建时间*/
    @TableField(fill = FieldFill.INSERT)
    private Date cteateTime;

    /*修改时间*/
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
  • 创建一个类来继承 MetaObjectHandler,该类作为MyBatis-Plus的自动填充处理类:
@Slf4j
@Component
/*自动填充处理类*/
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    

    /*在插入数据时填充时间,两个字段都填充cteateTime和updateTime*/
    @Override
    public void insertFill(MetaObject metaObject) {
    
    
        log.info("start insert fill ....");
        this.setFieldValByName("cteateTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    /*在插入数据时填充时间,只填充updateTime*/
    @Override
    public void updateFill(MetaObject metaObject) {
    
    
        log.info("start update fill ....");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}
  • 至此在数据的插入时cteateTime和updateTime两个字段都会被填充,在数据修改时updateTime字段会被填充。
    在这里插入图片描述

10.乐观锁

乐观锁的实现方式:这里需要多增加一个字段,这里取名为version(版本)

  1. 先查询出当前数据的version字段
  2. 修改时带上这个version字段
  3. 执行修改的SQL语句时:set version = 新version where version = 旧version
  4. 如果第一次取出的version和修改时的version不同,那么就修改失败。修改成功那么就在旧version的值上+1

其实这里就是线程并发的问题:
那我们假设现在有AB两个线程,两个线程都要去执行修改同一条数据的操作,并且该数据的version字段为0,A线程执行时去查询值为0,B线程执行时查询值也为0。但是A线程先执行,那么version字段+1,那么现在version已经为1了,线程B去执行修改时发现 两次的version不一样,查询时为0,执行时为1,那么B线程就修改失败
在这里插入图片描述

看看MyBatis-Plus如何实现乐观锁:
数据库表和实体类都要新增一个version字段,并且在实体类字段上加上@Version注解,数据库字段给一个默认值

    @Version
    private Integer version;

在这里插入图片描述

在SpringBoot的代码中注入一个Bean对象

    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    
    
        return new OptimisticLockerInterceptor();
    }

11.wrapper 条件构造器

上面的SQL案例都是些比较简单的数据库操作,但是如何有很复杂的条件查询应该在MyBatis-Plus中如何使用?
这里就需要用到wrapper条件构造器,下面给大家举几个复杂条件的例子,wrapper 的功能远不止这些,参考官方文档进行编写

/*wrapper构造器*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
public class wrapperTest {
    
    

    /*注入持久层userMapper接口*/
    @Autowired
    private userMapper userMapper;


    /*查询name和email不为null并且age大于等于18的数据*/
    @Test
    public void test(){
    
    
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper
                .isNotNull("name")
                .isNotNull("email")
                .ge("age",18);
        userMapper.selectList(wrapper).forEach(System.out::println);
    }


    /*查询name等于周杰伦的数据*/
    @Test
    public void test2(){
    
    
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.eq("name","周杰伦");
        userMapper.selectList(wrapper).forEach(System.out::println);
    }


    /*查询age在20到30之间的数据*/
    @Test
    public void test3(){
    
    
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.between("age",20,30);//区间
        Integer integer = userMapper.selectCount(wrapper);//查询结果数
        System.out.println(integer);
    }


    /*模糊查询*/
    @Test
    public void test4(){
    
    
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.
                notLike("name","李")//not like
                .likeLeft("email","123");//%123

        List<Map<String, Object>> map = userMapper.selectMaps(wrapper);
        map.forEach(System.out::println);
    }


    /*子查询*/
    @Test
    public void test5(){
    
    
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.inSql("id","select id from user where id < 5");

        List<Object> list = userMapper.selectObjs(wrapper);
        list.forEach(System.out::println);
    }


    /*根据id倒叙*/
    @Test
    public void test6(){
    
    
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.orderByDesc("id");

        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
}

在不同的遭遇里我发现你的瞬间,有种不可言说的温柔直觉。

猜你喜欢

转载自blog.csdn.net/weixin_45377770/article/details/111682112