Mybatis-plus学习

Mybatis plus

可以节省很多的工作,所有的CRUD

mybatis-plus官网:https://baomidou.com/

参考B站狂神说java

简介:

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

愿景

我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。

简介

特性


  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

支持数据库支持数据库


任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。

MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,…

框架结构


快速开始:

1.数据库:新建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)
);
DELETE FROM user;

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

2.新建springboot项目

3.导入依赖:

参考Spring boot 整合mybatisPlus 报错_你的啊澤的博客-CSDN博客

4.配置数据库:

#连接数据库 8  mysql8要配置时区  #serverTimezone=GMT%2B8:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

5.pojo

package com.lrz.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)//和数据库的id实现id自增的标签
    private long id;
    private String name;
    private Integer age;
    private String email;

}

6.mapper 继承父类BaseMapper

package com.lrz.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lrz.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

//继承父类BaseMapper
@Mapper
//@Repository //代表持久层
public interface UserMapper extends BaseMapper<User> {
    //所有的CRUD操作都已经编写完成了!
    //需要像spring配置一大堆文件了!

}

7.主启动类:添加 @MapperScan 注解,扫描 Mapper 文件夹

package com.lrz;

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

@MapperScan("com.lrz.mapper")
@SpringBootApplication
public class MybatisPlusApplication {

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

}

8.编写测试类

import com.lrz.mapper.UserMapper;
import com.lrz.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 MybatisPlusApplicationTests {

    //继承了BaseMapper,所有的方法都来自父类
    //我们也可以编写自己扩展方法
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        //参数是一个wrapper,条件构造器,这里我们先不用  写null
//        查询全部用户
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }
}

CRUD扩展

Insert

配置日志:

我们所有的sql现在都是不可见的,我们希望知道它是怎么执行的,所以我们必须要看日志!

可以参考此文档设置mybatis-plus 开启与关闭 SQL 日志打印_mybatis plus 日志_cj96248的博客-CSDN博客

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

配置完日志之后,后面的学习就需要注意这个自动生成的SQL。

插入测试:
 @Test
    public void contextLoads2(){
        User user = new User();
        user.setName("瑞泽");
        user.setAge(23);
        user.setEmail("1042145698@qq.com");
        int result = userMapper.insert(user);
        System.out.println("result:"+result);
        System.out.println("user:"+user);
    }

数据库插入的id的默认值为:全局的唯一id

主键生成策略:

源码解释

public enum IdType {
    AUTO, //数据库id自增
    INPUT, //手动输入
    ID_WORKER, //默认的全局唯一id
    UUID, //全局唯一id  uuid
    NONE;//未设置主键
}

*默认 : ID_WORKER 全局唯一Id*

默认ID_WORKER全局id

分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法:Twitter的snowflake算法

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

Update

插入测试
       @Test
    public void textUpdate(){
        User user = new User();
        user.setId(5L);

        user.setEmail("1042145698@qq.com");
        int result = userMapper.updateById(user);
        System.out.println(result);

    }
自动填充

创建时间、更改时间! 这些操作一般都是自动化完成,我们不希望手动更新

阿里巴巴开发手册︰几乎所有的表都要配置 gmt_create、gmt_modified !而且需要自动化

方式一:数据库级别(工作中不允许修改数据库级别)

1、在表中增加字段:create_time,update_time
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AXUJAPM4-1679726209468)(C:\Users\lrz99\AppData\Roaming\Typora\typora-user-images\image-20230324180750223.png)]

MySQL的CURRENT_TIMESTAMP

在创建时间字段的时候

DEFAULT CURRENT_TIMESTAMP
表示当插入数据的时候,该字段默认值为当前时间

ON UPDATE CURRENT_TIMESTAMP
表示每次更新这条数据的时候,该字段都会更新成当前时间

2、再次测试插入或更新方法,我们需要在实体类中同步!

添加到User表中

private Date createTime;//驼峰命名
private Date updateTime;

方式二:代码级别

1、删除数据库的默认值,更新操作!

2、实体类字段属性上需要增加注解

//字段  字段添加填充内容
@TableField(fill = FieldFill.INSERT)//value = ("create_time"),

private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

3、编写处理器来处理这个注解即可!

package com.lrz.handler;

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.util.Date;

@Slf4j//日志
@Component//丢到springboot里   一定不要忘记把处理器加到Ioc容器中!
public class MyMetaObjectHandler implements MetaObjectHandler {//extends??
    @Override//插入时的填充策略
    public void insertFill(MetaObject metaObject) {
        log.info("==start insert ······==");
        //源码:setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject);
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }    @Override//更新时的填充策略
    public void updateFill(MetaObject metaObject) {
        log.info("==start update ······==");
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

4、测试插入/更新,观察时间

乐观锁

**主要适用场景:**当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

**乐观锁实现方式:**取出记录时,获取当前version更新时,带上这个version执行更新时, set version = newVersion where version = oldVersion如果version不对,就更新失败

接下来介绍如何在Mybatis-Plus项目中,使用乐观锁。

OptimisticLockerInnerInterceptor

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

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败
乐观锁实现流程
  • 修改实体类

添加 @Version 注解

@Versionprivate
private Integer version;
  • 创建配置文件

创建包config,创建文件MpConfig.java

此时可以删除主类中的 @MapperScan 扫描注解

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.lrz.mapper")//重新配置此文件
public class MpConfig {

    /*
    * 乐观锁
    旧版
    * */
  public OptimisticLockerInterceptor optimisticLockerInterceptor(){
      return  new OptimisticLockerInterceptor();
  }
}
    //测试乐观锁成功
    @Test
	public void testOptimisticLocker(){
        //1.查询用户信息
        User user = userMapper.selectById(1l);
        //2.修改用户信息
        user.setName("张瀚丹");
        user.setAge(22);
        //3.执行更新操作
        userMapper.updateById(user);
    }
    //测试乐观锁失败     : 多线程下
    @Test
    public void testOptimisticLocker2(){
        //线程1
        User user = userMapper.selectById(1l);
        user.setName("1999林瑞泽");
        user.setAge(22);
        //线程2
        User user2 = userMapper.selectById(1l);
        user2.setName("林瑞泽1999");
        user2.setAge(23);
        userMapper.updateById(user2);
        userMapper.updateById(user);
    }

会执行的是线程一

查询

通过多个 id 批量查询

  • 完成了动态sql的foreach的功能
   //测试批量查询
    @Test
    public void selectByBatchId(){
        List<User> us = userMapper.selectBatchIds(Arrays.asList(1,2,3));
        us.forEach(System.out::println);

    }
  • 简单的条件查询

通过map封装查询条件

注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id

//测试条件区的查询用map操作
    @Test
    public void testSelectByBatchIds(){
        HashMap<String,Object> map = new HashMap<>();
        //自定义查询的条件
        map.put("name","啊泽");
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }

分页

分页在网站使用的十分之多!

1.原始的limit进行分页

2.pageHelper第三方插件

3.MP也内置了分页插件!

如何使用:

1.配置拦截器组件即可分页插件

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能

/**
* 分页插件 
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
	return new PaginationInterceptor();
}
  • 测试 selectPage 分页

测试:最终通过page对象获取相关数据

//分页查询
@Test
public void testSelectPage() {
    Page<User> page = new Page(1,3);
    Page<User> userPage = userMapper.selectPage(page, null);
    //返回对象得到分页所有数据
    long pages = userPage.getPages();
    //总页数
    long current = userPage.getCurrent();
    //当前页
    List<User> records = userPage.getRecords();
    //查询数据集合
    long total = userPage.getTotal();
    //总记录数
    boolean hasNext = userPage.hasNext();
    //下一页
    boolean hasPrevious = userPage.hasPrevious();
    //上一页
    System.out.println(pages);
    System.out.println(current);
    System.out.println(records);
    System.out.println(total);
    System.out.println(hasNext);
    System.out.println(hasPrevious);
}
  • 测试 selecMapsPage 分页

当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值

测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {
    //Page不需要泛型
    Page<Map<String, Object>> page = newPage<>(1, 5);
    Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
    List<Map<String, Object>> records = pageParam.getRecords();
    records.forEach(System.out::println);
    System.out.println(pageParam.getCurrent());
    System.out.println(pageParam.getPages());
    System.out.println(pageParam.getSize());
    System.out.println(pageParam.getTotal());
    System.out.println(pageParam.hasNext());
    System.out.println(pageParam.hasPrevious());
}

测试:直接使用Page对象即可

//分页查询@Test
public void testPaginationInterceptor(){
    Page<User> page = new Page<>(1,5);
    userMapper.selectPage(page,null);
    page.getRecords().forEach(System.out::println);
}

删除操作

//删除
@Test
public void testDeleteById(){
    userMapper.deleteById(1l);
}
//根据多个id删除
@Test
public void testDeleteBatchIds(){
    userMapper.deleteBatchIds(Arrays.asList(2,3));
  }
//按条件删除 Map
@Test
public void testDeleteByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","啊泽");
    userMapper.deleteByMap(map);
}

逻辑删除

逻辑删除:在数据库中没有移除 :通过 deleted =0 =>delete=1

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

管理员可以查看被删除的记录!防止数据的丢失,类似于回收站

测试一下:

1.在数据库中增加一个字段deleted,默认值为0

2.pojo,在实体类上添加字段

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

3.配置:

MyConfig

高版本的Spring Boot 不用配置config类

低版本的配置下列代码

@Bean
public ISqlInjector sqlInjector() {
  return new LogicSqlInjector();

.properties

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

.yml

mybatis-plus:
  global-config:
    db-config:
      # 1 代表已删除,不配置默认是1,也可修改配置
      logic-delete-value: 1
      # 0 代表未删除,不配置默认是0,也可修改配置
      logic-not-delete-value: 0

4.测试:

 //删除
    @Test
    public void testDeleteById(){
        userMapper.deleteById(2l);
    }

条件构造器和常用接口

Wrapper : 条件构造抽象类,最顶端父类

AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

QueryWrapper : 查询条件封装

UpdateWrapper : Update 条件封装

AbstractLambdaWrapper : 使用Lambda 语法

LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper

LambdaUpdateWrapper : Lambda 更新封装Wrapper

测试用例

  • ge、gt、le、lt、isNull、isNotNull
@Test
    public void testQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .isNull("name")//为空
                .ge("age", 12)//大于
                .isNotNull("email");//不为空
        int result = userMapper.delete(queryWrapper);
        System.out.println("delete return count = " + result);
    }
  • eq、ne

注意:seletOne()返回的是一条实体记录,当出现多条时会报错

@Test
    public void testSelectOne() {
        QueryWrapper<User>queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "Tom");
        User user = userMapper.selectOne(queryWrapper);//只能返回一条记录,多余一条则抛出异常
        System.out.println(user);
    }
  • between、notbetween

包含大小边界

@Test
    public void testSelectCount() {
        QueryWrapper<User>queryWrapper = new QueryWrapper<>();
        queryWrapper.between("age", 20, 30);
        Integer count = userMapper.selectCount(queryWrapper); //返回数据数量
        System.out.println(count);
    }
  • like、notLike、likeLeft、likeRight

selectMaps()返回Map集合列表,通常配合select()使用

 @Test
    public void testSelectMaps() {
        QueryWrapper<User>queryWrapper = new QueryWrapper<>();
        queryWrapper
                .select("name", "age")
                .like("name", "e")
                .likeRight("email", "e");
        List<Map<String, Object>>maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
        maps.forEach(System.out::println);
    }
  • orderBy、orderByDesc、orderByAsc
 @Test
    public void testSelectListOrderBy() {
        QueryWrapper<User>queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("age", "id");
        List<User>users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

查询方法

查询方式说明
setSqlSelect设置 SELECT 查询字段
whereWHERE 语句,拼接 + WHERE 条件
andAND 语句,拼接 + AND 字段=值
andNewAND 语句,拼接 + AND (字段=值)
orOR 语句,拼接 + OR 字段=值
orNewOR 语句,拼接 + OR (字段=值)
eq等于=
allEq基于 map 内容等于=
ne不等于<>
gt大于>
ge大于等于>=
lt小于<
le小于等于<=
like模糊查询 LIKE
notLike模糊查询 NOT LIKE
inIN 查询
notInNOT IN 查询
isNullNULL 值查询
isNotNullIS NOT NULL
groupBy分组 GROUP BY
havingHAVING 关键词
orderBy排序 ORDER BY
orderAscASC 排序 ORDER BY
orderDescDESC 排序 ORDER BY
existsEXISTS 条件语句
notExistsNOT EXISTS 条件语句
betweenBETWEEN 条件语句
notBetweenNOT BETWEEN 条件语句
addFilter自由拼接 SQL
last拼接在最后,例如:last(“LIMIT 1”)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值