02-mybatis_plus

 


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 的一个亮点,但是由于,个人积累没有到这一步,所以,晚点再补充学习。

posted @   Lovi*  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示