Mybatis-Plus

1. 概述

前置基础:Mybatis spring springMVC
CRUD代码可以自动完成
官网:https://mp.baomidou.com/
特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 几乎所有的数据库都可以支持

2. 快速入门

  1. 配置数据库和对相应的表
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)
);
--真实开发中,version(乐观锁)、delete(逻辑删除)、gmt_create、gmt_modified

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');
  1. 导入依赖
        <!--数据库依赖-->
	 <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.0.5</version>
        </dependency>
  1. 在application.yml中配置

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

#配置日志之后可以在控制台或者日志文件中输出执行的sql语句
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. 代码的编写(平常我们需要pojo dao service controller)
    现在只需要
    image
-----pojo-----(对应数据库即可)
package com.sli.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author 1_f_
 * @create 2021-10-08 20:28
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //插入

    /*所有的自增策略
    AUTO(0),            主键自增(如要开启自增策略的话需要先去数据库中将主键自增勾选上,然后将策略改为IdType.Auto)
    NONE(1),            none表示不使用,未设置主键,也就是表中没有主键的话使用
    INPUT(2),           手动输入
    ID_WORKER(3),       雪花算法推特开源的分布式id->Long,全球唯一
    UUID(4),            全局唯一id
    ID_WORKER_STR(5);   截取字符串ID_WORKER的字符串表示法
     */
    @TableId(type = IdType.INPUT)//默认为ID_WORKER全局唯一id
    private Long id;//对应数据库中的主键(uuid 自增id 雪花算法 redis zookeeper)
    private String name;
    private Integer age;
    private String email;

-----dao/mapper-----(什么都不需要写,只需要继承BaseMapper然后在后面的泛型中传入对饮的实体类)
package com.sli.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sli.pojo.User;
import org.springframework.stereotype.Repository;
/**
 * @author 1_f_
 * @create 2021-10-08 20:30
 */
//在对应的mapper / dao上面继承 BaseMapper
@Repository//代表他是持久层
public interface UserMapper extends BaseMapper<User> {
    //填写完泛型之后所有的CRUD已经自动编写完成,不需要配置了!
    //然后去主启动类中开启自动扫描
}
}
  1. 去主方法中开启扫描@MapperScan("com.sli.mapper")//扫描mapper文件夹
  2. 去单元测试中测试方法能否正常使用
package com.sli;

import com.sli.mapper.UserMapper;
import com.sli.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.sql.SQLOutput;
import java.util.List;

@SpringBootTest
class MybatisPlusApplicationTests {

    @Autowired
    private UserMapper userMapper;

    //使用了userMapper.之后里面所有的方法都来自与父类继承的方法,也可以编写扩展方法
    @Test
    void contextLoads() {
        //参数是一个wrapper , 条件构造器,先不使用的话为null;
        List<User> userList = userMapper.selectList(null);

        userList.forEach(System.out::println);
    }
    //测试插入
    @Test
    public void testInsert(){
        User user = new User();
        user.setAge(2);
        user.setEmail("123123");
        user.setName("qwq");

        int result = userMapper.insert(user);//帮我们自动生成id

        System.out.println(result);//受影响的行数

        System.out.println(user);//发现id会自动回填
    }

    //测试更新
    @Test
    public void testUpdate(){
        User user = new User();
        //而且他可以自动动态拼接sql
        user.setId(5L);
        user.setEmail("234234234");

        //注意:updateById的参数是一个对象

        int i = userMapper.updateById(user);
        System.out.println(i);
    }
}

测试结果以及控制台的sql语句输出证明可以正常使用

3. CRUD的扩展

1. 插入

  1. 插入操作的扩展(在单元测方法中添加插入)
 @Test
    public void testInsert(){
        User user = new User();
        user.setAge(2);
        user.setEmail("123123");
        user.setName("qwq");

        int result = userMapper.insert(user);//帮我们自动生成id

        System.out.println(result);//受影响的行数

        System.out.println(user);//发现id会自动回填
    }

会发现id会自动生成,并且数字会很大
id默认的插入策略是ID_WORKER(3)雪花算法推特开源的分布式id->Long,全球唯一的id
我们可以控制id的增长策略
2. 在pojo中可以控制id的增长策略

-----pojo-----(对应数据库即可)
package com.sli.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author 1_f_
 * @create 2021-10-08 20:28
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //插入

    /*所有的自增策略
    AUTO(0),            主键自增(如要开启自增策略的话需要先去数据库中将主键自增勾选上,然后将策略改为IdType.Auto)
    NONE(1),            none表示不使用,未设置主键,也就是表中没有主键的话使用
    INPUT(2),           手动输入
    ID_WORKER(3),       雪花算法推特开源的分布式id->Long,全球唯一
    UUID(4),            全局唯一id
    ID_WORKER_STR(5);   截取字符串ID_WORKER的字符串表示法
     */
    @TableId(type = IdType.INPUT)//默认为ID_WORKER全局唯一id
    private Long id;//对应数据库中的主键(uuid 自增id 雪花算法 redis zookeeper)
    private String name;
    private Integer age;
    private String email;

2. 修改

  1. 在单元测试中(注意,updateById需要的是一个对象,而不是id)
     @Test
    public void testUpdate(){
        User user = new User();
        //而且他可以自动动态拼接sql
        user.setId(5L);
        user.setEmail("234234234");

        //注意:updateById的参数是一个对象

        int i = userMapper.updateById(user);
        System.out.println(i);
    }

好处,所有的sql都是动态配置且为自动的,不需要我们拼接sql语句,节约了时间
image


  1. 基于代码版本的创建时间以及修改时间
  • 在数据库汇中添加两个字段分别为create_time和update_time分别为datetime类型的参数
  • 在实体类中增加这两个字段
  //字段添加填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
  • 创建handler.MyMateObjectHand.java
@Component
@Slf4j
public class MyMateObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    /*
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ------------------------");
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ------------------------");
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}
  • 测试更新以及创建看看时间是否发生改变

  1. 修改有可能设计锁的问题:悲观锁,乐观锁

乐观锁:干什么都不上锁,出现问题了,再次更新值测试
悲观锁:干什么都上锁,然后再去操作

乐观锁机制讲解及使用

  1. 实现方式
  • 取出记录,获取当前的version
  • 更新时,带上version
  • 执行更新时 set version = new version where version = oldversion
  • 如果version的值不对,就更新失败
  1. 使用方式
  • 在表中添加version字段
    image
  • 在实体类添加相应的字段(记得加version的注解)
    image
  • 注册组件
    新建一个config.MpConfig.java
@EnableTransactionManagement
@Configuration
public class MpConfig {
    //注册乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }

}
  • 测试乐观锁
 @Test
    //测试乐观锁成功
    public void testOptimisticLocker(){
        //查询用户信息
        User user = userMapper.selectById(1L);
        //修改用户信息
        user.setName("sli");
        user.setEmail("12312331231@qq.xmo");

        userMapper.updateById(user);

    }

    @Test
    //测试乐观锁失败(多线程下面)
    public void testOptimisticLocker2(){
        //线程1

        User user = userMapper.selectById(1L);
        //修改用户信息
        user.setName("sli1111");
        user.setEmail("12312331231@qq.xmo");


        //线程2 执行插队操作
        User user2 = userMapper.selectById(1L);
        //修改用户信息
        user2.setName("sli22222");
        user2.setEmail("12312331231@qq.xmo");
        userMapper.updateById(user2);

        userMapper.updateById(user);//如果没有乐观锁就会覆盖插队线程(线程2)的值

    }

3. 查询

  1. 查询操作直接测试(分为三种)
 @Test
    //测试查询2
    public void testSelectById(){
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }

    @Test
    //测试查询3(批量查询)
    public void testSelectByBatchId(){
        List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        userList.forEach(System.out::println);
    }
    @Test
    //测试条件查询
    public void testSelectByBatchIds(){
        HashMap<String, Object> map = new HashMap<>();
        //自定义查询
        map.put("name","Sandy");
        map.put("age","40");

        List<User> userList = userMapper.selectByMap(map);

        userList.forEach(System.out::println);
    }

条件查询 批量查询 普通查询
2. 分页查询

  • 使用分页插件在MpConfig中配置
 @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        return paginationInterceptor;
    }
  • 直接测试即可
   @Test
    //测试分页查询
    public void testPage(){
        //参数一:当前页 参数二:页面大小
        Page<User> page = new Page<>(1,5);
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println);
    }

4. 删除操作

删除操作分为三种


    @Test
    //测试删除
    public void testDeleteById(){
        userMapper.deleteById(1446458387612082178L);
    }

    @Test
    //通过id批量删除
    public void testDeleteByList(){
        userMapper.deleteBatchIds(Arrays.asList(22,5));
    }

    @Test
    //通过map删除
    public void testDeleteMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","qwq");
        map.put("age","2");
        userMapper.deleteByMap(map);
    }

5. 工作中会遇到的问题(逻辑删除)

  1. 和普通的删除的对比可知,物理删除是删除数据库中的数据,而逻辑删除是给数据库加上一个值通过deleted=0--->deleted=1使其失效
  2. 表中新增一行deleted字段,默认值为0 int
  3. pojo中增加对应的
@TabeLogic
private Integer deleted;
  1. application.yml配置以及MpConfig中配置
@Bean
public ISqlInjector sqlInjector(){
	return new LogicSqlInjector();
}
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  1. 测试删除,发现走的是update而不是删除表中发现只是将deleted字段变为了1

4. 插件-性能分析插件

插件的作用是:超过一定时间的sql语句会报错,还能开启sql语句格式化

  1. 导入插件
  @Bean
    @Profile({"dev","test"})//设置dev test 环境开启
    public PerformanceInterceptor performanceInterceptor(){
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        //设置sql执行的最大时间,如果超过了就不执行
        //在工作中,不允许用户等待,此时可以将1s之外的提出优化
        performanceInterceptor.setMaxTime(100);
        //设置sql格式化是否开启
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }
  1. 在application.yml中开启环境
spring:
	  #设置开发环境
  profiles:
    active: dev
  1. 进入项目测试
    image

5. 插件-条件构造器Wrapper

复杂的sql可以使用这个来替代例如多表查询

  1. 测试一
//测试一
    //使用了userMapper.之后里面所有的方法都来自与父类继承的方法,也可以编写扩展方法
    @Test
    void contextLoads() {
        //查询name不为空且邮箱不为空然后年龄大于12的
        //查询什么,泛型中就写什么
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        //isNotNull是条件不为空的,ge是大于等于的
        wrapper.isNotNull("name")
                .isNotNull("email")
                .ge("age",12);
        userMapper.selectList(wrapper).forEach(System.out::println);//和map类比发现map可以put值,而wrapper是一个对象,其中有很多的方法
    }
  1. 测试二

    @Test
    void test2(){
        //查询名字等于Tom
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        wrapper.eq("name","Tom");//eq为相等

        User user = userMapper.selectOne(wrapper);//查询一个数据

        System.out.println(user);
    }

  1. 测试三
  @Test
    void test3(){
        //查询年龄在20~30之间的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        wrapper.between("age",20,30);


        Integer count = userMapper.selectCount(wrapper);//查询结果数

        System.out.println(count);
    }

  1. 测试四
   @Test
    void test4(){
        //模糊查询 名字里面不包含T的并且邮箱是以t开头的
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        wrapper.notLike("name","T")
                .likeRight("email","t");//左和右边怎么区分左边是%T 右边是T% 统配是%T%(此处的意思是邮箱的首字母是t开头的)

        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);

        maps.forEach(System.out::println);
    }

  1. 测试五
 @Test
    void test5(){
        //
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //id 在子查询中查出来
        wrapper.inSql("id","select id from user where id<3");

        List<Object> objects = userMapper.selectObjs(wrapper);

        objects.forEach(System.out::println);
    }

6. 代码自动生成器

重点

  1. 理解
package com.sli;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import javax.xml.crypto.Data;
import javax.xml.stream.events.StartElement;
import java.util.ArrayList;

/**
 * @author 1_f_
 * @create 2021-10-09 15:51
 */
//代码自动生成器
public class Code {
    public static void main(String[] args) {
        //需要构建一个代码自动生成器对象
        AutoGenerator mpg = new AutoGenerator();
        //配置策略
        //1.全局配置
        GlobalConfig gc = new GlobalConfig();
        //输出的路径
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath+"/src/main/java");
        gc.setAuthor("sli");
        gc.setOpen(false);
        gc.setFileOverride(false);//是否覆盖
        gc.setServiceName("%sService");//去掉service的I前缀
        gc.setIdType(IdType.ID_WORKER);//设置生成id的策略
        gc.setDateType(DateType.ONLY_DATE);//生成日期的类型
        gc.setSwagger2(true);

        mpg.setGlobalConfig(gc);



        //2.设置数据源(配置连接数据库)
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("???????");
        dsc.setDbType(DbType.MYSQL);

        mpg.setDataSource(dsc);



        //3.配置生成哪些包
        PackageConfig pc = new PackageConfig();

        pc.setModuleName("blog");
        pc.setParent("com.sli");
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setController("controller");



        mpg.setPackageInfo(pc);



        //策略配置自动配置时间和乐观锁
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("user");//设置要映射的表名,也就是要对应的表
        strategy.setNaming(NamingStrategy.underline_to_camel);//_转驼峰命名
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);//自动生成lombok

        strategy.setLogicDeleteFieldName("deleted");



        //自动填充配置(创建时间和修改时间)
        TableFill createTime = new TableFill("createTime", FieldFill.INSERT);
        TableFill updateTime = new TableFill("updateTime", FieldFill.INSERT);
        ArrayList<TableFill> tableFills = new ArrayList<>();
        tableFills.add(createTime);
        tableFills.add(updateTime);

        strategy.setTableFillList(tableFills);



        //乐观锁
        strategy.setVersionFieldName("version");

        strategy.setRestControllerStyle(true);

        strategy.setRestControllerStyle(true);//连接请求localhost:8080/hello_id_2

        mpg.setStrategy(strategy);

        //执行自动生成
        mpg.execute();
    }
}

  1. 运行之后即可测试到自动生成的
    image
posted @ 2021-10-09 16:32  1_f  阅读(25)  评论(0)    收藏  举报