Mybatis——Mybatis-Plus 看这一篇就够了

博客资料参考官方文档

https://mp.baomidou.com/guide/

数据库创建和数据添加

创建数据库

新建数据库,并给数据库命令、选择字符集等。
在这里插入图片描述

创建数据表

数据表采取官方给定的为准:

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) VALUE
(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');

相比起官方给定的sql,将values更换为value

多条记录使用value,而不是values!

创建SpringBoot项目

主要依赖引入

<dependency>
   <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

【注意:】不要将 mybatis 和 mybatis-plus 两个依赖同时导入!

版本和兼容性问题。
mybatis-plus 不是官方的,是大佬自己写的。

springboot使用的版本如下所示:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>

配置文件编写

主要是写服务的端口、数据库连接等。

这里需要注意几点:

  • MySQL 8以前:
    驱动使用:com.mysql.jdbc.Driver

  • MySQL 8以后:
    驱动使用:com.mysql.cj.jdbc.Driver
    需要增加时区

【标注:】驱动具有向下兼容的特性,如果使用高版本的驱动,一定注意时区的限制

本次使用高版本

所以Springboot项目的配置文件编写如下所示:

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://192.168.99.100:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

server.port=80

编写POJO对象类

编写一个pojo的对象类,实现数据的接收映射。如下所示:

package cn.linkpower.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private long id;
    private String name;
    private Integer age;
    private String email;
}

编写mapper类

使用mybatis-plus插件,针对简单的CRUD操作,不需要额外的编写对他应的sql,或者说编写xml文件,只需要将对应的mapper接口继承一个BaseMapper即可。如下所示:

package cn.linkpower.mapper;

import cn.linkpower.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

@Repository // 表示这个接口是持久层;也可以换成一个  @Mapper 注解
public interface UserMapper extends BaseMapper<User> {
}

修改启动类,增加mapper扫描

由于没有配置mapper的自动扫描路径,可以直接在启动类上增加一个@MapperScan实现自动扫描装载操作。如下所示:

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("cn.linkpower.mapper")
public class SpringbootMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootMybatisApplication.class, args);
    }

}

编写测试类

/test/目录下,编写对应的测试类即可,如下所示:

查询所有的用户信息。
在这里插入图片描述

package cn.linkpower;

import cn.linkpower.mapper.UserMapper;
import cn.linkpower.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootMybatisApplicationTest  {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void getUserInfo(){
        List<User> users = userMapper.selectList(null);
        users.stream().forEach(System.out::println);
    }
}

执行后发现控制台打印数据如下:
在这里插入图片描述

增加日志打印配置

如果想查看对应自动生成的sql,则需要在配置文件中增加如下配置:

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

重新执行测试类,观察控制台输出:
在这里插入图片描述

日志打印出来了!

上面的基本配置已经实现了不需要编写sql就能达到简单的 CRUD操作。接下来具体分析各种操作方式的应用。

简单CRUD操作

插入数据 insert

BaseMapper<T>父类中,存在新增操作的接口,只需要调用即可。如下所示的测试操作:

@Test
public void insertTest(){
    User user = new User();
    user.setName("香蕉");
    user.setAge(22);
    user.setEmail("123456@");

    int insert = userMapper.insert(user);
    System.out.println("-->"+insert); // 受影响的行数
    System.out.println("-->"+user); // id 自动回填
}

执行操作执行后,查看控制台和数据库数据信息。如下所示:
在这里插入图片描述
在这里插入图片描述
【发现:】随机生辰的 id 居然是 0 !

再次执行一下上述的代码,直接报错!

所以,现在的问题根源在于主键生成的策略问题,接下来说明下主键生成策略有哪些。

主键生成策略

在之前编写的pojo.User类中,原内容如下所示:

package cn.linkpower.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private long id;
    private String name;
    private Integer age;
    private String email;
}

常见的能作为主键id的有以下几种类型:

自增id、UUID、雪花算法、redis生成、Zookeper生成等。
参考资料:分布式系统唯一ID生成方案汇总

pojo指定的类中,我们可以对其主键列增加新的注解@TableId,其中存在以下几个属性:
在这里插入图片描述
其中对应的类型为IdType是一个enum 枚举类,其中各项参数信息如下所示:

public enum IdType {
    AUTO(0), // 数据库 id 自增
    NONE(1), // 未设置主键
    INPUT(2), // 手动输入
    ID_WORKER(3), // 全局id
    UUID(4), // 全局唯一id uuis
    ID_WORKER_STR(5); // 截取字段串,ID_WORKER的字符串表示法

    private int key;

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

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

接下来我们将每个参数的含义做一个代码测试:

IdType.AUTO 主键自增

使用@TableId(type = IdType.AUTO)针对主键字段增加自增策略,需要保证以下几点:

  • 对应的类的主键上增加注解:@TableId(type = IdType.AUTO)
  • 对应数据库的主键必须是自增的。
package cn.linkpower.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

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

    private String name;
    private Integer age;
    private String email;
}

在这里插入图片描述
执行新增操作测试代码,查看控制台日志如下所示:
在这里插入图片描述

IdType.ID_WORKER 雪花算法

关于雪花算法,可以参考博客分布式系统唯一ID生成方案汇总

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

修改对应类中的主键生成策略类型:

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

【注意:】这里的类型由 long 更换为 包装类型 Long !

long 的默认值为 0,当数据库无id为0的数据信息时,可以增加成功!
如果存在。则会出现报错信息!

原因在于,mybatis plus源码中的MybatisDefaultParameterHandler中存在一个校验。
在这里插入图片描述
其次将数据库中的主键类型选择不自增

设置自增或不自增都可以!

测试代码执行,控制台和数据库数据如下所示:
在这里插入图片描述
在这里插入图片描述

IdType.INPUT 手动输入

如果主键类型选用INPUT方式,则需要在insert数据时,自己指定id信息

数据库主键不自增

当不指定时:

@Test
public void insertTest(){
    User user = new User();
    user.setName("香蕉 input 3");
    user.setAge(22);
    user.setEmail("123456@");

    int insert = userMapper.insert(user);
    System.out.println("-->"+insert);
    System.out.println("-->"+user);
}

控制台中则会出现报错:
在这里插入图片描述
手动设置主键id的值:

@Test
public void insertTest(){
    User user = new User();
    user.setId(66l);    
    user.setName("香蕉 input 3");
    user.setAge(22);
    user.setEmail("123456@");

    int insert = userMapper.insert(user);
    System.out.println("-->"+insert);
    System.out.println("-->"+user);
}

在这里插入图片描述

更新 update

在mybatis plus 中,针对插入操作也提供了相应的处理方式。

测试代码如下所示:

@Test
public void testUpdate(){
    User user = new User();
    user.setId(1l); // id为1
    user.setName("香蕉"); // 数据库原信息为 Jone ,此时需要将其name更改为  “香蕉”

    int updateNum = userMapper.updateById(user);
    System.out.println("--->"+updateNum);
}

其中,测试的User.java如下所示:

package cn.linkpower.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

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

    private String name;
    private Integer age;
    private String email;
}

执行后控制台数据信息如下所示:
在这里插入图片描述
如果是多个字段呢?

修改测试代码如下所示:

@Test
public void testUpdate(){
    User user = new User();
    user.setId(1l); // id为1
    user.setName("香蕉222"); // 数据库原信息为 “香蕉”  ,此时需要将其name更改为  “香蕉222”
    user.setAge(55); // 原数据为 18
    
    int updateNum = userMapper.updateById(user);
    System.out.println("--->"+updateNum);
    System.out.println("--->"+user);
}

在这里插入图片描述

这就是一个简单的动态sql

自动填充

在实际项目中,针对比如时间类型的数据信息,需要保证自动填充的方式动态的增加或修改敌营的数据信息。此时就需要使用到自动填充操作。

首先需要在对应的数据库表中新增字段:create_time、update_time
在这里插入图片描述


方式一:数据库方式(了解)

【扩展:】如果想依靠数据库自动添加数据,可以采取如下方式。(本次不采用此方式)

需要给其设定默认值。
在这里插入图片描述
CURRENT_TIMESTAMP

增加实体映射类的属性:

package cn.linkpower.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

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

    private String name;
    private Integer age;
    private String email;
    // 注意驼峰
    private Date createTime;
    private Date updateTime;
}

执行数据添加操作:

@Test
public void insertTest(){
    User user = new User();
    //user.setId(66l); // type = IdType.INPUT
    user.setName("香蕉 ");
    user.setAge(28);
    user.setEmail("123456@");

    int insert = userMapper.insert(user);
    System.out.println("-->"+insert);
    System.out.println("-->"+user);
}

在这里插入图片描述
在这里插入图片描述
【遗留问题:】

这里测试,数据库的更新操作能成功执行,但是时间并未变更。


方式二:代码方式(推荐)

数据库中只进行字段的增加,不设定默认值
在这里插入图片描述
官方文档参考连接:

https://mp.baomidou.com/guide/auto-fill-metainfo.html

修改对应的User.java类,增加注解标识

package cn.linkpower.pojo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

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

    private String name;
    private Integer age;
    private String email;

    // 和数据库字段绑定;填充:新增数据时填充
    @TableField(value = "create_time",fill= FieldFill.INSERT)
    private Date createTime;
    // 和数据库字段绑定;填充:新增数据时必须填入,修改数据时也需要填入
    @TableField(value = "update_time",fill= FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

编写处理器类,针对该注解信息自动处理数据:

package cn.linkpower.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Date;

@Slf4j
@Component // 将组件加载至spring的ioc容器中
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     * 插入时的填充策略
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("---- insertFill ---");
        // 插入数据时
        // 给 createTime 设定时间
        this.setFieldValByName("createTime",new Date(),metaObject);
        // 给 update_time 设定时间
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    /**
     * 更新时的填充策略
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("---- updateFill ---");
        // 更新操作时,只需要修改 update_time 的数据即可
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

执行新增数据操作:

@Test
public void insertTest(){
    User user = new User();
    //user.setId(66l); // type = IdType.INPUT
    user.setName("香蕉 time test");
    user.setAge(28);
    user.setEmail("123456@");

    int insert = userMapper.insert(user);
    System.out.println("-->"+insert);
    System.out.println("-->"+user);
}

在这里插入图片描述
此时数据库中的数据信息为:
在这里插入图片描述
执行一下修改操作:

@Test
public void testUpdate(){
    User user = new User();
    user.setId(1439160297374285825l); // id为1
    user.setName("香蕉 时间测试 222"); 
    user.setAge(55); // 原数据为 18

    int updateNum = userMapper.updateById(user);
    System.out.println("--->"+updateNum);
    System.out.println("--->"+user);
}

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

乐观锁

在平时的学习和工作中,总会听见数据库的乐观锁悲观锁等概念。在 Mybatis-Plus 中,也对其进行了技术支持。

详见官方文档:
https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor

【疑问:】乐观锁和悲观锁 到底是什么?

  • 乐观锁:
    顾名思义,十分乐观,认为不会出现问题;无论干什么,都不去进行加锁。
    如果出现问题,就再更新值测试!

  • 悲观锁:
    顾名思义,十分悲观,认为不论干什么总会出现问题;无论干什么,都会上锁!

本篇博客主要说明 乐观锁 机制。

在官方文档中,已经给定了测试方式。

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

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

实现步骤

关于乐观锁的实现方式,mybatis-plus已经为我们做好了实现方式,只需要按照其给定的方式编写代码和配置即可。

1、修改对应的数据库表,增加version字段,并设定默认值为1
在这里插入图片描述
2、修改对应的pojo中的User.java类,增加对应的属性。同时使用@com.baomidou.mybatisplus.annotation.Version注解对其修饰。

package cn.linkpower.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

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

    private String name;
    private Integer age;
    private String email;
    
    @Version // 使用mybatis-plus的注解信息,表明这是一个“乐观锁”
    private Integer version;

    // 和数据库字段绑定;填充:新增数据时填充
    @TableField(value = "create_time",fill= FieldFill.INSERT)
    private Date createTime;
    // 和数据库字段绑定;填充:新增数据时必须填入,修改数据时也需要填入
    @TableField(value = "update_time",fill= FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

【注意:】这里需要注意一点:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity 中
  • 仅支持 updateById(id) 与 update(entity, wrapper) 方法
  • 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!

3、使用 OptimisticLockerInnerInterceptor 组件,对其进行配置。
在官方文档中,提供了两种方式:旧版新版
如果是直接使用旧版配置,只需要配置下列bean即可:

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

如果使用新版,则需要先保证mybatis-plus的版本为3.4.0

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

其次使用下列配置项:

package cn.linkpower.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
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;

@Configuration
@EnableTransactionManagement // 开启事务,默认是开启的
// @MapperScan("cn.linkpower.mapper")  //通常会把mapper的扫描放在此处
public class MPOptimisticLockerConfig {

    /**
     * 旧版
     */
//    @Bean
//    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
//        return new OptimisticLockerInterceptor();
//    }

    /**
     * 新版
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

4、编写测试类进行测试。
这里的测试,分两种方式进行测试:单线程多线程

首先来看单线程环境下,代码测试结果:

// 测试乐观锁(单线程环境下)
@Test
public void testOptimisticLocker1(){
    // 1、查询数据库获取当前待操作数据的基本信息,主要是version
    User user = userMapper.selectById(2L);
    log.info("查询数据信息为:{}",user.toString());
    // 2、更新数据信息
    user.setName("香蕉1");
    user.setAge(18);

    // 3、执行更新操作
    userMapper.updateById(user);
    log.info("更新后的数据:{}",user.toString());
}

在这里插入图片描述
数据库中的原始数据:
在这里插入图片描述
执行后的数据信息:
在这里插入图片描述
【发现:】

能够修改乐观锁的版本 version 字段信息。

上面验证了正常逻辑,再看一个多线程环境下的异常逻辑。

@Test
public void testOptimisticLocker2(){
    // 线程1 查询操作
    User user1 = userMapper.selectById(2L);
    user1.setName("香蕉1111");
    user1.setAge(18);


    // 在线程1之前修改操作之前,进行了线程2的插队操作
    User user2 = userMapper.selectById(2L);
    user2.setName("香蕉2222");
    user2.setAge(18);
    // 线程2优先进行了提交操作
    int updateNum2 = userMapper.updateById(user2);
    log.info("线程2修改数据条数:{}",String.valueOf(updateNum2));

    // 线程1 的提交修改操作
    int updateNum1 = userMapper.updateById(user1);// 如果乐观锁正常,此处一定是修改数据条数为0
    log.info("线程1修改数据条数:{}",String.valueOf(updateNum1));
}

查看控制台日志如下所示:
在这里插入图片描述

查询 select

查询单个用户信息

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

在这里插入图片描述

查询多个用户信息

// 查询多个用户
@Test
public void testSelectMore(){
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4));
    users.forEach(System.out::println);
}

在这里插入图片描述

使用map封装条件,进行查询

上述的两种方式实现固有的主键进行查询,但现实开发中,单独的主键id查询很少,通常都会有很多附加条件。

使用Map针对条件进行保证,即可达到大部分的条件查询。

@Test
public void testSelectByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","香蕉 时间测试 222");
    map.put("age",55);

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

在这里插入图片描述
【注意:】如果条件名称错误,运行会报错!

org.springframework.jdbc.BadSqlGrammarException:

分页查询

简单的查询操作,往往在项目中还不够,针对数据量多的表,一般还会有分页查询。

一般分页操作有如下几种实现方式:

  • 原生sql使用limit进行分页操作
  • 使用第三方插件(如:PageHelper)进行分页
  • Mybatis-Plus自带分页插件

官方文档 中,针对分页插件就有很详细的说明配置等。

使用Mybatis-Plus自带分页插件需要先编写一个分页插件的配置类。
使用到的依赖版本为:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

配置文件:

package cn.linkpower.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
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;

@Configuration
@EnableTransactionManagement // 开启事务,默认是开启的
public class MPOptimisticLockerConfig {

    /**
     * 旧版
     */
//    @Bean
//    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
//        return new OptimisticLockerInterceptor();
//    }

    /**
     * 新版
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        
        // 乐观锁
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        // 分页
        // 官方给的案例是H2数据库,我们测试使用的是MySQL,此处需要做更改
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }

    // 分页旧版
//    @Bean
//    public PaginationInterceptor paginationInterceptor() {
//        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
//        // paginationInterceptor.setOverflow(false);
//        // 设置最大单页限制数量,默认 500 条,-1 不受限制
//        // paginationInterceptor.setLimit(500);
//        // 开启 count 的 join 优化,只针对部分 left join
//        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
//        return paginationInterceptor;
//    }
}

配置类编写完成后,只需要使用其给的一个内置Page对象即可。

// 普通分页查询测试
@Test
public void testSelectPage(){
    Page<User> page = new Page<>(1,3);
    Page<User> queryPage = userMapper.selectPage(page, null);

    page.getRecords().forEach(System.out::println);
    log.info("========================");
    queryPage.getRecords().forEach(System.out::println);
}

在这里插入图片描述
如果把Page中的数据修改成:

Page<User> page = new Page<>(2,3);

此时的控制台sql如下:
在这里插入图片描述

数据的回写。

删除 delete

普通删除

删除操作在BaseMapper中提供了好几种方式,接下来一起看看都有什么效果。

删除指定id的信息

数据库指定的表原数据信息如下所示:
在这里插入图片描述

// 删除指定id的数据信息
@Test
public void testDeleteOne(){
    int delete = userMapper.deleteById(1439160297374285825L);
    log.info("删除行数:{}",delete);
}

在这里插入图片描述

批量删除
// 删除指定id的数据信息
@Test
public void testDeleteMore(){
    int deleteBatchIds = userMapper.deleteBatchIds(Arrays.asList(1439160297374285825L, 1439155369759154178L));
    log.info("删除行数:{}",deleteBatchIds);
}

在这里插入图片描述

条件删除
// 删除指定id的数据信息
@Test
public void testDeleteByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","香蕉 时间测试 222");
    map.put("age",55);

    int deleteByMap = userMapper.deleteByMap(map);
    log.info("删除行数:{}",deleteByMap);
}

在这里插入图片描述
普通删除就是使用条件,直接删除指定数据库中的数据信息。但有些公司,需要保存一些基本的信息时,并不是能直接删除表中的数据的。这个时候就需要使用到另一个高大上的删除方式:逻辑删除

逻辑删除

逻辑删除到底是什么?

删除数据,数据并不会从数据库中直接移除,而是通过一个变量来让其失效!

接下来就来看怎么玩的。

配置类可以参考官方文档:

https://mp.baomidou.com/guide/logic-delete.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95

下面是自己的配置顺序:

1、首先,在数据库对应表中增加一个deleted字段,并设定初始值为0
在这里插入图片描述

2、pojo实体类中增加属性

package cn.linkpower.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

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

    private String name;
    private Integer age;
    private String email;

    @Version // 乐观锁版本注解
    private Integer version; // 乐观锁

    @TableLogic // 逻辑删除注解
    private Integer deleted; // 逻辑删除

    // 和数据库字段绑定;填充:新增数据时填充
    @TableField(value = "create_time",fill= FieldFill.INSERT)
    private Date createTime;
    // 和数据库字段绑定;填充:新增数据时必须填入,修改数据时也需要填入
    @TableField(value = "update_time",fill= FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

3、增加配置类,对@TableLogic的识别和监测

#逻辑删除配置
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
mybatis-plus.global-config.db-config.logic-delete-field: deleted
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value: 1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value: 0

4、进行删除测试

// 逻辑删除
@Test
public void testLogicDeleteOne(){
    int delete = userMapper.deleteById(1L);
    log.info("删除行数:{}",delete);
}

原始数据为:
在这里插入图片描述
执行删除方法后,数据库数据为:
在这里插入图片描述
控制台日志输出打印如下所示:
在这里插入图片描述
5、删除操作执行完成后,再次查询这个被删的记录呢?

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

在这里插入图片描述
【疑问:】逻辑删除了,该怎么查看被删除的数据呢?

自己写SQL!
mybatis-plus 只是提供了简单的CRUD操作,还是可以自己写SQL的!

条件构造器

在上面的查询操作中,针对很多条件的查询,我们通常采取的是使用Map集合封装查询条件和数据信息,实现基本的查询操作。
在这里插入图片描述
但是其执行后,SQL自动拼接成条件 = ?的方式,如果需要使用== 、!= 、like等这些,却显得力不从心。下面我们来一起看看什么是Wapper 条件构造器

https://mp.baomidou.com/guide/wrapper.html

eq、allEq、ne

命令含义栗子
allEq全部匹配allEq({id:1,name:“老王”,age:null}) —> id = 1 and name = ‘老王’ and age is null
eq= 等于eq(“name”, “老王”)—>name = ‘老王’
ne<> 不等于ne(“name”, “老王”)—>name <> ‘老王’

例如:数据库中的数据信息如下所示:
在这里插入图片描述
案例:查找id不为1age为18的记录数。

@Test
public void test1(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.ne("id",1).eq("age",18);

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

在这里插入图片描述

大小区分

命令含义栗子
gt>gt(“age”, 18)—>age > 18
ge>=ge(“age”, 18)—>age >= 18
lt<lt(“age”, 18)—>age < 18
le<=le(“age”, 18)—>age <= 18

案例:查找id大于等于1年龄小于等于30的数据记录。

@Test
public void test2(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.ge("id",1).le("age",30);

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

在这里插入图片描述

范围查询

命令含义栗子
betweenBETWEEN 值1 AND 值2between(“age”, 18, 30)—>age between 18 and 30
notBetweenNOT BETWEEN 值1 AND 值2notBetween(“age”, 18, 30)—>age not between 18 and 30

案例:查询年龄在20到30岁之间的数据。

//查询年龄在20到30岁之间的数据。
@Test
public void test3(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.between("age",20,30);

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

在这里插入图片描述

模糊查询

命令含义栗子
likeLIKE ‘%值%’like(“name”, “王”)—>name like ‘%王%’
notLikeNOT LIKE ‘%值%’notLike(“name”, “王”)—>name not like ‘%王%’
likeLeftLIKE ‘%值’likeLeft(“name”, “王”)—>name like ‘%王’
likeRightLIKE ‘值%’likeRight(“name”, “王”)—>name like ‘王%’

栗子:查询name 以香蕉开头、以2结尾的记录。

@Test
public void test4(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.likeRight("name","香蕉").likeLeft("name","2");

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

在这里插入图片描述

isNull、isNotNull

命令含义栗子
isNullIS NULLisNull(“name”)—>name is null
isNotNullIS NOT NULLisNotNull(“name”)—>name is not null

栗子:查找create_time为空update_time不为空的记录

@Test
public void test5(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.isNull("create_time").isNotNull("update_time");

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

在这里插入图片描述

in、notIn、inSql

命令含义栗子
in列 in (xx,xx,xx,)in(“age”, 1, 2, 3)—>age in (1,2,3)
nitIn字段 NOT IN (xx,xx,xx,)notIn(“age”,{1,2,3})—>age not in (1,2,3)
inSql字段 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)

栗子:查询email为 123456@的所有用户信息。

本来可以直接采取eq拿取数据,此处只是为了做测试!

@Test
public void test6(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.inSql("id","select id from user where email = '123456@'");

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

在这里插入图片描述

排序

命令含义栗子标注
orderByORDER BY 字段, …orderBy(true, true, “id”, “name”)—>order by id ASC,name ASC默认升序
orderByAscORDER BY 字段, … ASCorderByAsc(“id”, “name”)—>order by id ASC,name ASC升序
orderByDescORDER BY 字段, … DESCorderByDesc(“id”, “name”)—>order by id DESC,name DESC降序

栗子:查询id 升序name降序的记录。

@Test
public void test7(){
    // 使用 Wapper 接口的子实现类,查询操作使用 QueryWrapper
    QueryWrapper<User> wapper = new QueryWrapper<>();
    wapper.orderByAsc("id").orderByDesc("name");

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

在这里插入图片描述

性能分析插件

Mybatis-plus 为我们提供了一种性能分析插件。可以用来查看SQL执行的具体信息等,方便开发者判断哪些SQL需要进行优化操作。

在配置文件中添加如下的Bean,并指定试用环境。

保证测试环境下进行分析,生产环境下不分析。

3.2.0 版本之前

@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
    PerformanceInterceptor performanceInterceptor =  new PerformanceInterceptor();
    performanceInterceptor.setFormat(true);//格式化语句
    //performanceInterceptor.setMaxTime(5);//ms 执行时间超过多少ms会抛出异常
    return  performanceInterceptor;
}

再配置项目环境信息:

spring.profiles.active=dev

3.2.0 版本之后

上述的配置操作,在3.2.0版本进行了废除!此时需要使用到官方给定的另外一种实现方式:

https://mp.baomidou.com/guide/p6spy.html

添加对应的p6syp的依赖:

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.8.2</version>
</dependency>

其次,修改数据库连接配置信息:

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:p6spy:mysql://192.168.99.100:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver

主要是变更driver-class-name的驱动配置和urljdbc: 后添加 p6spy:

新增spy.properties文件,其中内容如下所示:

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

配置完成后,执行下列查询语句:

// 查询多个用户
@Test
public void testSelectMore(){
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4));
    users.forEach(System.out::println);
}

控制台输出日志信息如下所示:
在这里插入图片描述
参考资料:
MyBatisPlus高级功能 ——SQL性能分析打印插件

代码下载

https://gitee.com/xiangjiaobunana/springboot-mybatis-plus

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
将SSM框架中的MyBatis升级到MyBatis-Plus是可行的,可以实现共存。SSM框架由SpringSpring MVC和MyBatis组成,而MyBatis-Plus是对MyBatis的增强扩展。下面将介绍如何将它们共存。 首先,需要将MyBatis升级到MyBatis-Plus。可以将MyBatis-Plus的依赖项添加到项目的pom.xml文件中,替换原有的MyBatis依赖。然后,需要对原有的MyBatis配置文件进行修改。MyBatis-Plus提供了一些方便的功能和特性,如自动填充、逻辑删除等,可以根据项目需求选择开启或关闭。 在SSM框架中,MyBatis-Plus可以与原有的Spring框架和Spring MVC框架完美共存。Spring框架负责管理和配置各种Bean,MyBatis-Plus可以与Spring框架一起使用,将其作为DAO层的组件进行管理。在Spring的配置文件中,可以将MyBatis-Plus的配置文件加入到配置中。 在Spring MVC框架中,可以继续使用原有的控制器、服务和视图解析器等组件。MyBatis-Plus可以与Spring MVC框架无缝集成,通过Spring MVC接收请求,然后调用MyBatis-Plus进行数据访问和处理。 在具体开发过程中,可以利用MyBatis-Plus提供的一些特性简化开发工作。例如,可以使用MyBatis-Plus的代码生成器来自动生成DAO、实体类和Mapper等代码,减少手动编写的工作量。 总结来说,将SSM框架中的MyBatis升级到MyBatis-Plus是完全可以实现的,它们可以共存并完美集成。通过使用MyBatis-Plus,我们可以更加便捷地开发和管理数据库操作,提高开发效率和代码质量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值