MybatisPlus学习笔记

基于狂神说Java-MybatisPlus

MybatisPlus快速入门

学习MybatisPlus,前提需要熟悉以下内容

Spring、SpringMVC、Mybatis

为什么要学习它?它可以实现简化开发,CRUD代码实现全自动,解放双手辣~~~

MybatisPlus,只做增强,不做改变,只需要简单的配置就可以实现功能,如丝般顺滑

在这里插入图片描述

MybatisPlus是什么

我们知道Mybatis是一个封装了JDBC的半自动CRUD框架,而MybatisPlus在Mybatis的基础上又做了一个加强,实现半自动到全自动的转变

能干嘛?

在这里插入图片描述

在哪下?

MybatisPlus官网:https://baomidou.com

项目地址(源码)

GitHub:https://github.com/baomidou/mybatis-plus

Gitee:https://gitee.com/baomidou/mybatis-plus

怎么玩?

MybatisPlus的快速开始:https://baomidou.com/guide/quick-start.html#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B

使用第三方组件:

1、导入依赖

2、依赖如何配置

3、代码怎么写

4、提高技术扩展能力

首先创建数据库,名称为mybatis_plus

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

项目构建,使用SpringBoot的初始化向导进行快速构建

在pom文件中引入依赖

<!--数据库驱动-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>
</dependency>
<!--lombok-->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>
<!--MybatisPlus-->
<!--MybatisPlus启动器是属于baomidou公司本身自己开发,而不是SpringBoot家族-->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.0.5</version>
</dependency>

数据库配置

# MySQL 8 数据库需要在URL下增加一些规则
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_plus?useSSL=false&useUnicode=true\
  &CharacterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

原生MVC架构:pojo-dao(连接Mybatis、mapper.xml配置文件)-Service-Controller

使用了MybatisPlus之后

pojo

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

mapper

// 在对应的mapper上面实现基本的接口
@Repository // 声明当前接口是一个持久层接口
public interface UserMapper extends BaseMapper<User> {
    // 和JPA一样,只要实现或者继承父类(接口),那么基本的单表的CRUD已经编写完成
}

SpringBoot主配置类

// 扫描mapper包下的持久层接口
@MapperScan("com.hrc.mapper")

测试

// 继承BaseMapper,所有的方法都来自父类,也可以编写属于自己的API
@Autowired
private UserMapper userMapper;

@Test
void contextLoads() {
  // API:selectList(@Param Wrapper wrapper);括号传递一个参数构造器
  // 如果传递为null,那么就表示没有条件,查询全部数据
  List<User> list = userMapper.selectList(null);
  list.forEach(System.out::println);
}

MybatisPlus配置日志

对于现在的sql来说,是不可见的,我们希望知道它是怎么执行的,而它执行的过程都会在日志中进行详细的记录,所以MybatisPlus的日志配置也成为了必需

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

在这里插入图片描述

CRUD扩展

数据添加

@Test
public void testIns() {
  User user = new User(null, "YouTuBe", 22, "test6@baomidou.com");
  int res = userMapper.insert(user);
  System.out.println(res);
  System.out.println(user);
}

请注意,上面的代码,id传值为空,但是在控制台输出时,MybatisPlus自动将id进行回填了

在这里插入图片描述

这边就需要多聊一嘴东西,这边数据库插入id的默认值是,全局的唯一id

这边就需要聊一聊主键生成策略

id主键生成策略

MybatisPlus的默认主键生成策略

@TableId(type = IdType.ID_WORKER)

给大家推荐一篇博客

这边主要要聊的是一个东西叫做雪花算法

雪花算法是Twitter使用zookeeper实现的一种全局唯一id的服务,英文名叫snowflake,它生成的id的结构如下

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

  • 第一位,0,表示未使用
  • 接下来的41位,它表示毫秒级时间戳,它的使用时间可以长达69年
  • 在接下来的5位,数据中心id
  • 还有5位,机器id
  • 最后12位,毫秒内的计数,可以产生4096个id

这些数位加起来刚好64位,正好对应一个64位的Long类型

既然它使用的是MybatisPlus默认的,现在需要使用MySQL数据库支持的主键自增,那么,就需要将数据库中的表的主键id设置为主键自增

在这里插入图片描述
接着将User类中的id字段设置为主键自增策略

@TableId(type = IdType.AUTO)

在这里插入图片描述

当然,不止这两个主键生成策略,其余的在源码中也解释到了,而且还是中文注释(题外话,MybatisPlus的作者是中国人[doge])

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型
     */
    NONE(1),
    /**
     * 用户输入ID
     * 该类型可以通过自己注册自动填充插件进行填充
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 全局唯一ID (idWorker)
     */
    ID_WORKER(3),
    /**
     * 全局唯一ID (UUID)
     */
    UUID(4),
    /**
     * 字符串全局唯一ID (idWorker 的字符串表示)
     */
    ID_WORKER_STR(5);

    private int key;

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

数据更新

@Test
public void testUpd() {
    User user = new User();
    // 根据条件实现动态拼接
    user.setId(1330100039132925955L);
    user.setAge(19);
    // updateById,虽然是根据id进行修改,但是参数是一个对象
    int res = userMapper.updateById(user);
    System.out.println(res);
}

并且MybatisPlus会根据你设置了多少属性来实现动态SQL

在这里插入图片描述

自动填充

在数据库中,数据的创建时间,数据的修改时间,一般在语句执行之后显示,但是我们不能手动添加这些东西,这需要时间

在阿里巴巴开发手册中表示,所有的数据库表创建时间和修改时间一定要有,也就是gmt_create和gmt_modified,这两个字段,需要实现自动化

数据库增加字段

在这里插入图片描述
按照上面的进行同步设置就可以辣~~

字段增加完毕就可以在代码当中进行同步

private Date createTime;
private Date updateTime;

Java代码自动填充

在对应的创建时间和修改时间添加注解

// 字段的自动填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

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

并且还需要编写对应的处理器

// 在进行数据插入时的填充策略
@Component // 需要将组件加载到IOC容器中
@Slf4j
public class MyMetaObjectMapperHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("正在读取当前插入时间...");
        // 这边的API,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("正在读取当前修改时间...");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

乐观锁

先聊一聊乐观锁悲观锁的概念吧

乐观锁,很乐观,觉得任何操作都不会出现问题,所以不会加锁,不过在数据库设计中要使用到乐观锁的话,一般都会添加一个字段叫做version,版本号,在进行数据更新的时候,需要去返回比对一下版本号,版本号不相同,它就不让你修改数据,需要重新设置值进行测试

悲观锁,很悲观,觉得不管是什么操作它都会出现问题,只要有一个线程进来,立刻加锁,不允许其他线程访问,在高并发使用悲观锁将是一个非常致命的选择

这边主要是聊聊乐观锁在MybatisPlus中的使用

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败
-- 对于乐观锁来说,首先需要查询获得版本号
-- 在更新时,比对版本号
update user set name = "xxx" , version = version + 1
where id = 3 and version = 1

-- 如果这个时候有一个线程在上一个线程抢先完成,那么,这个时候,version = 2,就无法使用
update user set name = "abc", version = version + 1
where id = 1 and version = 1

在数据库中添加一个version字段

在这里插入图片描述

在实体类中添加一个属性

@Version // 添加一个版本号,实现乐观锁
private Integer version;

添加一个配置类

@MapperScan("com.hrc.mybatisplus.mapper") // 本来这个东西是可以放在SpringBoot的主启动类上来使用,不过现在有了配置类,可以交给MybatisPlus自己监管
@Configuration // 声明此类为配置类,加载到容器中使用
@EnableTransactionManagement // 此注解声明交给MybatisPlus自动管理事务
public class MybatisPlusConfig {

    // 注册乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

测试

// 测试一个成功的乐观锁
    @Test
    public void testOpt() {
        User user = userMapper.selectById(1L);
        user.setName("Jackie");
        user.setEmail("Mylove@baomidou.com");
        System.out.println(userMapper.updateById(user));
    }

    // 测试乐观锁失败
    @Test
    public void testOptFail() {
        // 在这边模拟两个线程进行插队操作
        // 线程1正常执行
        User user = userMapper.selectById(2L);
        user.setAge(22);
        user.setName("Jose");

        // 线程2进行插队操作,在线程1之前抢先更新
        User user2 = userMapper.selectById(2L);
        user2.setAge(21);
        user2.setName("Marting");
        userMapper.updateById(user2);
        
        userMapper.updateById(user); // 在它的上方,第二个线程最先执行完毕,所以这一段代码不会执行
      	// 如果这个地方执行失败,就是使用自旋锁来进行重复提交,直到成功为止
    }

数据查询

@Test
public void testSel() {
    // User user = userMapper.selectById(5L);
    List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4, 5));// 批量查询
    userList.forEach(System.out::println);
}

@Test // Map条件查询
public void testSel2() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "Tom");
    List<User> userList = userMapper.selectByMap(map);
    userList.forEach(System.out::println);
}

分页查询

分页对于一些网站来说,使用的还是比较多,比较常见的有几种

  • limit分页
  • pageHelper第三方插件
  • MybatisPlus内置分页插件

如何配置MybatisPlus分页插件?

配置类

    // MybatisPlus分页插件
    @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 new PaginationInterceptor();
    }

测试

@Test
public void testSelPage() {
  Page<User> page = new Page<>(1, 5);
  IPage<User> userIPage = userMapper.selectPage(page, null);
  userIPage.getRecords().forEach(System.out::println);
}

数据删除

@Test // 删除单个
public void testDel() {
    System.out.println(userMapper.deleteById(1330817100494761987L));
}

@Test // 删除多个
public void testBenchDel() {
    System.out.println(userMapper.deleteBatchIds(Arrays.asList(1330817100494761986L, 1330817100494761985L)));
}

@Test // Map删除
public void testDelMap() {
    Map<String, Object> map = new HashMap<>();
    map.put("name", "957");
    System.out.println(userMapper.deleteByMap(map));
}

逻辑删除

在讲物理删除的时候,需要了解两个东西

物理删除:从数据库中直接删除

逻辑删除:在数据库中没有被移除,而是使用一个字段来让他失效(举一个不是特别恰当的例子:可以理解为在未删除和删除之间,中间加了一个“回收站”)

可以使用一个字段叫做deleted,int类型,指定一个数值,表示这个数据会被删除

使用逻辑删除的好处,如果删除了,就可以使用管理员的身份直接查看被删除的记录

首先在数据库中添加一个字段

在这里插入图片描述

实体类增加字段

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

在配置类中增加组件

// 逻辑删除
    @Bean
    public ISqlInjector iSqlInjector() {
        return new LogicSqlInjector();
    }

配置文件中添加配置

# 配置逻辑删除
# 当字段值为1时,默认删除当前数据
mybatis-plus.global-config.db-config.logic-delete-value=1
# 当字段值为0时,不删除
mybatis-plus.global-config.db-config.logic-not-delete-value=0

用来删除的API进行测试,但是结果可以看到

在这里插入图片描述

逻辑删除本质上就是一个Update语句

它指定要删除的记录在数据库中还是存在的

在这里插入图片描述

但是在查询这一条数据时,它依旧不会被查出来

在这里插入图片描述

在进行查询的时候,会自动过滤掉被逻辑删除的字段;

性能分析插件

在开发中,会遇到一种情况叫做慢查询(也叫慢SQL),慢查询会导致CPU负载太高(主要是查询语句中会有比较复杂的处理算法,又或者是在进行IO时负载过高),MybatisPlus针对此种情况推出了一个东西叫做性能分析插件,一旦查询的时间超过插件指定时间,程序就会自动停止

首先需要定义一个开发环境,因为在下面导入插件的时候会使用到

spring.profiles.active=dev

1、导入插件

@Bean
// 一定要设置当前项目的环境,切记!切记!
@Profile({"dev"}) // 指定环境为dev生效
public PerformanceInterceptor performanceInterceptor() {
  PerformanceInterceptor interceptor = new PerformanceInterceptor();
  // 是否格式化代码
  interceptor.setFormat(true);
  // 设置SQL超时时间
  interceptor.setMaxTime(5000L);
  return interceptor;
}

测试,在测试时,控制台输出的东西会有一些不同

在这里插入图片描述

控制台打印输出的东西,会出现一个SQL的执行时间,如果当前SQL的执行时间超过了你所设定的这个最大超时时间,那么程序会抛出异常

条件构造器

其实之前就已经在查询全部数据的时候就已经埋下了一个伏笔,测试SQL是否连接成功的时候,当时的selectList方法的传递的值为null(不知道的可以看博客开头的快速入门),这个null表示的就是Wrapper,这才是本小节的重点

复杂的条件查询,MybatisPlus中提供了一个类,AbstractWrapper,这是个抽象类,源码中也明确的表示了,这是一个条件查询的封装,也就是说,所有的类都可以通过继承它来实现复杂查询的API调用

在这里插入图片描述

在这里插入图片描述

并且在官网中也罗列出了相关的API,这是一一对应的

在这里插入图片描述

来吧,测试一下叭~~~

多条件查询

@Test
void contextLoads() {
  // 查询name不为空,email不为空的用户,年龄>=21岁
  // QueryWrapper是AbstractWrapper的子类,内部属性是为了生成查询条件
  QueryWrapper<User> wrapper = new QueryWrapper<>();
  wrapper.isNotNull("name").isNotNull("email").ge("age", 21);
  List<User> users = userMapper.selectList(wrapper);
  users.forEach(System.out::println);
}

查看一下测试的结果

在这里插入图片描述

多条件查询这边控制台打印的信息和代码当中的Wrapper条件是一一对饮的

单表单个数据

@Test // 查询单个
void test01() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name", "957");
    System.out.println(userMapper.selectOne(wrapper));
}

查询 范围

@Test // 查询区间
void test0203() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.between("age", 20, 24);
    // 查询在此区间之内的数据
    // userMapper.selectMaps(wrapper).forEach(System.out::println);
    // 查询在此区间的数据有几条
    System.out.println(userMapper.selectCount(wrapper));
}

左右两边定义查询范围

@Test // 左右模糊匹配
void test04() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    /*
        这边的API,likeLeft和likeRight
            likeLeft:从指定字段的属性值的从右往左进行模糊查询,即 column like "%xx"
            likeRight:从指定字段的属性值的从左往右进行模糊查询,即 column like "xx%"
     */
    wrapper.likeRight("email", "t").likeLeft("name", "e");
    userMapper.selectMaps(wrapper).forEach(System.out::println);
}

套娃

@Test // 子查询
void test05() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.inSql("id", "select id from user where id < 3");
    userMapper.selectObjs(wrapper).forEach(System.out::println);
}

在这里插入图片描述

排序

@Test // 查询结果排序
void test06() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.orderByDesc("id");
    userMapper.selectObjs(wrapper).forEach(System.out::println);
}

代码自动生成器

让MybatisPlus自己写代码,dao,service,pojo,controller全部都是自动生成

public class HrcCode {
    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 配置策略
        // 1. 全局配置
        GlobalConfig gc = new GlobalConfig();
        String strProPath = System.getProperty("user.dir");
        gc.setOutputDir(strProPath + "/src/main/java");
        gc.setAuthor("小黄要成为一线程序员吖"); // 作者
        gc.setOpen(false); // 是否打开资源管理器
        gc.setFileOverride(false); // 是否覆盖
        mpg.setGlobalConfig(gc);

        // 2. 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDbType(DbType.MYSQL);
        dsc.setTypeConvert(new MySqlTypeConvert());
        dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 3. 包配置
        PackageConfig pc = new PackageConfig();
        pc.setEntity("entity");
        pc.setController("controller");
        pc.setMapper("mapper");
        pc.setModuleName("user");
        pc.setService("service");
        pc.setParent("com.hrc");
        mpg.setPackageInfo(pc);

        // 4. 策略配置
        StrategyConfig sc = new StrategyConfig();
        sc.setNaming(NamingStrategy.underline_to_camel);
        sc.setColumnNaming(NamingStrategy.underline_to_camel);
        sc.setEntityLombokModel(true); // 是否使用lombok
        sc.setLogicDeleteFieldName("deleted"); // 逻辑删除

        // 字段自动填充
        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
        TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
        List<TableFill> list = new ArrayList<>();
        list.add(gmtCreate);
        list.add(gmtUpdate);
        sc.setTableFillList(list);

        sc.setVersionFieldName("version"); // 乐观锁
        sc.setRestControllerStyle(true);
        sc.setControllerMappingHyphenStyle(true); // 是否为驼峰转连字符

        mpg.setStrategy(sc);
        mpg.execute(); // 执行
    }
}
underline_to_camel);
        sc.setColumnNaming(NamingStrategy.underline_to_camel);
        sc.setEntityLombokModel(true); // 是否使用lombok
        sc.setLogicDeleteFieldName("deleted"); // 逻辑删除

        // 字段自动填充
        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
        TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
        List<TableFill> list = new ArrayList<>();
        list.add(gmtCreate);
        list.add(gmtUpdate);
        sc.setTableFillList(list);

        sc.setVersionFieldName("version"); // 乐观锁
        sc.setRestControllerStyle(true);
        sc.setControllerMappingHyphenStyle(true); // 是否为驼峰转连字符

        mpg.setStrategy(sc);
        mpg.execute(); // 执行
    }
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页