Mybatis-Plus

概述

  • 学mybatis-plus之前,要学会Mybatis、Spring、SpringMVC
  • 已经有Mybatis了,为什么还要学习Mybatis-Plus呢?
  • Mybatis-Plus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成!即 偷懒
  • 官网:https://baomidou.com/

在这里插入图片描述
Mybatis本来简化jdbc的,Mybatis-Plus就是简化Mybatis的
类似于:JPA(Java持久层API)、tk-mapper、MybatisPlus
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

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

1、快速入门

在这里插入图片描述
官方地址:https://baomidou.com/pages/226c21/#初始化工程

使用第三方组件:

  1. 导入对应的依赖
  2. 研究依赖如何配置
  3. 代码如何编写
  4. 提高扩展技术能力

步骤

  1. 创建数据库mybatis_plus
  2. 创建表
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)
);
  1. 给表里添加数据
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');

在这里插入图片描述
真实开发中,version(乐观锁)、deleted(逻辑删除)、create_time、update_time

  1. 编写项目,初始化项目
  • 导入相关依赖
<!--数据库驱动-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己开发的,并非官方的 -->
<!--尽量不要同时导入mybatis和mybatis-plus,版本有差异-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.7.6</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>
  1. 连接数据库
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/数据库名?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  1. 传统方式,pojo-dao(连接mybatis、配置mapper.xml文件)-service-controller
  2. mybatis-plus
  • 编写pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private long id;
    private String name;
    private Integer age;
    private String email;
}
  • 编写mapper接口
// 如果我们在这没有@Mapper注解,我们就需要在主启动类里面加上@MapperScan("com.k2.dao"):包扫描 mapper接口的位置
@Mapper			// 表名这是一个mapper接口
public interface UserMapper extends BaseMapper<User> {
    // Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
    // 我们在这里继承BaseMapper、基础的CRUD,mybatis-plus已经帮我们写好了,直接使用即可
    // mybatis里面,写完接口,还需要写mapper.xml,写sql语句
}
  • 测试使用
@SpringBootTest
class MybatisPlus01HelloApplicationTests {
    // @Autowored:默认是按照类型装配注入的
	// @Resource:默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
    // 继承了BaseMapper,所有的方法都来自父类
    @Autowored
    UserMapper userMapper;

    @Test
    void contextLoads() {
        // UserMapper继承了BaseMapper,所以我们使用了mybatis-plus,我们就可以直接调用基础的CRUD使用
        // 参数是Wrapper,条件构造器
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }
}
  • 测试结果

在这里插入图片描述

2、配置日志

我们所有的sql语句都是不可见的,我们想知道它是怎么执行的(步骤),我们需要配置日志,让它输出出来

# 配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 这里也可以使用slf4jlog4j但是需要导入依赖包,我们在这里使用默认的
  • OK!我们控制台已经输出出来,我们就可以注意这个自动生成的sql

在这里插入图片描述

3、CRUD扩展

在这里插入图片描述

3.1、insert

@Test
public void insertUser(){
    User user = new User();
    user.setName("koen");
    user.setAge(18);
    user.setEmail("1034174907@qq.com");
    userMapper.insert(user);
    System.out.println(user);
}
  • 测试结果

在这里插入图片描述

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

雪花算法(Twitter的snowflake):ID_WORKER

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

3.2、delete

// 根据id删除记录
@Test
public void testDeleteById(){
    userMapper.deleteById(1597600799158972417L);
}

// 根据id批量删除
@Test
public void testDeleteBatchIds(){
	userMapper.deleteBatchIds(Arrays.asList(1597608667711131651L,1597608667711131653L,1597608667711131652L));
}

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

在这里插入图片描述

3.3、update

@Test
public void updateUser(){
    User user = new User();
    // 通过条件,自动调节动态sql
    user.setId(5L);
    user.setName("xiaokoen");
    // 调用的参数虽然是updateById,但是参数是一个对象
    userMapper.updateById(user);
}
  • 测试结果

在这里插入图片描述
通过条件,自动调节动态sql
在这里插入图片描述

3.4、select

1、基本查询操作

// 通过id查询用户
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

// 查询批量id
@Test
public void testSelectBatchIds(){
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);
}

// 多条件查询之一:使用map
@Test
public void testSelectByMap(){
    HashMap<String, Object> map = new HashMap<>();
    // 自定义查询
    map.put("name", "xiaokoen");
    map.put("age",16);

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

2、分页查询

分页在网站上使用非常多

  1. 原始使用limit进行分页
  2. 使用第三方插件pageHelper
  3. MP其实也内置了分页插件!

官网:https://baomidou.com/pages/97710a/#paginationinnerinterceptor

2.1、属性介绍
属性名类型默认值描述
overflowbooleanfalse溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法)
maxLimitLong单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法)
dbTypeDbType数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法)
dialectIDialect方言实现类(参见 插件#findIDialect 方法)

建议单一数据库类型的均设置 dbType

2.2、使用步骤
  1. 配置拦截器MybatisPlusInterceptor
@MapperScan("com.k2.mapper")        // 扫描我们的mapper文件
@EnableTransactionManagement        // 开启事务管理
@Configuration                      // 配置类
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 注册分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        return interceptor;
    }
}
  1. 直接使用Page对象
// 测试分页查询
@Test
public void testPage(){
    // 参数一:当前页
    // 参数二:页面大小
    IPage<User> page = new Page<>(2,5);
    page.getPages();        // 获取总页数
    page.getTotal();        // 获取数据总数
    page.getCurrent();      // 当前第几页
    page.getRecords();      // 获取分页后的数据
    IPage selectPage = userMapper.selectPage(page,null);
    page.getRecords().forEach(System.out::println);
}
  • 带条件的分页查询
@Test
public void testSelectPageByName() {
    // 参数(1):当前页    参数(2):每页显示行数
    IPage<User> page = new Page<User>(1, 3);
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.like(User::getName,"张");
    IPage<User> selectPage = userMapper.selectPage(page, lambdaQueryWrapper);
    log.info("总条数:{}", selectPage.getTotal());
    selectPage.getRecords().forEach(user -> {
        log.info("user ==> {}", user);
    });
}

4、条件构造器Wrapper

官网https://baomidou.com/pages/10c804/
Wrapper
我们写一些复杂的sql就可以使用它来替代

警告:

不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输

  1. wrapper 很重
  2. 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
  3. 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
  4. 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr

为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
在这里插入图片描述

我们在这里测试几个:注意观察sql语句

  1. 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于15岁
@Test
public void test1() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper
            .isNotNull("name")
            .isNotNull("email")
            .ge("age", 15);
    userMapper.selectList(wrapper);
}

在这里插入图片描述
2. 查询名字koen(selectOne),出现多个相同名字,不要使用selectOne

@Test
public void test2() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name", "koen");
    userMapper.selectOne(wrapper);
}

在这里插入图片描述

5、DQL条件查询

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

5.1、常规格式

  • 条件查询1:常规格式(硬编码),age > 10 and age < 20
@Test
public void testFind01() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
    queryWrapper.gt("age", 10);
    queryWrapper.lt("age", 20);
    // queryWrapper.gt("age",10).lt("age",20);       链式编程
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(user -> {
        log.info("【user】 == >{}", user);
    });
}

在这里插入图片描述

5.2、Lambda格式

  • 条件查询2:lambda方式,age > 20 and age < 30
@Test
public void testFind02() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
    queryWrapper.lambda().gt(User::getAge, 20).lt(User::getAge, 30);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(user -> {
        log.info("【user】 == >{}", user);
    });
}

在这里插入图片描述

  • 条件查询3:lambda方式(lambdaQueryWrapper),age > 10 and age < 20
@Test
public void testFind03() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    lambdaQueryWrapper.gt(User::getAge, 10).lt(User::getAge, 20);
    List<User> userList = userMapper.selectList(lambdaQueryWrapper);
    userList.forEach(user -> log.info("【user】 ==>{}", user));
}
  • 条件查询4(组合条件查询):or:或
@Test
public void testFind04() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    lambdaQueryWrapper.gt(User::getAge, 10).or().lt(User::getAge, 20);
    List<User> userList = userMapper.selectList(lambdaQueryWrapper);
    userList.forEach(user -> log.info("【user】 ==>{}", user));
}

在这里插入图片描述

  • 查询投影:只返回表中的特定字段
@Test
public void testFind05() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    // select name,password from user
    lambdaQueryWrapper.select(User::getName, User::getPassword);
    List<User> userList = userMapper.selectList(lambdaQueryWrapper);
    userList.forEach(user -> log.info("【user】 ==>{}", user));
}

在这里插入图片描述

  • 查询投影:查询结果包含模型类中未定义的属性
@Test
public void testFind06() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // select count(*) as count from user
    queryWrapper.select("count(*) as count");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    log.info("【user】 ==>{}", maps);
}

在这里插入图片描述

  • 范围查询( > 、 = 、 between)
@Test
public void testFind07() {
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>();
    // 大于:>
    // queryWrapper.gt(User::getAge, 20);
    // 介于...之间:between
    queryWrapper.between(User::getAge, 18, 30);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(user -> {
        log.info("【user】 == >{}", user);
    });
}

在这里插入图片描述

  • 模糊查询(like)
@Test
public void testFind08() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    lambdaQueryWrapper.like(User::getName, "k");
    List<User> userList = userMapper.selectList(lambdaQueryWrapper);
    userList.forEach(user -> log.info("【user】 ==>{}", user));
}

在这里插入图片描述

lambdaQueryWrapper.likeLeft(User::getName, "k");		// %在左边
lambdaQueryWrapper.likeRight(User::getName, "k");		// %在右边
  • 包涵性匹配(in)
@Test
public void testFind09() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    // lambdaQueryWrapper.in(User::getAge, 8, 12, 16);
    lambdaQueryWrapper.in(User::getAge, Arrays.asList(8, 12, 16));
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    users.forEach(user -> log.info("【user】 ==>{}", user));
}

在这里插入图片描述

  • 内嵌子查询(id在子查询中查出来)
@Test
public void test5() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
    queryWrapper.inSql("id", "select id from user where id < 3");
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    users.forEach(user -> log.info("【user】 ==>{}", user));
}

在这里插入图片描述

  • 分组查询(group by)
@Test
public void testFind10() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.select(User::getAge);
    lambdaQueryWrapper.groupBy(User::getAge);
    List<User> userList = userMapper.selectList(lambdaQueryWrapper);
    userList.forEach(user -> log.info("【user】 ==>{}", user));
}

在这里插入图片描述

  • 排序查询(order by)
@Test
public void testFind11() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    // 参数1:是否排序     参数2:true:正序/false:倒叙        参数3:按照什么排序
    lambdaQueryWrapper.orderBy(true, false, User::getAge);
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    users.forEach(user -> {
        log.info("【user】 ==>{}", user);
    });
}

在这里插入图片描述

  • orderByAsc:正序
@Test
public void testFind12() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    // 参数1:是否正序排序     参数2:按照什么排序
    lambdaQueryWrapper.orderByAsc(true, User::getAge);
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    users.forEach(user -> {
        log.info("【user】 ==>{}", user);
    });
}
  • orderByDesc:倒叙
@Test
public void testFind13() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
    // 参数1:是否倒叙排序     参数2:按照什么排序
    lambdaQueryWrapper.orderByDesc(true,  User::getAge);
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    users.forEach(user -> {
        log.info("【user】 ==>{}", user);
    });
}

6、实际开发中常遇到的问题

6.1、表字段与编码属性设计不同步

在这里插入图片描述

  • 属性注解:@TableField
  • 位置:定义在属性上
  • 作用:设置当前属性对应的数据库表中的字段关系
  • 相关属性
    • value(默认):设置数据库表字段名称

6.2、编码中添加了数据库中未定义的属性

在这里插入图片描述

  • 属性注解:@TableField
  • 位置:定义在属性上
  • 作用:设置当前属性对应的数据库表中的字段关系
  • 相关属性
    • value:设置数据库表字段名称
    • exist:设置属性在数据库表字段中是否存在,默认为true,此属性无法与value合并使用

6.3、采用默认查询开放了更多的字段查看权限

我们查询用户表的时候,一般不会查询 密码 字段
在这里插入图片描述

  • 属性注解:@TableField
  • 位置:定义在属性上
  • 作用:设置当前属性对应的数据库表中的字段关系
  • 相关属性
    • value:设置数据库表字段名称
    • exist:设置属性在数据库表字段中是否存在,默认为true,此属性无法与value合并使用
    • select:设置属性是否参与查询,默认是true,此属性与select()映射配置不冲突

6.4、表名与编码开发设计不同步

在这里插入图片描述

  • 类注解:@TableName
  • 位置:定义在类上方
  • 作用:设置当前类对应于数据库表关系
  • 相关属性
    • value:设置数据库表名称

7、DML编程控制

7.1、主键生成策略(Insert)

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

  • AUTO(0):使用数据库id自增策略控制id生成
  • NONE(1):不设置id生成策略
  • INPUT(2):用户手工输入id
  • ASSIGN_ID(3):雪花算法生成id(可兼容数值型与字符串型)
  • ASSIGN_UUID(4):以UUID生成算法作为id生成策略(对数据库性能有影响)

主键源码解析

在这里插入图片描述

  • 属性注解:@TableId
  • 位置:定义在属性上方
  • 作用:设置当前类中主键属性的生成策略
  • 相关属性
    • value:设置数据库主键名称
    • type:设置主键属性的生成策略,值参照IdType枚举值

主键自增:AUTO(0)

我们需要配置主键自增:

  1. 实体类字段上 @TableId(type = IdType.AUTO):一定要写,否则还是默认的
  2. 数据库字段要设置成自增!

在这里插入图片描述

默认 ASSIGN_ID:全局唯一ID

雪花算法(Twitter的snowflake):ID_WORKER

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

7.2、主键策略全局配置

在这里插入图片描述

mybatis-plus:
  global-config:
    db-config:
      id-type: auto
      table-prefix: ta1_

7.3、逻辑删除(Delete)

官网https://baomidou.com/pages/6b03c5/

说明:

只对自动注入的 sql 起效:

  • 插入: 不作限制
  • 查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
  • 更新: 追加 where 条件防止更新到已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
  • 删除: 转变为 更新

物理删除:在数据库中直接移除

逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效!

​ 由deleted=0 ==> deleted=1

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

1、代码测试

  1. 在数据表中增加一个deleted字段

在这里插入图片描述

  1. 同步实体类
@TableLogic     // 逻辑删除
private Integer deleted;
  1. 配置yaml
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted    # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1       # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0   # 逻辑未删除值(默认为 0)
  1. 高版本不用手动配置,只需要添加注解就行 @TableLogic
  • 测试结果
    在这里插入图片描述
  • 测试后变为:记录依旧还在,只是值已经变化
    在这里插入图片描述
  • 它执行的并不是删除操作,只是一个更新操作
    在这里插入图片描述
  • 我们再查询id=1的用户发现已经查询不到了:查询的时候自动过滤
    在这里插入图片描述

7.4、自动填充(create_time、update_time)

官网https://baomidou.com/pages/4c6bcf/

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

  • 所有的数据库表:gmt_create、gmt_modified几乎所有的表都要配置上!而且需要自动化

1、方式一:数据库级别

工作中不可以修改数据库(牢记)

  1. 在表中添加字段:create_time、update_time

默认是以时间戳方式

在这里插入图片描述

  1. 同步实体类
private LocalDateTime createTime;
private LocalDateTime updateTime;
  • 测试结果

再次运行修改操作,虽然我们没有修改时间,但是他们会自动帮我们修改

在这里插入图片描述

在这里插入图片描述

2、方式二:代码级别(常用)

在这里插入图片描述

  1. 删除数据库的默认值

在这里插入图片描述

  1. 实体类字段属性上需要增加注解(可以看源码)
// 字段自动填充策略
@TableField(fill = FieldFill.INSERT)    // 插入时更新字段
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和修改时更新字段
private Date updateTime;
  1. 创建处理器处理注解
@Slf4j
@Component      // 不要忘记把处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill-----");
        LocalDateTime now = LocalDateTime.now();
        // 方式一
        this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,now);
        this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,now);
        
		// 方式二
        // 添加时间
        metaObject.setValue("createTime", now);
        // 修改时间
        metaObject.setValue("updateTime", now);
    }

    // 更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill-----");
        LocalDateTime now = LocalDateTime.now();
		this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,now);
        // 修改时间
        metaObject.setValue("updateTime", now);
    }
}
  • 测试结果

在这里插入图片描述

7.5、乐观锁(Update)

官网https://baomidou.com/pages/0d93c0/

面试过程中,经常会被问到乐观锁、悲观锁

乐观锁:故名思意十分乐观,它总会认为不会出现问题,无论干什么都不会上锁,如果出现问题,再次更新测试

悲观锁:故名思意十分悲观,它总会认为会出现问题,无论干什么都会上锁,再操作

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

乐观锁实现方式:

  • 增加版本号,数据在提交前,进行版本检测,如果提交时和取出数据时的版本一致,才可以提交。
  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

测试MP的乐观锁插件

  1. 给数据库增加version字段,默认为1

在这里插入图片描述

  1. 同步实体类
@Version    // 乐观锁的注解
private Integer version;
  1. 配置拦截器(注册组件)
@MapperScan("com.k2.mapper")        // 扫描我们的mapper文件
@EnableTransactionManagement        // 开启事务管理
@Configuration                      // 配置类
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 注册乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
}
  1. 模拟乐观锁成功状态
// 模拟乐观锁成功状态
@Test
public void testLock(){
    User user = userMapper.selectById(1L);
    user.setName("koen小");
    user.setEmail("k2study@163.com");
    userMapper.updateById(user);
}

在这里插入图片描述

  1. 模拟乐观锁失败状态
// 模拟乐观锁失败状态
@Test
public void testLock2(){
    // 线程1
    User user = userMapper.selectById(1L);		// version1 = oldVersion
    user.setName("koen小");
    user.setEmail("k2study@163.com");
    // 模拟线程2 突然来了,线程1 user获取到了,但是还没有修改
    User user2 = userMapper.selectById(1L);		// version2 = oldVersion
    user2.setName("koen小");
    user2.setEmail("k2study@163.com");
    userMapper.updateById(user2);				// version2 = newVersion
    // 自旋锁来多次尝试提交(JUC里面学)
    // 如果没有乐观锁就会覆盖插队线程的值
    userMapper.updateById(user);    			// version1 = newVersion
}

在这里插入图片描述

自旋锁来多次尝试提交(JUC里面学)

  • 测试结果

在这里插入图片描述

8、三层封装

8.1、Mapper封装

Mybatis-Plus 为了开发更加快捷,对持久层也进行了封装,直接提供了相关的接口。我们在进行持久层开发时,可以继承它提供的接口,使得编码更加高效

// 如果我们在这没有@Mapper注解,我们就需要在主启动类里面加上@MapperScan("com.k2.dao"):包扫描 mapper接口的位置
@Mapper			// 表名这是一个mapper接口
public interface UserMapper extends BaseMapper<User> {
    // Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
    // 我们在这里继承BaseMapper、基础的CRUD,mybatis-plus已经帮我们写好了,直接使用即可
    // mybatis里面,写完接口,还需要写mapper.xml,写sql语句
}

8.2、Service封装

Mybatis-Plus 为了开发更加快捷,对业务层也进行了封装,直接提供了相关的接口和实现类。我们在进行业务层
开发时,可以继承它提供的接口和实现类,使得编码更加高效

  1. 定义Service接口,并继承Iservice

  2. 定义实现类,并继承ServiceImpl<Mapper,>之后,再实现定义的接口(注意:此处使用的泛型即是我们的Entity实体类)

public interface UserService extends IService<User>{}
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}

9、性能分析插件(执行SQL分析打印)

  1. 导入依赖
<!-- 导入数据库驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己开发的,并非官方的 -->
<!--尽量不要同时导入mybatis和mybatis-plus,版本有差异-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

<!-- 执行SQL分析打印 -->
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>
  1. 修改yaml配置文件

我们要注意,在这主要修改的是:driver-class-nameurl

spring:
  # 数据库连接配置
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
  profiles:
    active: dev
# 原始的    
#    url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
#    driver-class-name: com.mysql.cj.jdbc.Driver
#    username: root
#    password: 123456
  1. 添加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
  • 查询结果

在这里插入图片描述
但是也是非常耗时的,不建议使用

10、代码生成器

官网https://baomidou.com/pages/981406

自动生成:dao、pojo、service、controller、mapper映射

10.1、方式一:3.5.1版本及其以上版本

  1. 导入依赖,整合mybatis-plus的jar
<!--添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认):代码生成器需要-->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>
<!--代码生成器 3.5.1版本及其以上版本-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3</version>
</dependency>

<!--数据库驱动-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<!--mp依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>

<!-- 执行SQL分析打印(数据源) -->
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--swagger-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-common</artifactId>
    <version>3.0.0</version>
</dependency>
  1. 改服务端口、运行环境 、连接数据库
spring:
  # 数据库连接配置
  datasource:        
    url: jdbc:mysql://localhost:3306/数据库名?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    
    # driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    # url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
    # username: root
    # password: 123456
    
  profiles:
    active: dev

# 配置日志
mybatis-plus:
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:    # 指定mybatis全局配置文件中相关配置(就是mybatis-config.xml)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # 开启默认日志输出

  global-config:
    db-config:
      logic-delete-field: flag    # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1       # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0   # 逻辑未删除值(默认为 0)
# 服务端口(可以按自己的配置)
server:
  port: 9000
  1. 创建自动生成器类
package com.k2;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.fill.Column;

import java.util.Collections;

// 代码生成器
public class KoenCode {

        private final static String URL = "jdbc:mysql://localhost:3306/数据库名?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8";
        private final static String USERNAME = "root";
        private final static String PASSWORD = "123456";
    
    public static void main(String[] args) {
		String projectPath = System.getProperty("user.dir");
        System.out.println(projectPath);
        FastAutoGenerator.create(URL, USERNAME, PASSWORD)
                // 全局配置
                .globalConfig(builder -> {
                    builder.author("xiaokoen") // 设置作者
                            .outputDir(projectPath + "/src/main/java/") // 指定输出目录
                            .enableSwagger() // 开启 swagger2 模式
                            .fileOverride() // 覆盖已生成文件(默认为false)
                            .disableOpenDir()       // 禁止打开输出目录,默认为true
                            .dateType(DateType.ONLY_DATE)   // 时间策略(默认DateType.TIME_PACK)
                            .commentDate("yyyy-MM-dd")       // 注释日期(默认值: yyyy-MM-dd)
                    ;
                })
                // 包配置
                .packageConfig(builder -> {
                    builder.parent("com.k2") // 设置父包名
//                            .moduleName("blog") // 设置父包模块名
                            .entity("pojo")
                            .mapper("mapper")
                            .service("service")
//                            .serviceImpl("service.impl")  // 默认
                            .controller("controller")
//                            .xml("mapper.xml")        // 默认
                            .pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mapper")); // 设置mapperXml生成路径
                })
                // 策略配置
                .strategyConfig(builder -> {
                    builder.addInclude("t_user")      // 设置需要生成的表名
                            .addTablePrefix("t_", "c_")// 设置过滤表前缀
                            // 实体类策略
                            .entityBuilder()
                            .enableLombok()         // 开启lombok
                            .enableTableFieldAnnotation()   // 开启生成实体时,生成字段注解
                            .naming(NamingStrategy.underline_to_camel)      // 下划线转驼峰命(默认)
                            .columnNaming(NamingStrategy.underline_to_camel)        // 下划线转驼峰命
                            // 乐观锁
                            .versionColumnName("version")        // 乐观锁字段名(数据库)
                            .versionPropertyName("version")      // 乐观锁属性名(实体)
                            // 逻辑删除
                            .logicDeleteColumnName("deleted")    // 逻辑删除字段名(数据库)
                            .logicDeletePropertyName("deleted")   // 逻辑删除属性名(实体)
                            // 自动填充配置
                            .addTableFills(new Column("create_time", FieldFill.INSERT), new Column("update_time", FieldFill.INSERT_UPDATE))
//                            .addTableFills(new Property("create_time",FieldFill.INSERT),new Property("update_time",FieldFill.INSERT_UPDATE))
//                            .formatFileName("%sEntity")         // 格式化文件名称
                            .idType(IdType.AUTO)       // 全局主键类型
                            // service 策略
                            .serviceBuilder()
                            .formatServiceFileName("%sService")
                            .formatServiceImplFileName("%sServiceImpl")
                            // controller 策略
                            .controllerBuilder()
                            .enableHyphenStyle()        // 开启驼峰转连字符
                            .enableRestStyle()          // 开启生成@RestController 控制器
                    ;
                })
//                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}
  • 测试结果

在这里插入图片描述

  • 运行过程中,遇到版本不一致问题
    在这里插入图片描述
  • 解决方案:我们只需要重新build项目即可(两个都可以试试)
    在这里插入图片描述

10.2、方式二:3.5.1以下版本

  1. 导入依赖,整合mybatis-plus的jar
<!--代码生成器3.5.1版本以下-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<!--添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认):代码生成器需要-->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

<!--数据库驱动-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<!--mp依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
  1. 改服务端口、运行环境 、连接数据库
spring:
  # 数据库连接配置
  datasource:        
    url: jdbc:mysql://localhost:3306/数据库名?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    
    # driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    # url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
    # username: root
    # password: 123456
    
  profiles:
    active: dev

# 配置日志
mybatis-plus:
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:    # 指定mybatis全局配置文件中相关配置(就是mybatis-config.xml)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # 开启默认日志输出

  global-config:
    db-config:
      logic-delete-field: flag    # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1       # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0   # 逻辑未删除值(默认为 0)
# 服务端口(可以按自己的配置)
server:
  port: 9000
  1. 创建自动生成器类
public class GeneratorUtils {

    private final static String DB_URL = "jdbc:mysql://localhost/mybatisplus_db?serverTimezone=UTC";
    private final static String USER_NAME = "root";
    private final static String PASSWORD = "123456";
    private final static String AUTHOR = "koen";
    private final static String DRIVE = "com.mysql.cj.jdbc.Driver";


    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java/test");
        gc.setAuthor(AUTHOR);
        gc.setOpen(false);
        gc.setControllerName("%sController");
        gc.setServiceName("I%sService");
        gc.setServiceImplName("%sServiceImpl");
        gc.setMapperName("%sMapper");
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        // dsc.setSchemaName("public");
        dsc.setUrl(DB_URL);
        dsc.setDriverName(DRIVE);
        dsc.setUsername(USER_NAME);
        dsc.setPassword(PASSWORD);
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.koen");
        pc.setEntity("pojo");
        pc.setMapper("mapper");
        pc.setController("controller");
        pc.setService("service");
        mpg.setPackageInfo(pc);

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

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

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            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);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 要生成的表
        strategy.setInclude("tbl_user");
        strategy.setTablePrefix("tbl_");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setLogicDeleteFieldName("deleted");
        strategy.setVersionFieldName("version");
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.execute();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_koen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值