[Mybatis-plus]

[Mybatis-plus]

因为考虑到安装mysql比较复杂,这里直接使用基于内存的H2数据库,这样可以让大家快速的开始。

前期准备:


pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lhb</groupId>
    <artifactId>springboot-mybatisplus</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <artifactId>springboot2.1.3</artifactId>
        <groupId>com.lhb</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

springboot的resource下建立H2需要的创建表的语句
[Mybatis-plus]
data-h2.sql

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

 schema-h2.sql

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 '邮箱',
    create_time datetime,
    update_time datetime,
    version LONG,
    deleted Int,
    PRIMARY KEY (id)
);

springboot的yml配置H2

spring:
  profiles:
    active: dev
  datasource:
    platform: h2
    driver-class-name: org.h2.Driver
    schema: classpath:db/schema-h2.sql
    data: classpath:db/data-h2.sql
    url: jdbc:h2:mem:test
    username: root
    password: test
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
      path: /h2-console

经过上面的配置后,就可以在springboo的test下进行mybatis-plus的操作了,如下

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

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
       userMapper.selectList(null);
    }
}

 

Mybatis-plus的使用

配置日志:
程序运行时,我们默认是看不到mybatis-puls运行的sql的,毕竟输出日志是很浪费时间的。但是如果开发过程中想看到这些运行的sql相关日志时,需要进行配置。本节将介绍日志的配置。

配置日志的方式如下:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #这个日志的实现类有很多,可自行选择

配置日志后,可以发现打印结果有了mybtis-plus执行的过程了和sql了

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659f226a] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1020421154 wrapping conn0: url=jdbc:h2:mem:test user=ROOT] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email FROM user 
==> Parameters: 
<==    Columns: ID, NAME, AGE, EMAIL
<==        Row: 1, Jone, 18, test1@baomidou.com
<==        Row: 2, Jack, 20, test2@baomidou.com
<==        Row: 3, Tom, 28, test3@baomidou.com
<==        Row: 4, Sandy, 21, test4@baomidou.com
<==        Row: 5, Billie, 24, test5@baomidou.com
<==      Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659f226a]
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

CRUD扩展

1.insert插入操作
 

package com.lhb.practice;

import com.lhb.practice.entity.User;
import com.lhb.practice.mapper.UserMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    public void testInsert(){
        User user = new User();
        user.setName("john");
        user.setAge(3);
        int result = userMapper.insert(user);//帮我们自动生成id,观察log
        System.out.println(result); //相应行数
        System.out.println(user); // 自动将生成的id进行对象的回填
    }
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@713ec32d] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@730949640 wrapping conn0: url=jdbc:h2:mem:test user=ROOT] will not be managed by Spring
==>  Preparing: INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? ) 
==> Parameters: 1482594978522202114(Long), john(String), 3(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@713ec32d]
1
User(id=1482594978522202114, name=john, age=3, email=null)

从上图log中看到id是一长串数字,那么这些主键的生成策略有哪些呢?下面来看看

主键生成策略(mybatis-puls默认雪花算法)
分布式系统唯一ID生成方案汇总:分布式系统唯一ID生成方案汇总 - nick hao - 博客园

雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,比如有的数据中心在北京,有的在上海,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。

自增主键策略实现:
 

@Data
public class User {
    // 通过这个注解来设置ID的策略
    @TableId(type=IdType.AUTO)
    private Long id;
    private String name;
}

// IdType的类型中含有的一些id主键生成策略
public enum IdType {
    AUTO(0),  // 自增长,数据库必须勾上自增再能用这个
    NONE(1),  // 不设置主键id
    INPUT(2), // 通过代码手动设置id
    ID_WORKER(3), //全局唯一ID(Long型),也就是雪花算法,它是默认的
    UUID(4), // uuid作为主键
    ID_WORKER_STR(5); // ID_WORKER字符串表示法(字符串类型)
    private int key;
    private IdType(int key) {this.key = key;}
    public int getKey() {return this.key;}
}

自动填充
例如一般表中的更新时间,创建时间都是默认的,不需要设置的,这种就属于自动填充,不需要我们想对象中插入值。
阿里巴巴java开发手册要求数据库表中必须有一下三个字段:id, gmt_create, gmt_modified。而且这三个字段必须自动化赋值。自动化的方式有哪些呢?如下:
方法1:数据库级别(在数据库设置操作时自动填充的值)
[Mybatis-plus]

 

方法2:代码级别

通过代码设置进行插入和更新时自动对那些属性进行填充,然后保存到表对应的字段中

@Data
public class User { // 这里设置对创建时间和更新时间使用自动填充
    @TableId(type=IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private String email;
    @TableField(fill= FieldFill.INSERT)
    private Date createTime;
    @TableField(fill= FieldFill.INSERT_UPDATE) //更新和插入时都要修改这个字段
    private Date updateTime;
}

通过fill参数来设置自动填充的策略(什么时候进行自动填充) ,具体含有的策略如下

//自动填充时的策略
public enum FieldFill {
    DEFAULT,// 默认值,不进行填充
    INSERT, // 只有插入操作时自动填充
    UPDATE, // 只有更新操作时自动填充
    INSERT_UPDATE; // 插入和更新时进行自动填充
    private FieldFill() {
    }
}

设置了自动填充策略后,那么究竟把什么数据填充到这两个字段中呢?这就需要再写一个专门用来生成数据的类。如下,创建一个handler包,创建一个类MyMetaOjectHandler.java,如下:
 

@Component // 把组件加入到spring容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        //第一个参数:要给谁填充值
        //第二个参数:填充的值
        setFieldValByName("createTime",new Date(),metaObject);
        setFieldValByName("updateTime",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        setFieldValByName("updateTime",new Date(),metaObject);
    }
}

测试:此时执行代码,然后可以在控制台看到打印出的插入后的对象自动填充了日期

    @Test
    public void testInsert(){
        User user = new User();
        user.setName("john");
        user.setAge(3);
        int result = userMapper.insert(user);//帮我们自动生成id,观察log
        System.out.println(result); //相应行数
        System.out.println(user); // 自动将生成的id进行对象的回填
    }

乐观锁

实现方式:

  • 取数据时,获取当前的version
  • 更新世带上这个version
  • 执行更新时,set version = newVersion where version = oldversion
  • 如果version不对,就更改失败

存在问题:乐观锁有ABA问题。

Mybatis-plus乐观锁的使用:
第一步:给表中添加version字段,初始值为1
第二步:给实体类同步添加version属性,然后给这个属性添加@version属性,这就表示这个字段用来实现乐观锁了。

@Data
public class User {
    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
    private long version;
}


第三步:注册一个乐观锁组件,用来实现乐观锁,一般这个会放到配置类的config包中

@EnableTransactionManagement //开启事务处理,乐观锁要用
@Configuration
public class MyBatisPlusConfig {
    // 注册乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }
}

测试:
 

// 乐观锁成功例子
public void testOptimisticLocker(){
        User user = new User();
        user.setName("john");
        user.setAge(3);
        userMapper.updateById(user);
}
// 乐观锁失败的例子
 @Test
    public void testOptimisticLocker(){
        // 模拟线程1
        User user1 = new User();
        user1.setName("john1");
        user1.setAge(31);
        //模拟线程2
        User user2 = new User();
        user2.setName("john2");
        user2.setAge(32);
        userMapper.updateById(user2);
        //由于线程2先插入了,导致version变了,所以线程一的user1在更新时version不对了,
        //就不会在更新了。如果没有乐观锁这个功能,那么就会覆盖user2更新的值了
        // 如果非要让user1更新到表中,就可以采用自旋锁来解决
        userMapper.updateById(user1);
    }

查询用法进阶
注意:如果开启了逻辑删除功能,那么在查询时逻辑删除的记录会自动过滤掉,不会查询出来(sql的select语句的where会自动带上deleted=0这个条件。deleted时我们自己设置的逻辑删除的字段,可以自己起名字)
例子1:查询id为1,2,3的用户
selectBatchIds通过接收一个集合的参数,查询出满足条件数据,最后会变为sql中的in条件

  @Test
    public void testSelect() {
        List<User> userList = userMapper.selectBatchIds(Arrays.asList(1,2,3));
        userList.forEach(System.out::println);
    }

例子2:通过map作为条件进行查询

   @Test
    public void testSelect() {
        Map<String,Object> map = new HashMap<>();
        map.put("name","Jack");
        List<User> userList = userMapper.selectByMap(map);
        userList.forEach(System.out::println);
    }

删除进阶操作:
可以通过传递一个集合参数,来批量删除,跟查询用法相同。这里就不写代码了。 

逻辑删除
第一步:在数据库添加逻辑删除字段deleted
第二步:在实体类中添加属性deleted,并且在属性上添加注解@TableLogic

 @TableLogic
 private Integer deleted;

第三步:在上面的那个配置类中再次添加逻辑删除的组件

@Bean
private ISqlInjector injector(){
    return new LogicSqlInjector(); //也可以自己重写这个接口
}

 第四步:在yml中配置逻辑删除

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:#这块就是逻辑删除的配置
    db-config:
      logic-delete-value: 1
      logic-not-delete-value: 0

第五步:执行逻辑删除

public void testDelete(){
   userMapper.deleteById(1L);//配置逻辑删除后,就会自动进行逻辑删除了
}

mybatis-plus逻辑删除最后执行的实际就是一个更新操作,把deleted字段由0改为1 。

分页查询
第一步:在上面的config包中的那个配置乐观锁的类MyBatisPlusConfig.java中添加分页插件

 // 分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }

第二步:使用Page对象操作

@Test
    public void testSelect() {
        //参数意思:5条为一页,现在要去第一页的内容
        Page<User> page =  new Page<>(1,5);
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println);
    }

性能分析插件

开发中有可能会遇到一些慢sql,mybatis-plus提供了一个性能分析插件,这个插件会检测查询语句是否过慢,如果太慢就会停止sql执行。
作用:分析每个sql执行的时间
第一步:在config包的那个配置类中配置这个分析插件需要的组件

   @Bean
    @Profile({"dev","test"}) // 只有yml中的active设置的时dev或者test才会向容器中注册这个组件,也就是让组件起作用。
    public PerformanceInterceptor performanceInterceptor(){
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        performanceInterceptor.setMaxTime(1);//设置sql执行的最长时间,如果超过这个时间sql就不执行了,默认单位为毫秒
        performanceInterceptor.setFormat(true);//开启格式化,控制台打印出的sql会格式化
        return performanceInterceptor;
    }

第二步:配置yml的active属性

spring:
  profiles:
    active: dev

第三步:执行看效果
随便执行一个查询语句,由于我们设置的最慢时间时1毫秒,所以sql执行肯定会超过1毫秒,这是就会停止查询,然后把异常打印出来,如下,并且还不会sql格式化后显示出来。

JDBC Connection [HikariProxyConnection@571251299 wrapping conn0: url=jdbc:h2:mem:test user=ROOT] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 
==> Parameters: 
<==      Total: 0
 Time:11 ms - ID:com.lhb.practice.mapper.UserMapper.selectList
Execute SQL:
    SELECT
        id,
        name,
        age,
        email,
        create_time,
        update_time,
        version,
        deleted 
    FROM
        user 
    WHERE
        deleted=0

Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3cd26422]

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException:  The SQL execution time is too large, please optimize ! 
### The error may exist in com/lhb/practice/mapper/UserMapper.java (best guess)
### The error may involve com.lhb.practice.mapper.UserMapper.selectList
### The error occurred while handling results
### SQL: SELECT  id,name,age,email,create_time,update_time,version,deleted  FROM user  WHERE deleted=0
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException:  The SQL execution time is too large, please optimize ! 

条件构造器-Wrapper
使用复杂的条件查询时,就需要这个条件构造器
例子1:

@Test
    public void testSelect() {
        // 查询名字不为空,邮箱不为空,年龄大于12的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.isNotNull("name").isNotNull("email").gt("age",12);
        userMapper.selectList(wrapper);
    }

例子2

@Test
    public void testSelect() {
        // 查询20-30岁之间用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age",20,30);
        userMapper.selectCount(wrapper);
    }

例子3:

  @Test
    public void testSelect() {
        // 模糊查询,名字中不含有e的并且email还有t的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // likeRight和likeLeft就是只%位置
        wrapper
                .notLike("name","e")
                .likeRight("email","t"); // t%
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);
    }

我们通过日志中的sql来观察这个模糊查询最后生成的sql、

==>  Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ? 
==> Parameters: %e%(String), t%(String)

例子4:条件含有子查询

 @Test
    public void testSelect() {
        // 条件中使用子查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
               .inSql("id","select id from user where id<3");
        List<Object> objects = userMapper.selectObjs(wrapper);
        objects.forEach(System.out::println);
    }

// 日志打印的sql
 SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND id IN (select id from user where id<3) 

代码自动生成器
第一步:导入maven的mybatis-plus依赖
第二步:yml配置数据库
第三步:在test下创建一个我们自己的代码生成器类
注意:官网的代码生成器分为新旧两版,注意版本对应

package com.lhb.practice;

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

import java.util.ArrayList;

public class MyCodeGen {
    public static void main(String[] args) {
        //需要创建一个 代码自动生成器对象
        AutoGenerator mpg = new AutoGenerator();
        //配置策略
        //1. 全局配置
        GlobalConfig gc = new GlobalConfig();
        // 项目所在目录
        String projectPath = System.getProperty("user.dir");
        // 生成的代码放到项目所在目录
        gc.setOutputDir(projectPath + "src/main/java");
        // 设置作者
        gc.setAuthor("lhb");
        //生成后是否自动打开
        gc.setOpen(false);
        //是否覆盖
        gc.setFileOverride(false);
        // 设置接口前缀的名字:例如我们IHelloService中的I
        gc.setServiceName("%sService");// 就可以去掉前缀的I
        gc.setIdType(IdType.ID_WORKER);//id的生成缩略
        gc.setSwagger2(true);// 是否配置Swagger文档
        gc.setDateType(DateType.ONLY_DATE);

        mpg.setGlobalConfig(gc);

        // 2. 设置数据源
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:h2:mem:test");
        dsc.setDriverName("org.h2.Driver");
        dsc.setPassword("test");
        dsc.setUsername("root");
        dsc.setDbType(DbType.H2);
        mpg.setDataSource(dsc);
        //3.配置生成包的位置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.lhb"); // 包名
        pc.setModuleName("blog"); // 生成com.lhb.blog
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setController("controller");
        mpg.setPackageInfo(pc);

        // 策略配置,比如乐观锁
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("user");// 想要哪个表自动生成,可以写多个
        strategy.setNaming(NamingStrategy.underline_to_camel);//字段下划线转驼峰
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);//是否直接让生成的entity里直接使用lombok
        strategy.setRestControllerStyle(true);
        strategy.setLogicDeleteFieldName("deleted");//设置逻辑删除的字段名,作用就是entity中对象自动生成逻辑删除(添加逻辑删除注解)
        // 自动填充策略配置
        TableFill createTime = new TableFill("createTime", FieldFill.INSERT);
        TableFill updateTime = new TableFill("updateTime", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> tableFill = new ArrayList<>();
        tableFill.add(createTime);
        tableFill.add(updateTime);
        strategy.setTableFillList(tableFill);
        // 乐观锁策略配置
        strategy.setVersionFieldName("version");
        strategy.setControllerMappingHyphenStyle(true);// 将请求由多个单词时,自动下环线,例如:localhost:8080/hello_id_2
        mpg.setStrategy(strategy);
        
        // 最后执行代码生成
        mpg.execute();

    }
}



 

上一篇:还不会用mybatis-plus,手把手教你


下一篇:uniapp 获取当前定位