MyBatis-Plus

Posted xiaozhizxj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis-Plus相关的知识,希望对你有一定的参考价值。

MyBatis-Plus

一、简介

文档:https://mp.baomidou.com/

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

1.特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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.框架结构

3.快速开始

3.1创建数据库

创建数据库mybatisplus,并创建user表

DROP TABLE IF EXISTS user;

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

user表插入数据

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

3.2创建springboot项目,导入依赖

引入 spring-boot-starterspring-boot-starter-testmybatis-plus-boot-starterh2 依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </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>Latest Version</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!

3.3 在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:

@SpringBootApplication
@MapperScan("com.xiaozhi.mapper")
public class MybatisplusSpringbootApplication {

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

}

3.4编写实体类

public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;

    public User() {
    }
    public User(Long id, String name, Integer age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name=\'" + name + \'\\\'\' +
                ", age=" + age +
                ", email=\'" + email + \'\\\'\' +
                \'}\';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

3.5编写Mapper类 UserMapper.java

public interface UserMapper extends BaseMapper<User> {

}

3.6连接数据库

# DataSource Config
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root123

3.7编写测试类

@SpringBootTest
class MybatisplusSpringbootApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        userList.forEach(System.out::println);
    }
}

二、配置日志和CRUD

1.配置日志

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

2.CRUD

2.1插入操作

@Test
public void testInsert() {
        User user = new User();
        user.setName("xiaozhi");
        user.setAge(12);
        user.setEmail("xiao@xianx.com");

        int result = userMapper.insert(user);// 帮我们自动生成id
        System.out.println(result);// 受影响的行数
        System.out.println(user);// 发现: id自动回填
}

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

2.2主键生成策略

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

2.21 雪花算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!

2.22主键自增

实体类上增加 @TableId(type = IdType.AUTO)

保证数据库id字段自动递增

2.23再次插入变为自增
2.24源码解释
public enum IdType {
    AUTO(0), //数据库id自增
    NONE(1),  //未设置主键
    INPUT(2), //手动输入
    ASSIGN_ID(3),
    ASSIGN_UUID(4); //全局唯一id UUID

    private final int key;

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

    public int getKey() {
        return this.key;
    }
}

2.3更新操作

@Test
public void testUpdate(){
        User user = new User(5L,"张三",19,"xiao@ss.com");

        //注意:updateById 参数是一个对象
        int i = userMapper.updateById(user);
        System.out.println(i);
}

2.4自动填充

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

阿里巴巴开发手册:所有的数据库表:gmt_create、gmr_modified、几乎所有的表都要配置上!而且需要自动化!

数据库级别(工作中不允许修改数据库)
2.41在表中新增字段 create_time, update_time

2.42.//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
2.43 编写处理器处理注解
@Component  //一定不要忘记把处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时候的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        //default MetaObjectHandler
        //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) {
        this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
    }
}

2.5乐观锁

2.51简介
乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试!

悲观锁:顾名思义十分悲观,他总是任务总是出现问题,无论干什么都会上锁!再去操作!
2.52 乐观锁实现方式
  • 取出记录,获取当前version
  • 更新时,带上这个version
  • 执行更新时,set version = new version where version = oldversion
  • 如果version不对,就更新失败
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "ChanV", version = version + 1
where id = 2 and version = 1

-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "ChanV", version = version + 1
where id = 2 and version = 1
2.53MybatisPlus的乐观锁插件
1.数据库添加version字段

2.实体类加对应的字段
@Version    //乐观锁version注解
private Integer version;
3.注册组件
//扫描我们的mapper文件夹
@MapperScan("com.xiaozhi.mapper")
@EnableTransactionManagement
@Configuration  //配置类
public class MyBatisPlusConfig {
    //注册乐观锁插件
     /**
     * 旧版
     */
//    @Bean
//    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
//        return new OptimisticLockerInterceptor();
//    }
    /**
     * 新版
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
4.测试乐观锁
//测试乐观锁成功!
@Test
public void testOpversion(){
//        查询用户
        User user = userMapper.selectById(5L);
        //修改用户信息
        user.setName("小明");
        userMapper.updateById(user);
}

//测试乐观锁失败!多线程下(线程2成功,线程1失败)
@Test
public void testOpversion1(){
        //线程1
//        查询用户
        User user = userMapper.selectById(5L);
        //修改用户信息
        user.setName("小明1");

        //线程2
        User user1 = userMapper.selectById(5L);
        user1.setName("小明2");
        //线程2先提交
        int i = userMapper.updateById(user1);
        System.out.println("线程2update的值为:  "+i);

        //线程1再提交
        int i1 = userMapper.updateById(user);
        System.out.println("线程1update的值为:  "+i1);
}

2.6查询操作

1.通过id查询
@Test
public void testSelectByid(){
        User user = userMapper.selectById(3L);
        System.out.println(user);
}
2. 测试批量查询!
@Test
public void testSelectByBatchIds(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        users.forEach(System.out::println);
}
3. 条件查询之一使用map操作
@Test
public void testSelectByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","Xss");
        System.out.println(userMapper.selectByMap(map));

}

3.分页查询

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

1.使用

配置拦截器组件即可

//分页插件
// 旧版
@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 paginationInterceptor;
}
    
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
}

直接使用Page对象

//测试分页查询
@Test
public void testPage(){
    //参数一:当前页
    //参数二:页面大小
    //使用了分页插件之后,所有的分页操作也变得简单了!
    Page<User> page = new Page<>(1, 3);
    userMapper.selectPage(page, null);
    page.getRecords().forEach(System.out::println);
    System.out.println(page.getTotal());
}

4.删除操作

基本的删除操作

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

//通过id批量删除
@Test
public void testDeleteBatchId(){
    userMapper.deleteBatchIds(Arrays.asList(2, 3, 4));
}

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

5.逻辑删除

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

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

1.数据库增加一个isdeleted字段

2.实体类中增加属性

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

3.springboot配置

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)

4.测试一下删除

 //逻辑删除
    @Test
    public void testIsdelete(){
        int i = userMapper.deleteById(1L);
        System.out.println("执行成功"+i);
    }

    @Test
    public void testIsdelete1(){
        int i = userMapper.deleteBatchIds(Arrays.asList(1L,2L,3L));
        System.out.println("执行成功"+i);
    }

执行的是update更新操作

5.性能分析插件(新版找不到)

三.条件构造器AbstractWrapper

1.AbstractWrapper

QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为

2.条件参数说明

查询方式 说明
setSqlSelect 设置SELECT查询字段
where WHERE语句,拼接 - WHERE条件
and AND语句,拼接 - AND 字段=值
andNew AND语句,拼接 - AND (字段=值)
or OR语句,拼接 - OR 字段=值
orNew OR语句,拼接 - OR(字段=值)
eq 等于=
allEq 基于map内容等于=
ne 不等于<>
gt 大于>
ge 大于等于>=
lt 小于<
le 小于等于<=
like 模糊查询LIKE
notLike 模糊查询NOT LIKE
in IN查询
notin NOT IN查询
isNull NULL值查询
isNotNull IS NOT NULL
groupBy 分组GROUP BY
having HAVING关键词
orderBy 排序ORDER BY
orderAsc ASC排序ORDER BY
orderDesc DESC排序ORDER BY
exists EXISTS条件语句
notExists NOT EXISTS条件语句
between BETWEEN条件语句
notBetween NOT BETWEEN条件语句
addFilter 自由拼接SQL
last 拼接在最后,例如last("LIMIT 1")

3.QueryWrapper

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取

4.UpdateWrapper

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
LambdaUpdateWrapper, 可以通过 new UpdateWrapper().lambda() 方法获取!

5.操作实例

//条件构造器
@Test
public void testContextloads(){
    //查询name不为空的用户,并且邮箱不为空的用户,年龄大于12
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.isNotNull("name")
            .isNotNull("email")
            .ge("age",12);
    List<User> userList = userMapper.selectList(wrapper);
    userList.forEach(System.out::println);

}

@Test
public void testContextloads1(){
    //查询名字Sandy
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("name","Sandy");
    List<User> userList = userMapper.selectList(queryWrapper);
    userList.forEach(System.out::println);
}

@Test
public void testContextloads2(){
    //查询年龄在10到20岁之间的用户
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.between("age", 10, 20);
    List<User> userList = userMapper.selectList(queryWrapper);
    userList.forEach(System.out::println);

}
@Test
public void testContextloads3(){
    //like模糊查询
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("name","%x%");
    List<User> userList = userMapper.selectList(queryWrapper);
    userList.forEach(System.out::println);
}
@Test
public void testContextloads4(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //id 在子查询中查出来
    //id IN (select id from user where id < 3)
    wrapper.inSql("id", "select id from user where id < 3");
    List<Object> objects = userMapper.selectObjs(wrapper);
    objects.forEach(System.out::println);
}
@Test
public void testContextloads5(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //通过id进行排序
    wrapper.orderByDesc("id");
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

四、多表查询

user表

public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;

    //字段添加填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    @Version    //乐观锁version注解
    private Integer version;

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

    private int r_id;

    private Role role;
    }

role表

public class Role {
    private Long u_id;

    private String name1;
}

1.配置文件

mapper-locations: classpath*:/com/xiaozhi/mapper/*.xml

2.UserMapper

// 在对应的Mapper上面继承基本的接口 BaseMapper
@Repository // 代表持久层
public interface UserMapper extends BaseMapper<User> {
    Page getSelectByid1(Page page);
}

3.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="com.xiaozhi.mapper.UserMapper">

    <select id="getSelectByid1" resultMap="result1">
        select  * from mybatisplus.user join mybatisplus.role on user.r_id = role.u_id
    </select>

    <resultMap id="result1" type="com.xiaozhi.pojo.User">
        <result property="id" column="id"/>
        <result  property="name" column="name"/>
        <result  property="age" column="age"/>
        <result property="email" column="email"/>
        <result property="createTime" column="createTime"/>
        <result property="updateTime" column="updateTime"/>
        <result property="version" column="version"/>
        <result property="isdeleted" column="isdeleted"/>
        <result property="r_id" column="r_id"/>
        <association property="role" javaType="com.xiaozhi.pojo.Role">
            <result property="u_id" column="u_id"/>
            <result property="name1" column="name1"/>
        </association>
    </resultMap>
</mapper>

4.测试

@Test
public void testDou1(){
    Page<Object> page = new Page<>(1, 3);
    userMapper.getSelectByid1(page);
    page.getRecords().forEach(System.out::println);
    System.out.println(page.getTotal());
}

5.注意每个字段都不能有相同

五、多表查询(二)

MyBatis两张表字段名相同产生的问题

参考链接: (2条消息) MyBatis两张表字段名相同产生的问题_Never lose the opportunity to succeed.-CSDN博客

以上是关于MyBatis-Plus的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis-Plus逆向生成代码

mybatis-plus 代码生成

mybatis-plus代码生成器代码

详解:MyBatis-Plus 代码生成器的使用

详解:MyBatis-Plus 代码生成器的使用

详解:MyBatis-Plus 代码生成器的使用