MyBatisPlus详细介绍

 
MybatisPlus 入门
在后台系统服务的开发过程中,必然要和数据库进行交互,在本套课程中, ORM 这一层的技术选型,我们采用
Mybatis 框架作为持久层框架,原因是 Mybatis SQL 语句编写更加的灵活。
为了提升开发的效率,所以选用 MybatisPlus 作为 mybatis 的插件,以提升开发的效率。下面我们来学习下
MybatisPlus 插件的使用。
 
、简介
MyBatis-Plus (简称 MP )是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提
高效率而生。
 
特性
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响
损耗小 :启动即会自动注入基本 CURD ,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作 :内置通用 Mapper 、通用 Service ,仅仅通过少量配置即可实现单表大部分 CRUD
作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用 :通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持多种数据库 :支持 MySQL MariaDB Oracle DB2 H2 HSQL SQLite Postgre
SQLServer2005 SQLServer 等多种数据库
支持主键自动生成 :支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence ),可自由配置,完美
解决主键问题
支持 XML 热加载 Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
支持 ActiveRecord 模式 :支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD
操作
支持自定义全局通用操作 :支持全局通用方法注入( Write once, use anywhere
支持关键词自动转义 :支持数据库关键词( order key...... )自动转义,还可自定义关键词
内置代码生成器 :采用代码或者 Maven 插件可快速生成 Mapper Model Service Controller 层代
码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件 :基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通
List 查询
 
内置性能分析插件 :可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件 :提供全表 delete update 操作智能分析阻断,也可自定义拦截规则,预防误操作
内置 Sql 注入剥离器 :支持 Sql 注入剥离,有效预防 Sql 注入攻击
 
架构图(官网)
快速入门
2.4.1 、创建表
首先,创建数据库
数据表
#创建用户表
CREATE TABLE user (
    id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',
    name VARCHAR(30) DEFAULT NULL COMMENT '姓名',
    age INT(11) DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
    manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id',
    create_time DATETIME DEFAULT NULL COMMENT '创建时间',
    CONSTRAINT manager_fk FOREIGN KEY (manager_id)
        REFERENCES user (id)
)  ENGINE=INNODB CHARSET=UTF8;

#初始化数据:
INSERT INTO user (id, name, age, email, manager_id
    , create_time)
VALUES (1087982257332887553, '大boss', 40, 'boss@baomidou.com', NULL
        , '2019-01-11 14:20:20'),
    (1088248166370832385, '王天风', 25, 'wtf@baomidou.com', 1087982257332887553
        , '2019-02-05 11:12:22'),
    (1088250446457389058, '李艺伟', 28, 'lyw@baomidou.com', 1088248166370832385
        , '2019-02-14 08:31:16'),
    (1094590409767661570, '张雨琪', 31, 'zjq@baomidou.com', 1088248166370832385
        , '2019-01-14 09:15:15'),
    (1094592041087729666, '刘红雨', 32, 'lhm@baomidou.com', 1088248166370832385
        , '2019-01-14 09:48:16');

 创建工程

加入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/test?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
logging:
  level:
    root: warn
    org.ywb.demo.dao: trace
  pattern:
    console: '%p%m%n'

 pojo 

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private String managerId;
    private LocalDateTime createTime;
}
 
  1. 在dao包中创建mapper接口,并集成mybatisPlus的BaseMapper

 

public interface UserMapper extends BaseMapper<User> {

}
  1. 在springboot启动类添加@MapperScan扫描dao层接口

 

@MapperScan("****.dao")
@SpringBootApplication
public class MybatisPlusDemoApplication {

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

}


测试类

 

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

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

}
可以看到,通过简单的一些代码的编写即可实现数据的查询操作。
2.5 BaseMapper
MybatisPlus 中, BaseMapper 中定义了一些常用的 CRUD 方法,当我们自定义的 Mapper 接口继承 BaseMapper
后即可拥有了这些方法。
需要说明的是:这些方法仅适合单表操作。
/**
* <p>
* Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得 CRUD 功能
* </p>
* <p>
* 这个 Mapper 支持 id 泛型
* </p>
 
*/
public interface BaseMapper < T > {
/**
* <p>
* 插入一条记录
* </p>
*
* @param entity 实体对象
*/
int insert ( T entity );
/**
* <p>
* 根据 ID 删除
* </p>
*
* @param id 主键 ID
*/
int deleteById ( Serializable id );
/**
* <p>
* 根据 columnMap 条件,删除记录
* </p>
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap ( @Param ( Constants . COLUMN_MAP ) Map < String , Object > columnMap );
/**
* <p>
* 根据 entity 条件,删除记录
* </p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null
*/
int delete ( @Param ( Constants . WRAPPER ) Wrapper < T > queryWrapper );
/**
* <p>
* 删除(根据 ID 批量删除)
* </p>
 
 
*
* @param idList 主键 ID 列表 ( 不能为 null 以及 empty)
*/
int deleteBatchIds ( @Param ( Constants . COLLECTION ) Collection <? extends
Serializable > idList );
/**
* <p>
* 根据 ID 修改
* </p>
*
* @param entity 实体对象
*/
int updateById ( @Param ( Constants . ENTITY ) T entity );
/**
* <p>
* 根据 whereEntity 条件,更新记录
* </p>
*
* @param entity 实体对象 (set 条件值 , 不能为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null, 里面的 entity 用于生成 where
语句)
*/
int update ( @Param ( Constants . ENTITY ) T entity , @Param ( Constants . WRAPPER )
Wrapper < T > updateWrapper );
/**
* <p>
* 根据 ID 查询
* </p>
*
* @param id 主键 ID
*/
T selectById ( Serializable id );
/**
* <p>
* 查询(根据 ID 批量查询)
* </p>
*
* @param idList 主键 ID 列表 ( 不能为 null 以及 empty)
*/
List < T > selectBatchIds ( @Param ( Constants . COLLECTION ) Collection <? extends
Serializable > idList );
/**
* <p>
* 查询(根据 columnMap 条件)
* </p>
*
* @param columnMap 表字段 map 对象

 

List < T > selectByMap ( @Param ( Constants . COLUMN_MAP ) Map < String , Object >
columnMap );
/**
* <p>
* 根据 entity 条件,查询一条记录
* </p>
*
* @param queryWrapper 实体对象
*/
T selectOne ( @Param ( Constants . WRAPPER ) Wrapper < T > queryWrapper );
/**
* <p>
* 根据 Wrapper 条件,查询总记录数
* </p>
*
* @param queryWrapper 实体对象
*/
Integer selectCount ( @Param ( Constants . WRAPPER ) Wrapper < T > queryWrapper );
/**
* <p>
* 根据 entity 条件,查询全部记录
* </p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null
*/
List < T > selectList ( @Param ( Constants . WRAPPER ) Wrapper < T > queryWrapper );
/**
* <p>
* 根据 Wrapper 条件,查询全部记录
* </p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null
*/
List < Map < String , Object >> selectMaps ( @Param ( Constants . WRAPPER ) Wrapper < T >
queryWrapper );
/**
* <p>
* 根据 Wrapper 条件,查询全部记录
* 注意: 只返回第一个字段的值
* </p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null
*/
List < Object > selectObjs ( @Param ( Constants . WRAPPER ) Wrapper < T > queryWrapper );
/**
* <p>
* 根据 entity 条件,查询全部记录(并翻页)
 
 
2.5.1 like 查询
@Test
public void testSelectByLike (){
QueryWrapper queryWrapper = new QueryWrapper ( new User ());
// 查询名字中包含 “o” 的用户
queryWrapper . like ( "name" , "o" );
 
 
2.5.2 、条件查询
* </p>
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT
* @param queryWrapper 实体对象封装操作类(可以为 null
*/
IPage < T > selectPage ( IPage < T > page , @Param ( Constants . WRAPPER ) Wrapper < T >
queryWrapper );
/**
* <p>
* 根据 Wrapper 条件,查询全部记录(并翻页)
* </p>
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
IPage < Map < String , Object >> selectMapsPage ( IPage < T > page ,
@Param ( Constants . WRAPPER ) Wrapper < T > queryWrapper );
}
 
@Test
public void testSelectByLe (){
QueryWrapper queryWrapper = new QueryWrapper ( new User ());
// 查询年龄小于等于 20 的用户
queryWrapper . le ( "age" , 20 );
List < User > users = this . userMapper . selectList ( queryWrapper );
for ( User user : users ) {
System . out . println ( user );
}
}
 
 

常用注解

MyBatisPlus提供了一些注解供我们在实体类和表信息出现不对应的时候使用。通过使用注解完成逻辑上匹配。

注解名称说明
@TableName实体类的类名和数据库表名不一致
@TableId实体类的主键名称和表中主键名称不一致
@TableField实体类中的成员名称和表中字段名称不一致

 

@Data
@TableName("t_user")
public class User {
    @TableId("user_id")
    private Long id;
    @TableField("real_name")
    private String name;
    private Integer age;
    private String email;
    private Long managerId;
    private LocalDateTime createTime;
}



 

排除实体类中非表字段

  1. 使用transient关键字修饰非表字段,但是被transient修饰后,无法进行序列化。
  2. 使用static关键字,因为我们使用的是lombok框架生成的get/set方法,所以对于静态变量,我们需要手动生成get/set方法。
  3. 使用@TableField(exist = false)注解

插入一条记录测试:

 

  @Test
    public void insert(){
        User user = new User();
        user.setAge(31);
        user.setManagerId(1088250446457389058L);
        user.setCreateTime(LocalDateTime.now());
        int insert = userMapper.insert(user);
        System.out.println("影像记录数:"+insert);
    }

条件构造器查询

除了BaseMapper中提供简单的增删改查方法之外,还提供了很多关于区间查询,多表连接查询,分组等等查询功能,实现的类图如下所示:

 
 

通过观察类图可知,我们需要这些功能时,只需要创建QueryWrapper对象即可。

 

  1. 模糊查询

 

/**
     * 查询名字中包含'雨'并且年龄小于40
     * where name like '%雨%' and age < 40
     */
    @Test
    public void selectByWrapper(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name","雨").lt("age",40);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

 

  1. 嵌套查询

 

    /**
     * 创建日期为2019年2月14日并且直属上级姓名为王姓
     * date_format(create_time,'%Y-%m-%d') and manager_id in (select id from user where name like '王%')
     */
    @Test
    public void selectByWrapper2(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.apply("date_format(create_time,'%Y-%m-%d')={0}","2019-02-14")
                .inSql("manager_id","select id from user where name like '王%'");
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

注意
上面的日期查询使用的是占位符的形式进行查询,目的就是为了防止SQL注入的风险。
apply方法的源码

 

 

   /**
     * 拼接 sql
     * <p>!! 会有 sql 注入风险 !!</p>
     * <p>例1: apply("id = 1")</p>
     * <p>例2: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")</p>
     * <p>例3: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", LocalDate.now())</p>
     *
     * @param condition 执行条件
     * @return children
     */
    Children apply(boolean condition, String applySql, Object... value);

SQL 注入的例子:

queryWrapper.apply("date_format(create_time,'%Y-%m-%d')=2019-02-14 or true=true")
              .inSql("manager_id","select id from user where name like '王%'");
  1. and & or

 

  /**
     * 名字为王姓,(年龄小于40或者邮箱不为空)
     */
    @Test
    public void selectByWrapper3(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.likeRight("name","王").and(wq-> wq.lt("age",40).or().isNotNull("email"));

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

    }
  1. between & and

 

   /**
     * 名字为王姓,(年龄小于40,并且年龄大于20,并且邮箱不为空)
     */
    @Test
    public void selectWrapper4(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.likeRight("name", "王").and(wq -> wq.between("age", 20, 40).and(wqq -> wqq.isNotNull("email")));
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }
  1. nested

 

   /**
     * (年龄小于40或者邮箱不为空)并且名字为王姓
     * (age<40 or email is not null)and name like '王%'
     */
    @Test
    public void selectWrapper5(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        queryWrapper.nested(wq->wq.lt("age",40).or().isNotNull("email")).likeRight("name","王");

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

 

   /**
     * 年龄为30,31,35,34的员工
     */
    @Test
    public void selectWrapper6(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        queryWrapper.in("age", Arrays.asList(30,31,34,35));

        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }
  1. last 有SQL注入的风险!!!

 

  /**
     * 无视优化规则直接拼接到 sql 的最后(有sql注入的风险,请谨慎使用)
     * <p>例: last("limit 1")</p>
     * <p>注意只能调用一次,多次调用以最后一次为准</p>
     *
     * @param condition 执行条件
     * @param lastSql   sql语句
     * @return children
     */
    Children last(boolean condition, String lastSql);
   /**
     * 只返回满足条件的一条语句即可
     * limit 1
     */
    @Test
    public void selectWrapper7(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        queryWrapper.in("age", Arrays.asList(30,31,34,35)).last("limit 1");

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

 

 

  1. 查询指定部分列

 

    /**
     * 查找为王姓的员工的姓名和年龄
     */
    @Test
    public void selectWrapper8(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name","age").likeRight("name","王");
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }
  1. 使用过滤器查询指定列

 

   /**
     * 查询所有员工信息除了创建时间和员工ID列
     */
    @Test
    public void selectWrapper9(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select(User.class,info->!info.getColumn().equals("create_time")
                &&!info.getColumn().equals("manager_id"));
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

condition 的作用

在我们调用的查询语句中,通过查看源码(这里以apply方法为例)可以看出,每个查询方法的第一个参数都是boolean类型的参数,重载方法中默认给我们传入的都是true。

 

 default Children apply(String applySql, Object... value) {
        return apply(true, applySql, value);
    }
    Children apply(boolean condition, String applySql, Object... value);

这个condition的作用是为true时,执行其中的SQL条件,为false时,忽略设置的SQL条件。

实体作为条件构造方法的参数

在web开发中,controller层常常会传递给我们一个用户的对象,比如通过用户姓名和用户年龄查询用户列表。
我们可以将传递过来的对象直接以构造参数的形式传递给QueryWrapper,MyBatisPlus会自动根据实体对象中的属性自动构建相应查询的SQL语句。

 @Test
    public void selectWrapper10(){
        User user = new User();
        user.setName("刘红雨");
        user.setAge(32);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

如果想通过对象中某些属性进行模糊查询,我们可以在跟数据库表对应的实体类中相应的属性标注注解即可。
比如我们想通过姓名进行模糊查询用户列表。

 

@TableField(condition = SqlCondition.LIKE)
    private String name;

 

 @Test
    public void selectWrapper10(){
        User user = new User();
        user.setName("红");
        user.setAge(32);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

Lambda条件构造器

MybatisPlus提供了4种方式创建lambda条件构造器,前三种分别是这样的

 

        LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda();
        LambdaQueryWrapper<User> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
        LambdaQueryWrapper<User> lambdaQueryWrapper2 = Wrappers.lambdaQuery();
  1. 查询名字中包含‘雨’并且年龄小于40的员工信息

 

    @Test
    public void lambdaSelect(){
        LambdaQueryWrapper<User> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.like(User::getName,"雨").lt(User::getAge,40);

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

image.png

QueryWrapper类已经提供了很强大的功能,而lambda条件构造器做的和QueryWrapper的事也是相同的为什么要冗余的存在lambda条件构造器呢?
QueryWrapper是通过自己写表中相应的属性进行构造where条件的,容易发生拼写错误,在编译时不会报错,只有运行时才会报错,而lambda条件构造器是通过调用实体类中的方法,如果方法名称写错,直接进行报错,所以lambda的纠错功能比QueryWrapper要提前很多。
举个例子:
查找姓名中包含“雨”字的员工信息。
使用QueryWrapper

queryWrapper.like("name","雨");

使用lambda

lambdaQueryWrapper.like(User::getName,"雨");

如果在拼写name的时候不小心,写成了naem,程序并不会报错,但是如果把方法名写成了getNaem程序立即报错。

第四种lambda构造器
细心的人都会发现无论是之前的lambda构造器还是queryWrapper,每次编写完条件构造语句后都要将对象传递给mapper 的selectList方法,比较麻烦,MyBatisPlus提供了第四种函数式编程方式,不用每次都传。

  1. 查询名字中包含“雨”字的,并且年龄大于20的员工信息

 

    @Test
    public void lambdaSelect(){
        List<User> userList = new LambdaQueryChainWrapper<>(userMapper).like(User::getName, "雨").ge(User::getAge, 20).list();
        userList.forEach(System.out::println);
    }

image.png

自定义SQL

  1. 在resources资源文件夹下新建mapper文件夹,并将mapper文件夹的路径配置到配置文件中

     

    image.png

 

mybatis-plus:
  mapper-locations: mapper/*.xml
  1. 在mapper 文件夹中新建UserMapper.xml。
  2. 像mybatis那样在UseMapper接口中写接口,在UserMapper接口中写SQL即可。
    UserMapper

 

public interface UserMapper extends BaseMapper<User> {

    /**
     * 查询所有用户信息
     * @return list
     */
    List<User> selectAll();
}

UserMapper.xml

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="*****.UserMapper">

    <select id="selectAll" resultType="org.ywb.demo.pojo.User">
        select * from user
    </select>
</mapper>

分页查询

MyBatis分页提供的是逻辑分页,每次将所有数据查询出来,存储到内存中,然后根据页容量,逐页返回。如果表很大,无疑是一种灾难!
MyBatisPlus物理分页插件

  1. 新建config类,在config类中创建PaginationInterceptor对象

 

@Configuration
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }
}
  1. 测试:查询年龄大于20 的用户信息,并以每页容量为两条分页的形式返回。

 

    @Test
    public void selectPage(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.ge("age",20);

        //设置当前页和页容量
        Page<User> page = new Page<>(1, 2);

        IPage<User> userIPage = userMapper.selectPage(page, queryWrapper);

        System.out.println("总页数:"+userIPage.getPages());
        System.out.println("总记录数:"+userIPage.getTotal());
        userIPage.getRecords().forEach(System.out::println);
    }

image.png

  1. 测试:不查询总记录数,分页查询
    IPage类的构造参数提供了参数的重载,第三个参数为false时,不会查询总记录数。

 

public Page(long current, long size, boolean isSearchCount) {
        this(current, size, 0, isSearchCount);
}
~~·
## 更新
1. 通过userMapper提供的方法更新用户信息
~~~java
    @Test
    public void updateTest1(){
        User user = new User();
        user.setId(1088250446457389058L);
        user.setEmail("update@email");
        int rows = userMapper.updateById(user);
        System.out.println(rows);
    }

image.png

  1. 使用UpdateWrapper更新数据(相当于使用联合主键)

 

    @Test
    public void updateTest2(){
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("name","李艺伟").eq("age",26);

        User user = new User();
        user.setEmail("update2@email");
        int rows = userMapper.update(user, updateWrapper);
        System.out.println(rows);
    }

image.png

  1. 当我们更新少量用户信息的时候,可以不用创建对象,直接通过调用set方法更新属性即可。

 

    @Test
    public void updateTest3(){
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("name","李艺伟").eq("age",26).set("email","update3@email.com");
        userMapper.update(null,updateWrapper);
    }

image.png

  1. 使用lambda更新数据

 

    @Test
    public void updateByLambda(){
        LambdaUpdateWrapper<User> lambdaUpdateWrapper = Wrappers.lambdaUpdate();
        lambdaUpdateWrapper.eq(User::getName,"李艺伟").eq(User::getAge,26).set(User::getAge,27);
        userMapper.update(null,lambdaUpdateWrapper);
    }

image.png

删除

删除方式和update极其类似。

AR模式(Active Record)

直接通过实体类完成对数据的增删改查。

  1. 实体类继承Model类

 

@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {
    private Long id;
    @TableField(condition = SqlCondition.LIKE)
    private String name;
    private Integer age;
    private String email;
    private Long managerId;
    private LocalDateTime createTime;
}

Model类中封装了很多增删改查方法,不用使用UserMapper即可完成对数据的增删改查。

  1. 查询所有用户信息

 

    @Test
    public void test(){
        User user = new User();
        user.selectAll().forEach(System.out::println);
    }

image.png

主键策略

MyBatisPlus的主键策略封装在IdType枚举类中。

 

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

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

    private final int key;

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

在实体类中对应数据库中的主键id属性上标注注解TableId(type='xxx')即可完成主键配置。

 

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

这种配置方式的主键策略只能在该表中生效,但是其他表还需要进行配置,为了避免冗余,麻烦,MybatisPlus提供了全局配置,在配置文件中配置主键策略即可实现。

 

mybatis-plus:
  mapper-locations: mapper/*.xml
  global-config:
    db-config:
      id-type: auto

如果全局策略和局部策略全都设置,局部策略优先。

基本配置

MyBatisPlus官方文档

 

mybatis-plus:
  mapper-locations: mapper/*.xml
  global-config:
    db-config:
      # 主键策略
      id-type: auto
      # 表名前缀
      table-prefix: t
      # 表名是否使用下划线间隔,默认:是
      table-underline: true
  # 添加mybatis配置文件路径
  config-location: mybatis-config.xml
  # 配置实体类包地址
  type-aliases-package: org.ywb.demo.pojo
  # 驼峰转下划线
  configuration:
    map-underscore-to-camel-case: true

逻辑删除

  1. 设定逻辑删除规则
    在配置文件中配置逻辑删除和逻辑未删除的值

 

mybatis-plus:
  global-config:
      logic-not-delete-value: 0
      logic-delete-value: 1
  1. 在pojo类中在逻辑删除的字段加注解@TableLogic

 

@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField(condition = SqlCondition.LIKE)
    private String name;
    private Integer age;
    private String email;
    private Long managerId;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private Integer version;
    @TableLogic
    private Integer deleted;
}
  1. 通过id逻辑删除

 

    @Test
    public void deleteById(){
        userMapper.deleteById(4566L);
    }

  1. 查询中排除删除标识字段及注意事项
    逻辑删除字段只是为了标识数据是否被逻辑删除,在查询的时候,并不想也将该字段查询出来。
    我们只需要在delete字段上增加@TableField(select = false)mybatisplus在查询的时候就会自动忽略该字段。

 

    @Test
    public void selectIgnoreDeleteTest(){
        userMapper.selectById(3456L);
    }

 


自定义sql,MybatisPlus不会忽略deleted属性,需要我们手动忽略

 

自动填充

MybaitsPlus在我们插入数据或者更新数据的时候,为我们提供了自动填充功能。类似MySQL提供的默认值一样。
如果我们需要使用自动填充功能,我们需要在实体类的相应属性上加@TableField注解,并指定什么时候进行自动填充。mybatisPlus为我们提供了三种填充时机,在FieldFill枚举中

 

public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}

设置好之后,我们还需要编写具体的填充规则,具体是编写一个填充类并交给Spring管理,然后实现MetaObjectHandler接口中的insertFillupdateFill方法。
eg:

  1. 插入User对象的时候自动填充插入时间,更新User对象的时候自动填充更新时间。
  • 指定实体类中需要自动填充的字段,并设置填充时机

 

@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {
    ...
    @TableField(fill = INSERT)
    private LocalDateTime createTime;
    @TableField(fill = UPDATE)
    private LocalDateTime updateTime;
    ...
}
  • 编写填充规则

 

@Component
public class MyMetaObjHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        if(metaObject.hasSetter("createTime")){
            setInsertFieldValByName("createTime", LocalDateTime.now(),metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        if(metaObject.hasSetter("updateTime")){
            setUpdateFieldValByName("updateTime",LocalDateTime.now(),metaObject);
        }
    }
}

解释一下为什么要用if判断是否有对应的属性
mybatisPlus在执行插入或者更新操作的时候,每次都会执行该方法,有些表中是没有设置自动填充字段的,而且有些自动填充字段的值的获取比较消耗系统性能,所以为了不必要的消耗,进行if判断,决定是否需要填充。

有些时候我们已经设置了属性的值。不想让mybatisPlus再自动填充,也就是说我们没有设置属性的值,mybatisPlus进行填充,如果设置了那么就用我们设置的值。这种情况我们只需要在填充类中提前获取默认值,然后使用该默认值就可以了。

 

    @Override
    public void updateFill(MetaObject metaObject) {
        if(metaObject.hasSetter("updateTime")){
            Object updateTime = getFieldValByName("updateTime", metaObject);
            if(Objects.nonNull(updateTime)){
                setUpdateFieldValByName("updateTime",updateTime,metaObject);
            }else{
                setUpdateFieldValByName("updateTime",LocalDateTime.now(),metaObject);
            }
        }
    }

乐观锁

乐观锁适用于读多写少的情况,更新数据的时候不使用“锁“而是使用版本号来判断是否可以更新数据。通过不加锁来减小数据更新时间和系统的性能消耗,进而提高数据库的吞吐量。CAS机制就是一种典型的乐观锁的形式。
乐观锁是逻辑存在的一种概念,我们如果使用乐观锁需要手动在表的加上version字段。

  1. mysql使用乐观锁伪代码示例:

 

update user 
set balabala....
where balabala... and version = xxx

乐观锁

1.配置类中注入乐观锁插件

 

    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }
  1. 实体类中的版本字段增加@version注解

 

@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {
    ...
    @Version
    private Integer version;
    ...
}
  1. test
    更新王天风的年龄

 

    @Test
    public void testLock(){
        int version = 1;
        User user = new User();
        user.setEmail("wtf@163.com");
        user.setAge(34);
        user.setId(2345L);
        user.setManagerId(1234L);
        user.setVersion(1);
        userMapper.updateById(user);

    }

image.png

 

数据库中的version已经变成2

 

image.png

注意事项:

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

性能分析

  1. 配置类中注入性能分析插件

 

    @Bean
   // @Profile({"dev,test"})
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        // 格式化sql输出
        performanceInterceptor.setFormat(true);
        // 设置sql执行最大时间,单位(ms)
        performanceInterceptor.setMaxTime(5L);

        return performanceInterceptor;
    }

执行sql就可以打印sql执行的信息了

 

image.png

依靠第三方插件美化sql输出

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

  1. 第三方依赖

 

        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.8.5</version>
        </dependency>
  1. 更改配置文件中的dirver和url

 

spring:
  datasource:
#    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
#    url: jdbc:mysql://localhost:3306/test?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3306/test?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
  1. 增加spy.properties配置文件

 

module.log=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,batch,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

  1. test

     

注意

开启性能分析会消耗系统的性能,所以性能分析插件要配合@Profile注解执行使用的环境。

SQL注入器 ->_-> 封装自定义通用SQL

实现步骤:

  1. 创建定义方法的类
  2. 创建注入器
  3. 在mapper中加入自定义方法

eg: 编写一个删除表所有数据的方法

  1. 创建定义方法的类

 

public class DeleteAllMethod extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        // 执行的sql
        String sql = "delete from " + tableInfo.getTableName();
        // mapper接口方法名
        String method = "deleteAll";
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, mapperClass);
        return addDeleteMappedStatement(mapperClass, method, sqlSource);
    }
}
  1. 创建注入器。添加自己的方法

 

@Component
public class MySqlInject extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new DeleteAllMethod());
        return methodList;
    }
}
  1. 在mapper中加入自定义方法

 

public interface UserMapper extends BaseMapper<User> {

    /**
     * 删除所有表数据
     *
     * @return 影响行数
     */
    int deleteAll();

}

  1. test

 

    @Test
    public void deleteAll(){
        userMapper.deleteAll();
    }



 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值