Mybatis-Plus笔记

仅为学习记录,方便回顾复习,如有侵权请联系删除!

Mybatis-Plus

官方网站:https://baomidou.com/

总结:mapper接口继承BaseMapper<T>,主启动类添加@MapperScan注解扫描mapper包,结束。

还有乐观锁、自动填充gmt、逻辑删除、wrapper条件构造、代码生成器

目录

文章目录

介绍

简介

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑;
  • 损耗小:启动即会自动注入基本 CRUD,性能基本无损耗,直接面向对象操作,BaseMapper<T>;
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求,简单的CRUD操作不用自己编写;
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错;
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题;
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作;
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere );
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用(自动生成代码);
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询;
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库;
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询;
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作;

一、快速开始

https://baomidou.com/pages/226c21/#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B

基本思路

1、导入依赖;

2、配置;

3、编写代码;

4、提高扩展技术;

实现步骤

1、创建数据库mybatis_plus

image-20220726155232982

2、创建user表插入数据

USE `mybatis_plus`;
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)
);

#真实开发中,version(乐观锁)、deleted(逻辑删除)、gmt_create(创建时间)、gmt_modified(修改时间)

INSERT INTO USER (id,`name`,age,email) VALUES
(1,'Tom',18,'tom@qq.com'),
(2,'Jerry',20,'jerry@qq.com'),
(3,'Jack',21,'jack@qq.com'),
(4,'Rose',30,'rose@qq.com'),
(5,'Smith',24,'smith@qq.com');
image-20220726153725767

3、初始化项目

使用Spring Initializr新建springboot项目,选择web组件。

4、引入对应依赖

<!--数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3.1</version>
</dependency>

注意:使用mybatis-plus可以节省大量代码,不要同时导入mybatis和mybatis-plus,可能存在版本冲突。

5、yml配置数据库连接

注意,mysql 8 的驱动不同,而且需要加上时区的配置

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #url: localhost:3306/数据库名称?运用统一编码=是&是否启用安全认证=否&字符集编码格式=utf8&时区设置=Asia/shanghai
    url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 9527

useSSL=false 和 true 的区别:
SSL(Secure Sockets Layer 安全套接字协议),在mysql进行连接的时候,如果mysql的版本是5.7之后的版本必须要加上useSSL=false,mysql5.7以及之前的版本则不用进行添加useSSL=false,会默认为false,一般情况下都是使用useSSL=false,尤其是在将项目部署到linux上时,一定要使用useSSL=false!!!,useSSL=true是进行安全验证,一般通过证书或者令牌什么的,useSSL=false就是通过账号密码进行连接,通常使用useSSL=false!!!

mysql数据库用的是gbk编码,而项目数据库用的是utf-8编码。这时候如果添加了useUnicode=true&characterEncoding=UTF-8

存数据时:
数据库在存放项目数据的时候会先用UTF-8格式将数据解码成字节码,然后再将解码后的字节码重新使用GBK编码存放到数据库中。

取数据时:
在从数据库中取数据的时候,数据库会先将数据库中的数据按GBK格式解码成字节码,然后再将解码后的字节码重新按UTF-8格式编码数据,最后再将数据返回给客户端。

6、使用mybatis-plus

6.1 创建pojo类

创建包com/chw/mybatisplus/pojo

创建实体类:com/chw/mybatisplus/pojo/User.java

package com.chw.mybatisplus.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //常见的数据库中主键自动设置方法有:uuid , 自增id , 雪花算法 , redis生成 , zookeeper生成
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
创建mapper接口集成BaseMapper

创建包:com/chw/mybatisplus/mapper

创建接口:com/chw/mybatisplus/mapper/UserMapper.java

package com.chw.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chw.mybatisplus.pojo.User;
import org.springframework.stereotype.Repository;

// BaseMapper<T> 泛型T是对应的pojo类。
@Repository //注册到spring中
public interface UserMapper extends BaseMapper<User> {
    //这样所有的crud都编写完了
}
6.3 主启动类添加@MapperScan注解

添加扫描mapper的注解,指定要扫描的mapper包(是java里的包,而不是resources里的mapper映射文件)

创建主启动类

com/chw/mybatisplus/SpringbootMybatisplusApplication.java

package com.chw.mybatisplus;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.chw.mybatisplus.mapper")
public class SpringbootMybatisplusApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootMybatisplusApplication.class, args);
    }
}
6.4 单元测试

test文件夹下

com.chw.mybatisplus.SpringbootMybatisplusApplicationTests

package com.chw.mybatisplus;
import com.chw.mybatisplus.mapper.UserMapper;
import com.chw.mybatisplus.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 SpringbootMybatisplusApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        System.out.println("---- selectAll method test 测试查询所有用户的方法");
        //selectList 的参数 wrapper 是条件构造器,可以先写null
        List<User> userList = userMapper.selectList(null);
        //增强for循环遍历
        for (User user : userList) {
            System.out.println("user = " + user);
        }
    }
}

二、yml配置mybatisplus日志

application.yml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

再次执行上面的查询方法查看分析日志
1fb27041ea9531728487bbd41ffa7c7e

三、insert测试

1、测试插入方法及分析日志

test文件夹下

com.chw.mybatisplus.SpringbootMybatisplusApplicationTests

@Autowired
private UserMapper userMapper;

@Test
public void testInsert() {
    User user = new User();
    user.setName("zelda");
    user.setAge(16);
    user.setEmail("zelda@hyrule.com");
    //没有设置ID,但mybatisplus会自动生成ID
    int result = userMapper.insert(user);
    System.out.println("result = " + result);
    System.out.println("user = " + user);
}

控制台输出日志

image-20220727061417926

并没有给user设置id,数据库自动插入了id=1552054238408888322。

注意:数据库默认插入的id是全局唯一的id,下面介绍主键生成策略

2、主键生成策略

2.1 雪花算法

mybaits-plus生成id默认采用雪花算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000),几乎保证全球唯一。

雪花算法在工程实现上有单机版本和分布式版本。单机版本如下,分布式版本可以参看美团leaf算法:https://github.com/Meituan-Dianping/Leaf

可以在User类的id属性上加入注解TableId更改和查看策略

//type = IdType.ASSIGN_ID   /*ASSIGN_ID表示雪花算法*/
public class User {
    //常见的数据库中主键自动设置方法有:uuid , 自增id , 雪花算法 , redis生成 , zookeeper生成
    @TableId(type = IdType.ASSIGN_ID, value = "id") //枚举注解,使用ID_WORKER策略,全局唯一ID,不管数据库是否设置自增。
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

2.2 主键自增策略

通过@TableId注解设置自增策略

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

同时数据库设计时,一定要将id设计为自增,这样自增id会设置在最大值上加1。

2.3 其他策略

ctrl+左键点击 IdType进去查看都有哪些枚举

public enum IdType {
    AUTO(0),	//数据库ID自增,需要确保数据库也设置了ID自增
    NONE(1),	//未设置主键类型
    INPUT(2),	//用户输入ID,也可以自定义自动填充插件来填充
    ASSIGN_ID(3),	//雪花算法。要求插入的id为空值才能自动填充,要求主键类型为number或String
    ASSIGN_UUID(4);	//uuid。要求插入的id为空值才能自动填充,要求主键类型为String

    private final int key;

    private IdType(int key) {
        this.key = key;
    }

    public int getKey() {
        return this.key;
    }
}

四、update测试

1、测试update及日志分析

test文件夹下com.chw.mybatisplus.SpringbootMybatisplusApplicationTests

@Autowired
private UserMapper userMapper;

//更新测试
@Test
public void testUpdateById() {
    User user = new User();
    user.setId(5L);
    user.setName("Link");
    user.setAge(18);
    int i = userMapper.updateById(user);
    System.out.println("i = " + i);
}

控制台输出

image-20220727064134625

2、自动填充

创建时间、更新时间等操作可以自动完成,不需手动。

强制要求:gmt_create、gmt_modified字段必须在所有表都要配置,会自动化填充。

gmt表示Greenwich Mean Time,世界时间、格林尼治时间。

2.1 方式一:数据库级别(不建议)

在数据库种添加字段gmt_create、gmt_modified,然后在pojo类中添加这两个属性。

image-20220727064946152

com/chw/mybatisplus/pojo/User.java

import java.util.Date;
public class User {
    ...
    private Date gmtCreate;
    private Date gmtModified;
}

2.2 方式二:代码级别

1、先在数据库中删掉刚刚新建的gmt_create和gmt_modified字段,在pojo中删掉刚刚添加的属性。

数据库中创建两个字段:create_time、update_time。类型都是datetime

2、在实体类的成员变量上添加注解@TableField

com/chw/mybatisplus/pojo/User.java

import java.time.LocalDateTime;
public class User {
    ...
    //@TableField提供自动填充功能
    @TableField(fill = FieldFill.INSERT,value = "create_time")  //FieldFill.INSERT 表示插入时填充create_time字段
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.UPDATE, value = "update_time") //FieldFill.UPDATE 表示更新时填充update_time字段
    private LocalDateTime updateTime;
}

3、编写处理器实现类来处理这个注解

com/chw/mybatisplus/compo/MyMetaObjectHandler.java

package com.chw.mybatisplus.compo;
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;

@Slf4j  //日志
@Component  //注册组件
public class MyMetaObjectHandler implements MetaObjectHandler {

    //插入时启动
    //注意第3个参数的类型必须和createTime的类型保持一致,否则生成null插入,如果是Date,就都设置为Date。
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); //起始版本 3.3.0(推荐)
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); //起始版本 3.3.0(推荐)
    }

    //更新时启动
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); //起始版本 3.3.0(推荐)
    }
}

4、测试执行插入、更新操作,观察效果。

五、乐观锁

1、乐观锁原理

  • 乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不会上锁!如果出现了问题,就再次更新值加锁处理

  • 悲观锁:顾名思义十分悲观,他总是认为无论干什么都会出现问题,所以都会上锁,再操作!

乐观锁(OptimisticLockerInnerInterceptor)机制:

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

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

相当于给每一个记录都加一个version字段。当我们要改记录时,把version字段拿出来看一看,对比一下这个version 有没有在你操作数据时被其他线程更改,如果依然等于oldVersion,你就对数据进行操作同时把 version = newVersion 更新(比如+1),以此你在改数据的途中告诉其他线程不要读了脏数据。

2、乐观锁的应用

2.1 测试update

(1) 数据库添加version字段,int型,默认0,长度10,不自增。
(2) pojo类中添加属性,

com/chw/mybatisplus/pojo/User.java

...
public class User {
    ...
    @Version    //开启乐观锁支持,一个类中只能有一个@Version注解
    private int version;
}

@Version说明:

  • 仅支持的数据类型:int , Integer , long , Long , Timestamp , LocalDateTime;
  • 整数类型下 newVersion = oldVersion + 1 ;
  • newVersion 会回写到entity中;
  • 仅支持 updateById(id) 与 update(entity , wrapper) 方法;
  • 在 update(entity , wrapper) 方法下,wrapper 不能复用;
(3) 在config下注册组件,开启乐观锁拦截器

com/chw/mybatisplus/config/MybatisPlusConfig.java

package com.chw.mybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement    //开启事务
@Configuration  //配置类注解
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //乐观锁拦截器插件
        return mybatisPlusInterceptor;
    }
}
(4) 测试乐观锁

test文件夹下com.chw.mybatisplus.SpringbootMybatisplusApplicationTests

@Autowired
private UserMapper userMapper;

//测试乐观锁
@Test
public void testOptimisticLocker() {
    //1. 查询用户信息
    User user = userMapper.selectById(1L);
    //2. 修改用户信息
    user.setEmail("123@qq.com");
    user.setName("小可爱");
    //3. 更新操作
    userMapper.updateById(user);
}

控制台输出结果

image-20220727082015005

(5) 模拟多线程下乐观锁失败案例

test文件夹下com.chw.mybatisplus.SpringbootMybatisplusApplicationTests

@Autowired
private UserMapper userMapper;

//模拟多线程下乐观锁失败
@Test
public void testOptimisticLockerFail() {
    //模拟多线程
    User user1 = userMapper.selectById(3L);
    user1.setEmail("333@qq.com");
    user1.setName("波克布林");   //对线程1修改值
    //user1里已经有了oldVersion值

    //线程2插队
    User user2 = userMapper.selectById(3L);
    user2.setEmail("thread2@qq.com");
    user2.setName("莫力布林");
    userMapper.updateById(user2);//线程2抢先提交
    //user2更新了Version值

    userMapper.updateById(user1);//线程1失败,乐观锁防止了脏数据存在,如果没有乐观锁,线程2的修改就会被覆盖
}

控制台输出

image-20220727083043467

六、select测试

1、根据一个id查询一条数据

@Autowired
private UserMapper userMapper;

@Test
public void testSelectBatchId() {
    User user = userMapper.selectById(1L);
    System.out.println("user = " + user);
}
//控制台输出
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id=?
==> Parameters: 1(Long)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 1, 小可爱, 18, 123@qq.com, null, 2022-07-27 08:18:49, 1
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@358ab600]
user = User(id=1, name=小可爱, age=18, email=123@qq.com, createTime=null, updateTime=2022-07-27T08:18:49, version=1)

2、根据多个id查询多条数据

@Autowired
private UserMapper userMapper;

@Test
public void testSelectBatchIds() {
    //Arrays.asList()创建了一个固定大小的集合
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));//selectBatchIds(Collection<? extends Serializable> idList);
    users.forEach(System.out::println);
}
//控制台输出
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? , ? )
==> Parameters: 1(Integer), 2(Integer), 3(Integer)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 1, 小可爱, 18, 123@qq.com, null, 2022-07-27 08:18:49, 1
<==        Row: 2, Jerry, 20, jerry@qq.com, null, null, 0
<==        Row: 3, 莫力布林, 21, thread2@qq.com, null, 2022-07-27 08:26:32, 1
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44065156]
User(id=1, name=小可爱, age=18, email=123@qq.com, createTime=null, updateTime=2022-07-27T08:18:49, version=1)
User(id=2, name=Jerry, age=20, email=jerry@qq.com, createTime=null, updateTime=null, version=0)
User(id=3, name=莫力布林, age=21, email=thread2@qq.com, createTime=null, updateTime=2022-07-27T08:26:32, version=1)

两个方法的源码

//selectBatchIds(..)方法源码
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
//Arrays.asList(..)方法源码
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }

日志分析

image-20220727084546278

sql语句用了 in ( , , )

3、条件查询 and

@Autowired
private UserMapper userMapper;

@Test
public void testSelectByMap() {
    HashMap<String, Object> map = new HashMap<>();
    //定义查询条件
    map.put("name", "zelda");
    map.put("age", 3);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

image-20220727085321266

4、分页查询

4.1 配置拦截器

com/chw/mybatisplus/config/MybatisPlusConfig.java

package com.chw.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement    //开启事务
@Configuration  //配置类注解
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //乐观锁拦截器插件
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        //分页插件拦截器,DbType.MYSQL表示MySQL数据库系统
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }
}

4.2 测试使用page对象

@Autowired
private UserMapper userMapper;

//测试分页查询
@Test
public void testPage() {
    //添加分页插件后,会注册一个page对象,构造器参数为 (当前页,每页条数)
    Page<User> page = new Page<>(1, 5);

    userMapper.selectPage(page, null);//分页查询
    List<User> records = page.getRecords(); //获取分页后的数据
    records.forEach(System.out::println);   //打印
    System.out.println(page.getTotal());//获取记录总数
}

image-20220727094814294

测试 Page<User> page = new Page<>(2 , 5);

image-20220727095008714

LIMIT ? , ? 即 LIMIT 初始位置 , 记录数。

  • 初始位置:从哪条记录开始显示。第一条记录的位置是 0,第二条记录的位置是 1;
  • 记录数:表示显示记录的条数;

LIMIT 5 , 5 则表示从第6条数据开始显示,显示5条数据。

七、delete测试

1、常见删除

1.1 根据id删除

@Test
public void testDeleteById() {
    userMapper.deleteById(1552054238408888322L);
    // userMapper.delete(null); //表示删除全部
}

image-20220727100100212

2、逻辑删除

物理删除:在数据库中移除;

逻辑删除:在数据库中并没有移除,而是在通过改变指定字段的值来标记它失效;

逻辑删除可以用于防止数据丢失,管理员可以查看被删除的记录,用户没法查看。

2.1 在数据库中添加deleted字段

字段名称deleted,数据类型int,默认值是0,长度为1,注释为"逻辑删除"

2.2 在实体类中同步该属性,添加逻辑删除注解 @TableLogic

com/chw/mybatisplus/pojo/User.java

...
public class User {
	...
    @TableLogic //逻辑删除
    private Integer deleted;
}

2.3 yml编写逻辑删除配置

application.yml

mybatis-plus:
  global-config:
    db-config:
      #logic-delete-field: flag  #全局逻辑删除的实体字段名,配置之后可以不加2.2的注解@TableLogic
      logic-delete-value: 1 #逻辑已删除的值(默认1表示逻辑已删除)
      logic-not-delete-value: 0 #逻辑未删除的值(默认0表示逻辑未删除)

2.4 测试逻辑删除

执行删除操作,但实际上是执行更新操作,把deleted字段改为1了

@Autowired
private UserMapper userMapper;

@Test
public void testDeleteById() {
    userMapper.deleteById(1L);
    // userMapper.delete(null); //表示删除全部
}

控制台输出

image-20220727101628574

数据库效果

image-20220727101646649

如果去执行根据id查询语句查询这个id的值时,后台会自动拼接deleted=0的判断,是的查不到逻辑删除的结果

@Test
public void testSelectBatchId() {
    User user = userMapper.selectById(1L);
    System.out.println("user = " + user);
}

image-20220727101929168

八、性能分析插件

官方使用的p6spy性能有损耗,推荐使用其他第三方性能分析插件,比如阿里巴巴的Druid。

九、条件构造器Wrapper(重点)

条件构造器官方文档:https://baomidou.com/pages/10c804/

我们之前一直是增删改查,没有一个是带条件的,而这个条件构造器就相当于sql中加了一个where。

  • QueryWrapper 用于生成 select 的 where 条件;

  • UpdateWrapper 用于生成 update 的 where 条件;

查询多条记录与单条记录

@Autowired
private UserMapper userMapper;

@Test
void contextLoads() {
    // 查询多条记录
    // 一个复杂的查询比如:查询name不为空、邮箱为rose@qq.com、年龄大于等于20的用户
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //链式编程 添加查询条件
    wrapper.isNotNull("name")
            .eq("email", "rose@qq.com")
            .ge("age", 20);
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);

    // 查询单条记录
    QueryWrapper<User> wrapper2 = new QueryWrapper<>();
    wrapper2.isNotNull("name")
            .eq("email", "rose@qq.com")
            .eq("age", 28);
    User user = userMapper.selectOne(wrapper2);  //只查询一个,出现多个结果会报错
    //出现多个结果时报错内容:
    //TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
    System.out.println("user = " + user);
}

控制台输出

日志分析,就是where中多添加了一个条件

image-20220727104721879

知识点回忆:Lombok的@Accessors注解——开启链式编程

部分方法说明

  • eq:等于,equals

  • ne:不等于,not equals

  • gt:大于,greater than

  • ge:大于等于,greater or equals

  • lt:小于,less than

  • le:小于等于,less or equals

  • or:拼接 OR

    • 注意事项:

      主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

    • 例: eq("id",1).or().eq("name","老王")—>id = 1 or name = '老王'

区域查询与计数 between() , selectCount()

between

between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
  • BETWEEN 值1 AND 值2。包括边界,是个闭区间 [ 值1 , 值2 ]
  • 例: between("age", 18, 30)—>age between 18 and 30
@Autowired
private UserMapper userMapper;

@Test
void test2() {
    //查询区间内的记录
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.between("age", 20, 30);
    Integer count = userMapper.selectCount(wrapper);
    System.out.println("count = " + count);
}

日志

image-20220727110448941

模糊查询 like

@Autowired
private UserMapper userMapper;

@Test
public void test3() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.like("name",99)         //名字中 存在 99
            .notLike("name",6)      //名字中 不存在 6
            .likeRight("email",2)   //邮箱 最左边是 2  ---- 2%
            .likeLeft("email","m"); //邮箱 最右边是 m  ---- %m
    //List<Map<String, Object>> list = userMapper.selectMaps(wrapper);
    //list.forEach(System.out::println);
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

日志

image-20220727112057711

注:

like("name", "王")`--->`name like '%王%'
notLike("name", "王")--->name not like '%王%'
likeLeft("name", "王")--->name like '%王'
likeRight("name", "王")--->name like '王%'

子查询(多表查询)

@Autowired
private UserMapper userMapper;

@Test
public void test4() {
    //子查询
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.inSql("id", "select id from 另一个表格的名字 where id < 9");
    List<Object> list = userMapper.selectObjs(wrapper);
    list.forEach(System.out::println);
}

日志

JDBC Connection [HikariProxyConnection@982565180 wrapping com.mysql.cj.jdbc.ConnectionImpl@39c96e48] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND (id IN (select id from 另一个表格的名字 where id < 9))
==> Parameters: 

inSql

inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
  • 字段 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)

排序 orderByAsc/orderByDesc

@Autowired
private UserMapper userMapper;

@Test
public void test5() {
    //排序
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.orderByAsc("id");   //根据id升序排列,如果要降序则 orderByDesc()
    userMapper.selectList(wrapper).forEach(System.out::println);
}

日志

image-20220727114205475

分组,条件

@Autowired
private UserMapper userMapper;

@Test
public void test6() {
    //分组排序
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.groupBy("version").having("version = 1");
    userMapper.selectList(wrapper).forEach(System.out::println);
}

日志

image-20220727115007469

根据version进行分组,且只查询version = 1的记录

sql回顾:

GROUP BY语句
该GROUP BY语句将具有相同值的行分组为汇总行,例如“查找每个国家/地区的客户数量”。
该GROUP BY语句通常与聚合函数 ( COUNT(), MAX(), MIN(), SUM(), AVG()) 一起使用, 以按一列或多列对结果集进行分组。

SELECT COUNT(CustomerID), Country
FROM Customers
GROUP BY Country;

HAVING 子句

语法:

SELECT column_name(s)
FROM table_name
WHERE condition
GROUP BY column_name(s)
HAVING condition
ORDER BY column_name(s);
SELECT COUNT(CustomerID), Country
FROM Customers
GROUP BY Country
HAVING COUNT(CustomerID) > 5;
#列出了每个国家/地区的客户数量。仅包括拥有超过 5 个客户的国家/地区:

更多条件构造方法

https://baomidou.com/pages/10c804/

image-20220727144035460

十、代码自动生成器(重点)

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

1、引入依赖

pom.xml

<!--mp 代码生成器 依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<!--导入swagger-->
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.5.20</version>
</dependency>
<!--添加模板引擎thymeleaf,还有freemarker都是模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<!--数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3.1</version>
</dependency>

2、在任意文件夹新建一个类进行配置

package com.chw;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        //构建一个代码自动生成器对象
        AutoGenerator mpg = new AutoGenerator();

        // 1、创建全局配置类的对象
        GlobalConfig gc = new GlobalConfig();
        //获取当前项目路径
        String projectPath = System.getProperty("user.dir");
        System.out.println("projectPath = " + projectPath);
        //自动生成代码存放的路径
        gc.setOutputDir(projectPath + "/src/main/java");
        //设置 --作者注释
        gc.setAuthor("chw");
        //是否打开文件夹
        gc.setOpen(false);
        //是否覆盖已有文件
        gc.setFileOverride(false);
        //各层文件名称方式,例如: %sAction 生成 UserAction  %s占位符
        gc.setServiceName("%sService");
        //设置日期策略  date类型
        gc.setDateType(DateType.ONLY_DATE);
        //设置主键策略 雪花算法
        gc.setIdType(IdType.ASSIGN_ID);
        //设置开启 swagger2 模式
        gc.setSwagger2(true);
        //把全局配置放入代码生成器
        mpg.setGlobalConfig(gc);

        // 2、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc); //把数据源配置加入到代码生成器

        // 3、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.chw");
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setController("controller");
        // ...  有默认值,点击查看源码
        mpg.setPackageInfo(pc);//包加入代码生成器

        // 4、策略配置
        StrategyConfig strategy = new StrategyConfig();
        //下划线转驼峰命名  表
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 下划线转驼峰命名字段
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //实体类是否加上lombok注解
        strategy.setEntityLombokModel(true);
        //控制层采用RestControllerStyle注解
        strategy.setRestControllerStyle(true);
        // RequestMapping中 驼峰转连字符 -
        strategy.setControllerMappingHyphenStyle(true);
        //要映射的数据库表名  (重点)
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        //添加表名前缀
        //strategy.setTablePrefix("m_"); //自动拼接上m_
        //逻辑删除字段名
        strategy.setLogicDeleteFieldName("deleted");
        //乐观锁字段名
        strategy.setVersionFieldName("version");
        // -------自动填充策略
        ArrayList<TableFill> fillList = new ArrayList<>();
        fillList.add(new TableFill("createTime", FieldFill.INSERT));
        fillList.add(new TableFill("updateTime",FieldFill.INSERT_UPDATE));
        // 参数是 List<TableFill> 的链表
        strategy.setTableFillList(fillList);
        mpg.setStrategy(strategy);

        //---------------------------------
        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override 
            //输出了 静态资源下的 Mapper
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        //        FreemarkerTemplateEngine模板引擎
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值