02-mybatis_plus

发布时间 2023-09-20 14:49:59作者: Lovi*

Mybatis_plus 基础

参考资料

b 站视频:https://www.bilibili.com/video/BV17E411N7KN/?spm_id_from=333.999.0.0&vd_source=73cf57eb7e9ae1ddd81e6b44cf95dbeb

代码和笔记:https://gitee.com/kuangstudy/kuang_livenote/tree/master/【遇见狂神说】MyBatisPlus视频笔记

MyBatisPlus 概述

需要的基础:MyBatis、Spring、SpringMVC

为什么要学习它呢?

MyBatisPlus 可以节省我们大量工作时间,所有的 CRUD 代码它都可以自动化完成!
JPA 、 tk-mapper、MyBatisPlus

MyBatis 本来就是简化 JDBC 操作的!

关键点:学来偷懒

简介

官网:https://mp.baomidou.com/

快速入门

使用第三方组件:
1、导入对应的依赖
2、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力!

步骤

新建数据库和表

1670750586263

数据库名mybatis_plus

user 表

DROP TABLE IF EXISTS user;
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');

建好了

1670750714497

创建一个新的 springboot 文件

1670750915680

  • 引入 Spring Boot Starter 父工程:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0+ 版本</version>
        <relativePath/>
    </parent>
    

    引入 spring-boot-starterspring-boot-starter-testmybatis-plus-boot-starterh2 依赖:

    <!-- 数据库驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
        <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>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>最新版本</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    
    
  • 关于版本问题,mp 的版本导入最新版本

连接数据库

1670753212142

# mysql 5 驱动不同 com.mysql.jdbc.Driver
# mysql 8 驱动不同com.mysql.cj.jdbc.Driver、需要增加时区的配置
serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=shujuku
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

建实体类包 pojo

package com.example.mp01.pojo;

/**
 * 实体类
 */
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data //所有的属性的get set方法
@AllArgsConstructor //有参构造方法
@NoArgsConstructor  //无参构造方法
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

注解说明

1670753633470

  • mapper 接口
package com.example.mp01.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp01.pojo.User;
import org.springframework.stereotype.Repository;

/**
 * 当我继承了BaseMapper<User>之后 所有的CRUD操作都已经编写完成了
 */
@Repository //代表持久层
public interface UserMapper extends BaseMapper<User> {

}
  • 要在主启动类去添加 mapper 接口的扫描

  • @MapperScan("com.example.mp01.Mapper")
    
  • 1670753949435

测试类

package com.example.mp01;

import com.example.mp01.Mapper.UserMapper;
import com.example.mp01.pojo.User;
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
class Mp01ApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        // 参数是一个wrapper ,条件构造器,这里我们先不用nul1

        //查询全部用户
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }

}

bug 与解决

bug:java: 错误: 无效的源发行版:17

解决:

1670758562877

bug: java: 无效的目标发行版: 17

解决:

1670758711122

bug:

java: 无法访问 org.springframework.stereotype.Repository
错误的类文件: /D:/MAVENNNN/maven_repository/org/springframework/spring-context/6.0.2/spring-context-6.0.2.jar!/org/springframework/stereotype/Repository.class
类文件具有错误的版本 61.0, 应为 52.0
请删除该文件或确保该文件位于正确的类路径子目录中。

原因:版本问题

我这里的 spring-boot-starter-parent 版本是

3.0.0(推测是太高了)

改成了 2.7.5 就可以了

1670760038809

测试成功

1670757392542

配置日志

原因是原来的控制台只能看到结果,不能看到 sql 语句。

1670761564450

为了看到过程。在 application.properties 配置日志

# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

效果:

1670761600362

CRUD 扩展

插入操作

//插入
@Test
public void testInsert(){
    User user = new User();
    user.setName("Lovi learning MP");
    user.setAge(18);
    user.setEmail("123@qq.com");
    int result = userMapper.insert(user);//mapper插入的时候会帮我们自主生成id
    System.out.println("===================");
    System.out.println(result);//影响的行数
    System.out.println(user);//打印出来看看,能看到id值是因为实体类包含了toString方法
}

结果:1670762406033

主键生成策略

  • 主键自增

      1. navicat 设置自动递增
    • 1670762602739
      1. 在实体类 id 字段上加上@TableId(type = IdType.AUTO)
      1. 做测试

        1. /**
           * 插入操作
           */
          @Test
          public void testInsert(){
              User user = new User();
              user.setName("happy");
              user.setAge(99);
              user.setEmail("465464@qq.com");
              int result = userMapper.insert(user);//// mp会帮我们自动生成id(默认是雪花算法)
              System.out.println("受影响的行数===》"+result);//受影响的行数
              System.out.println(user);//发现,id会自动回填,这里会输出id是因为前面实体类有toString方法
          }
          
        2. 结果:

        3. 1670922334472

更新操作

/**
* 更新操作
*/
@Test
public void testUpdate(){
    User user = new User();
    //L指的是类型为Long
    user.setId(1601961740072718357L);
    user.setAge(16);
    user.setName("哈哈哈哈哈");
    int i = userMapper.updateById(user);
    System.out.println("受影响的行数===》"+i);
}

bug:乱码,不知道说什么。。。(用了 6 种方法都解决不了,凋谢)

bug:关于我执行插入方法的时候,甚至我不执行插入方法,数据库都会给我插入多一条数据,也就是再 build 的时候就已经给我更改掉数据库了,我解决了一天半都没解决,却因为取消委托给 Maven 的 build/run 的权利就解决了,真的太快乐了!

bug:我的 idea 一直很慢,在这次这个项目里,实在是太离谱了。然后也是意外取消了委托。然后就快起来了!

1670818692804

解决方法:

1670818788379

成功解决:

1670818861203

公共字段自动填充

  1. 数据库新增 create_time 和 update_time 字段

    1. 默认值为CURRENT_TIMESTAMP 当前时间戳
    2. 1670828098515
    3. 1670828132542
  2. 实体类设置

    1. @TableField(fill = FieldFill.INSERT)
      private Date createTime;
      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date updateTime;
      
  3. 新建一个我的元对象处理器类

    1. package com.example.mp01.handler;
      
      import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
      import org.apache.ibatis.reflection.MetaObject;
      import org.springframework.stereotype.Component;
      
      import java.util.Date;
      
      /**
       *  MP提供的自动填充方法
       *  需要实现MetaObjectHandler接口(元对象处理器)或者元数据处理器
       *  @Component:定义Spring管理Bean(也就是将标注@Component注解的类交由spring管理)
       */
      @Component
      public class MyMetaObjectHandler implements MetaObjectHandler{
          //需要重写插入方法和更新方法
          //插入时的操作
          @Override
          public void insertFill(MetaObject metaObject) {
              //default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
              //setFieldValByName 按照名称设置字段值
              this.setFieldValByName("creatTime", new Date(), metaObject);
              this.setFieldValByName("updateTime", new Date(), metaObject);
          }
          //更新时的操作
          @Override
          public void updateFill(MetaObject metaObject) {
              this.setFieldValByName("updateTime", new Date(), metaObject);
          }
      }
      
    2. 就成功啦(用测试类测试插入与更新)

      1. 1670828539607

乐观锁

  1. 数据库里新建 version 字段

    1. 1670829400594
  2. 实体类 version 字段

    1. @Version
      private Integer version;
      
  3. 测试

    1. //1.测试乐观锁更改版本
      @Test
      public void leGuan(){
          //更新值,那么版本就更新
          User user = new User();
          //1.查询用户信息
          user = userMapper.selectById(1L);
          System.out.println("打印查询到的user!"+user);
          //2.执行更新操作
          user.setName("I love u");
          user.setEmail("666.163.com");
          user.setAge(20);
          //修改数据库表
          int i = userMapper.updateById(user);
          System.out.println("受影响的行数===》"+i);
      }
      
    2. 运行结果

      1. 1670830308064
      2. 1670830328642
    3. //测试乐观锁,多线程失败的情况
      @Test
      public void leGuan2(){
          //更新值,那么版本就更新
          User user1 = new User();
          //1.查询用户信息
          user1 = userMapper.selectById(1L);
          //2.执行更新操作
          user1.setName("线程A");
          user1.setEmail("555.163.com");
          user1.setAge(20);
      
          //模拟有新的B线程抢先操作
          User user2 = new User();
          //1.查询用户信息
          user2 = userMapper.selectById(1L);
          //2.执行更新操作
          user2.setName("线程B");
          user2.setEmail("666.163.com");
          user2.setAge(20);
          int i1 = userMapper.updateById(user2);
          System.out.println("线程B受影响的行数===》"+i1);
      
          //线程A
          int i = userMapper.updateById(user1);
          System.out.println("线程A受影响的行数===》"+i);
      }
      
    4. 运行结果

      1. 1670830742817
      2. 1670830750898

bug:乐观锁版本我在数据库设置明明是 1,但是,每次插入数据的时候,代码层面总是自动帮我插入 0,导致插入的结果是 0

解决:只需要将实体类的 int 改成 integer 就可以了。因为 int 默认值为 0,不设置也会自动帮我插入 0.然后 integer 默认值为 null。不会帮我插进去默认值。然后新增数据的时候,就会走默认值通道。

查询操作

/**
     * 查询操作
     */
    //1.查询一条数据(按照id)
    @Test
    public void testSeleteById(){
        User user = new User();
        user=userMapper.selectById(2L);
        System.out.println(user);
    }
    //2.批量查询多条数据
    @Test
    public void testSelect(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
        users.forEach(System.out::println);
    }
    //3.多条件查询数据,符合条件就行 hash map
    @Test
    public void testSelectMultiCondition(){
        HashMap<String,Object> map1 = new HashMap<>();

        //设置查询条件
        map1.put("name", "小宝贝");
        map1.put("version", 1);

        List<User> users = userMapper.selectByMap(map1);
        users.forEach(System.out::println);

    }

结果:

1670833286058

1670833292021

1670833298023

分页查询

配置类配置分页拦截器

//分页拦截器
@Bean
public PaginationInterceptor paginationInterceptor(){
    return new PaginationInterceptor();
}

测试类

/**
 * 分页查询
 */
@Test
public void testPage(){
    //参数1:当前页
    //参数2:页面大小
    //public Page(long current, long size)
    Page<User> userPage = new Page<>(1,2);
    //查出来
    userMapper.selectPage(userPage, null);
    userPage.getRecords().forEach(System.out::println);
    System.out.println(userPage.getTotal());
}

运行结果

1670849324287

删除操作

/**
 * 删除操作
 */
//1.通过id删除
@Test
public void deleteById(){
    userMapper.deleteById(1601961740072718372L);
}
//2.通过id批量删除
@Test
public void deleteBatchId(){
    List<User> users = new ArrayList<>();
    userMapper.deleteBatchIds(Arrays.asList(1601961740072718376L,1601961740072718377L));
}
//3.通过map删除
@Test
public void deleteMap(){
    HashMap<String,Object> map = new HashMap<>();
    map.put("name", "小宝贝");
    map.put("version", 1);
    userMapper.deleteByMap(map);
}
  • 执行前

1670849404524

  • 执行后

1670849907294

逻辑删除

  1. 1670917971791

  2. 实体类配置

    1. //逻辑删除
      @TableLogic
      private Integer isDelete;
      
  3. 配置类

    1. //逻辑删除
      @Bean
      public ISqlInjector iSqlInjector(){
          return new LogicSqlInjector();
      }
      
  4. application.properties类加入配置

    1. #逻辑删除 已经删除为1,未删除未0
      mybatis-plus.global-config.db-config.logic-delete-value=1
      mybatis-plus.global-config.db-config.logic-not-delete-value=0
      
  5. 测试

    1. @Test
      public void deleteById(){
          userMapper.deleteById(1601961740072718373L);
      }
      
  6. 结果

    1. 1670851649958
    2. (实质执行的是更新操作,数据库还是在的)
    3. 1670851668356

性能分析插件

我们在平时的开发中,会遇到一些慢 sql。测试! druid,,,,,
作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间
MP 也提供性能分析插件,如果超过这个时间就停止运行!

  1. 设置开发环境

    1. #设置开发环境
      spring.profiles.active=dev
      
  2. 配置对应的插件(拦截器)

    1. //sql执行效率插件,性能分析插件
      @Bean
      @Profile({"dev","test"})//设置dev test环境开启,保证效率
      public PerformanceInterceptor performanceInterceptor(){
          //性能拦截器
          PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
          performanceInterceptor.setMaxTime(10);//ms设置sql执行的最大时间,如果超过了就不执行,而报错
          performanceInterceptor.setFormat(true);//是否格式化代码
          return performanceInterceptor;
      }
      
  3. 测试

    1. @Test
      void contextLoads() {
          // 参数是一个 Wrapper ,条件构造器(这里先不用,所以就用null)
          //查询全部用户
          List<User> users = userMapper.selectList(null);
          users.forEach(System.out::println);
      }
      
  4. 结果

    1. 1670852499491
    2. 当我改成 100ms 之后问题就解决了

条件构造器

官网介绍和例子:https://baomidou.com/pages/10c804/#abstractwrapper

条件构造器可以自己弄很多复杂的 sql 语句

测试 1--gt ge lt le

/**
     * gt大于>
     * ge大于等于>=
     * lt小于<
     * le小于等于<=
     */
    @Test
    public void test1(){
        // 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .isNotNull("name")
                .isNotNull("email")
                .ge("age", 12); //大于等于

       userMapper.selectList(wrapper).forEach(System.out::println);

    }

1670898660930

测试 2--eq ne

/**
     * eq等于
     * ne不等于
     */
    @Test
    public void test2(){
        //查询名字为EM的用户,并且age不等于68
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .eq("name", "EM")
                .ne("age", 68);
        User user = userMapper.selectOne(wrapper);
        System.out.println(user);
    }

1670910254769

测试 3--BETWEEN NOT BETWEEN

/**
     * BETWEEN 值1 AND 值2===>between("age", 18, 30)--->age between 18 and 30
     * NOT BETWEEN 值1 AND 值2===>notBetween("age", 18, 30)--->age not between 18 and 30
     */
    @Test
    public void test3(){
        //查询年龄在10-20岁的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .between("age",10,20);
        userMapper.selectList(wrapper).forEach(System.out::println);
    }

1670898924450

测试 4--模糊查询

/**
     * 模糊查询
     * 假设查a
     * like 就是%a%
     * not like 就是不能含有a
     * likeRight 右边模糊查询 a%
     * likeLeft  左边模糊查询 %a
     */
    @Test
    public void test4(){
        //模糊查询名字中带有o的用户并且以J开头的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .like("name","o")
                .likeRight("name","J");
//        userMapper.selectList(wrapper).forEach(System.out::println);

        //也可以用map的方式输出
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);
    }

1670899660280

测试 5--集合查询与 sql 子查询

/**
     * 集合查询与子查询
         * 集合查询
             * 字段 IN (value.get(0), value.get(1), ...)
             * 例: in("age",{1,2,3})--->age in (1,2,3)
             * in("age", 1, 2, 3)--->age in (1,2,3)
             * 字段 NOT IN (value.get(0), value.get(1), ...)
             * 例: notIn("age",{1,2,3})--->age not in (1,2,3)
             * notIn("age", 1, 2, 3)--->age not in (1,2,3)
         * sql语句子查询
             * 字段 IN ( sql语句 )
             * 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
             * 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
             * 字段 NOT IN ( sql语句 )
             * 例: notInSql("age", "1,2,3,4,5,6")--->age not in (1,2,3,4,5,6)
             * 例: notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3)
     */
    @Test
    public void test5(){
        //子查询,id在子查询中查出来
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.in("id",1,2,3);
        wrapper.notIn("id",1,2,3);
        wrapper.inSql("id","select id from user where id < 5");
        wrapper.notInSql("id","select id from user where id < 5");
        //子查询
        //输出
//        userMapper.selectList(wrapper).forEach(System.out::println);
        //也可以用map的方式输出
//        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
//        maps.forEach(System.out::println);
        //也可以用Object的方式输出
        List<Object> objects = userMapper.selectObjs(wrapper);
        objects.forEach(System.out::println);
    }

核心:

wrapper.in("id",1,2,3);
wrapper.notIn("id",1,2,3);
wrapper.inSql("id","select id from user where id < 5");
wrapper.notInSql("id","select id from user where id < 5");

集合的结果

1670911402594

子查询的结果

1670900903188

测试 6 --分组与排序

/**
     * 分组与排序
     * 1.分组:GROUP BY 字段, ...
     * 例: groupBy("id", "name")--->group by id,name
     * 2.排序:ORDER BY 字段, ... ASC
     * 例: orderByAsc("id", "name")--->order by id ASC,name ASC
     * 3.排序:ORDER BY 字段, ... DESC
     * 例: orderByDesc("id", "name")--->order by id DESC,name DESC
     * 4.排序:ORDER BY 字段, ...
     * 例: orderBy(true, true, "id", "name")--->order by id ASC,name ASC
     * 5.HAVING ( sql语句 )
     * 例: having("sum(age) > 10")--->having sum(age) > 10
     * 例: having("sum(age) > {0}", 11)--->having sum(age) > 11
     *
     */
    @Test
    public void test6(){
        QueryWrapper wrapper = new QueryWrapper();

        wrapper.select("name","sum(age)");
        wrapper.groupBy("name");//通过名字进行分组
        wrapper.orderByAsc("name");//排序咯(分组后排序)
        wrapper.having("sum(age)>100");//把分组里年龄总和大于100的组筛选出来
        userMapper.selectList(wrapper).forEach(System.out::println);
    }

1670917551566

1670917578614

bug:在执行

SELECT  id,name,age,email,version,create_time,update_time,is_delete  FROM user  WHERE  is_delete=0 GROUP BY name,version

这个 sql 语句时报错

1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'mybatis_plus.user.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

意思就是:在使用 group by 的时候,select 查询的字段一定都要在 group by 后面的列当中。不然就会报错

错误原因:数据库的版本导致,sql5.7 以上的版本都有这个问题

当我改成:

SELECT  name,version FROM user  WHERE  is_delete=0     GROUP BY name,version

果然就不报错了

1670913493613

错误解决:

先查看当前数据库配置

SELECT @@sql_mode;

我得到的结果为:

ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

得把 ONLY_FULL_GROUP_BY 去掉,重新设置值

也就是

SET sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

这个时候再查就可以了

1670915129318

如果要设置全局的就得

SELECT @@GLOBAL.sql_mode;

一样把查出来的结果去掉 ONLY_FULL_GROUP_BY 重新设置

set @@global.sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';


扩展一个 sql 语句

SELECT name , sum(age) t from user group by name having t>200

意思是,查找名字对应的年龄总和为大于 200 的组

测试 7--allEq

/**
     * allEq
     * 全部eq(或个别isNull)
     */
    @Test
    public void test7(){
        //allEq
        QueryWrapper wrapper = new QueryWrapper();
        //需要用到map
        Map<String,Object> map = new HashMap<>();
        map.put("name", "oh my god");
        map.put("email",null);
        wrapper.allEq(map);//查找的是name=oh my god 并且email为空的数据
//        wrapper.allEq(map,false);//查找的是name=oh my god 的数据,不管email空不空都找出来
        userMapper.selectList(wrapper).forEach(System.out::println);
    }

1670909867937

代码生成器

这个也是 mybatisplus 的一个亮点,但是由于,个人积累没有到这一步,所以,晚点再补充学习。