📖 官方文档

🌐文档

👉 mybatis-plus

☄️特性

  • 润物无声:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
  • 效率至上:只需简单配置,即可快速进行单表 CRUD 操作,从而节省大量时间。
  • 丰富功能:代码生成、物理分页、性能分析等功能一应俱全。

⛓结构


➕ 添加依赖

<?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>top.parak</groupId>
    <artifactId>Mybatis-plus</artifactId>
    <description>Mybatis-plus Learning</description>
    <version>1.0-SNAPSHOT</version>

    <developers>
        <developer>
            <name>KHighness</name>
            <email>parakovo@gmail.com</email>
        </developer>
    </developers>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
    </parent>

    <properties>
        <mysql.version>8.0.20</mysql.version>
        <fastjson.version>1.2.58</fastjson.version>
        <gson.version>2.8.6</gson.version>
        <mybatis-plus.version>3.4.0</mybatis-plus.version>
        <common-io.version>2.6</common-io.version>
        <common-fileupload.version>1.4</common-fileupload.version>
        <mybatis-plus-generate.version>2.3.3</mybatis-plus-generate.version>
    </properties>

    <dependencies>

        <!-- Spring Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- Spring Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Spring Configuration -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- mybatis-plus-generator -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generate</artifactId>
            <version>${mybatis-plus-generate.version}</version>
        </dependency>

        <!-- Mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- Unit Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- FastJSON -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>top.parak.KHighnessApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

💥配置日志

# Server
server.port=3333
server.tomcat.uri-encoding=UTF-8

# Database
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=KAG1823

# Mybatis-plus
mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
mybatis-plus.type-aliases-package=top.parak.entity
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.global-config.banner=false
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# Logic-Delete
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

# Log
logging.level.top.parak.mapper=debug
logging.level.top.parak.controller=debug

🌀主键生成策略

❄️Snowflake

SnowFlake是Twitter开源的分布式ID生成算法。

🆔ID结构

SnowFlake生成ID固定是一个long型的数字,一个long型占8个字节,也就是64个bit,分配如下:

  1. 第一个bit是标识位部分,在Java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以固定为0。
  2. 时间戳部分占41bit,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始。41位的时间戳可以使用69年。
  3. 工作机器ID占10bit,这里比较灵活,比如,可以使用前5位作为数据中心机房标识,后5位作为单机房机器标识,可以部署1024个节点。
  4. 序列号部分占12bit,支持同一毫秒内同一个节点可以生成2^12^=4096个ID。

🌠 优点和缺点

优点:

  • 毫秒数在高位,自增序列在低位,ID趋势递增。
  • 以服务方式部署,可以做高可用。
  • 根据业务分配bit位,灵活。

缺点:

  • 每台机器的时钟不同,当时钟回拨可能会发生重复ID。
  • 当数据量大时,需要对ID取模分库分表,在跨毫秒时,序列号总是归0,会发生取模后分布不均衡。

💻 Java实现

package top.parak.common;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * <p> Project: Mybatis-plus </P>
 * <p> Package: top.parak.common </p>
 * <p> FileName: SnowShakeUntil <p>
 * <p> Description: 雪花算法 <p>
 * <p> Created By IntelliJ IDEA </p>
 *
 * @author KHighness
 * @since 2020/11/8
 */

public class SnowFlakeUntil {

    /**
     * <p>开始时间戳</p>
     */
    public static long START_STMP;
    static {
        String startDateTime = "2001-09-11 00:00:00";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            /* 13位时间戳 */
            START_STMP = simpleDateFormat.parse(startDateTime).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    /**
     * <p>序列号占用的位数</p>
     */
    private final static long SEQUENCE_BIT = 12;

    /**
     * <p>数据中心标识占用的位数</p>
     */
    private final static long MACHINE_BIT = 5;

    /**
     * <p>机器标识占用的位数</p>
     */
    private final static long DATACENTER_BIT = 5;

    /* 每一部分的最大值:先进行左移运算,再同-1进行异或运算 */

    /**
     * <p>用位运算计算出最大支持的数据中心数量:31</p>
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

    /**
     * <p>用位运算计算出最大支持的机器数量</p>
     */
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    /**
     * <p>用位运算计算出最大支持的最大正整数4095</p>
     */
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * <p>机器标志较序列号的偏移量</p>
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;

    /**
     * <p>数据中心较机器标志的偏移量</p>
     */
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    /**
     * <p>时间戳较数据中心的偏移量</p>
     */
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    /**
     * <p>数据中心</p>
     */
    private static long dataCenterId;

    /**
     * <p>机器标识</p>
     */
    private static long machineId;

    /**
     * <p>序列号</p>
     */
    private static long sequence = 0L;

    /**
     * 4<p>上一次时间戳</p>
     */
    private static long lastStmp = -1L;

    /**
     * <p>此处无参构造私有,同时没有给出有参构造,在于避免以下两点问题:</p>
     * <li>1. 私有化避免了通过new的方式进行调用,主要是解决了在for循环中通过new的方式调用产生的id不一定唯一问题问题,因为用于记录上一次时间戳的lastStmp永远无法得到比对</li>
     * <li>2. 没有给出有参构造在第一点的基础上考虑了一套分布式系统产生的唯一序列号应该是基于相同的参数</li>
     */
    private SnowFlakeUntil() {}

    /**
     * <p>生成ID</p>
     * @return
     */
    public static synchronized long nextID() {
        /* 获取当前时间戳*/
        long currStmp = getNewStmp();
        /* 如果当前时间戳小于上次时间戳则抛出异常 */
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id");
        }
        /* 相同毫秒内,序列号自增 */
        if (currStmp == lastStmp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            /* 同一毫秒的序列数已经达到最大*/
            if (sequence == 0L) {
                currStmp = getNextStmp();
            }
        }
        /* 不同毫秒内,序列号设为0 */
        else {
            sequence = 0L;
        }

        /* 当前时间戳存档记录*/
        lastStmp = currStmp;


        return (currStmp - START_STMP) << TIMESTMP_LEFT  // 时间戳部分
                | dataCenterId << DATACENTER_LEFT        // 数据中心部分
                | machineId << MACHINE_LEFT              // 机器标识部分
                | sequence;                              // 序列号部分
    }

    /**
     * <p>当前时间戳</p>
     * @return
     */
    public static long getNewStmp() {
        return System.currentTimeMillis();
    }

    /**
     * <p>下一时间的时间戳</p>
     * @return
     */
    public static long getNextStmp() {
        long mill = getNewStmp();
        while (mill <= lastStmp) {
            mill = getNewStmp();
        }
        return mill;
    }

}

🔱在Mybatis-plus中自定义ID生成器

@Component
public class CustomIdGenerator implements IdentifierGenerator {
    @Override
    public Long nextId(Object entity) {
          // 可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成.
          String bizKey = entity.getClass().getName();
        // 根据bizKey调用分布式ID生成
        long id = ....;
          // 返回生成的id值即可.
        return id;
    }
}

🚀 CRUD拓展

建表

CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `age` tinyint DEFAULT NULL,
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

实体

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

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

    private String name;

    private Integer age;

    @Email
    private String email;

}

🌌自动填充

对于普通字段

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

所有的数据库表都应该包含创建时间gmt_create和修改时间gmt_modified,而且需要自动化。——《阿里巴巴开发手册》

🛢数据库级别

 ALTER TABLE user ADD update_time DATETIME DEFAULT CURRENT_TIMESTAMP;
 ALTER TABLE user ADD create_time DATETIME DEFAULT CURRENT_TIMESTAMP;

⌨️代码级别

  • 数据表添加字段
 ALTER TABLE user ADD update_time DATETIME;
 ALTER TABLE user ADD create_time DATETIME;
  • 实体类添加字段和注解
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
  • 编写处理器处理注解
package top.parak.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * <p> Project: Mybatis-plus </P>
 * <p> Package: top.parak.handler </p>
 * <p> FileName: DataObjectHandler <p>
 * <p> Description: 自动填充处理器 <p>
 * <p> Created By IntelliJ IDEA </p>
 *
 * @author KHighness
 * @since 2020/11/9
 */

@Slf4j
@Component
public class DataObjectHandler implements MetaObjectHandler {

    /**
     * <p>插入时的填充策略</p>
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    /**
     * <p>更新时的填充策略</p>
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

🔏悲观锁

📖理解

当要对数据库的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对数据进行加锁防止并发。

这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称为悲观并发控制(Pessimistic Concurrency Control,缩写PCC,又名悲观锁)。

💠实现

悲观锁的实现,往往依靠数据库提供的锁机制(只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。悲观锁的实现:

  • 传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁
  • Java里面的同步synchronized关键字的实现
  • JUC中的lock加锁

🔱分类

  • 共享锁:又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一个数九可以共享一把锁,都能访问到数据,但是只能读不能修改。
  • 排他锁:又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排它锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取到排他锁的事务可以对数据进行读取和修改。

💬说明

悲观并发控制实质上是先取锁再访问的保守策略,为数据处理的安全提供了保证。

但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。

另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。


🔓乐观锁

📖理解

乐观锁假设数据在一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。

💠实现

乐观锁不会可以使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁的实现:

  • CAS实现:Java 中java.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式

  • 版本号控制:一般是在数据表中添加一个数据版本号version字段,表示数据被修改的次数。当数据被修改时,version值会+1。当线程A更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到version值与当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

    -- 1 查询版本号
    select version as oldversion where id = #{id}
    -- 2 更新操作
    update set ... , version = version  + 1 where id = #{id}  and version = oldversion

💬说明

乐观并发控制相信事务之间的数据竞争概率是比较小的。因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁或死锁。

🔧插件

  • 数据表添加字段
ALTER TABLE user ADD version INT DEFAULT 1; 
  • 实体类添加属性和注解
    /* 乐观锁 */
    @Version
    private Integer version;
  • 在mybatis-plus配置类中增加乐观锁拦截器
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()  {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        /* 乐观锁插件 */
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }

}
  • 乐观锁测试
    /**
     * <p>乐观锁测试1</p>
     */    
    @Test
    void optimisticLock() {
        // 查询信息
        User user1 = userMapper.selectById(10L);
        // 修改信息
        user1.setName("Khighness10");
        user1.setAge(10);
        user1.setEmail("khighness10@qq.com");

        // 插队操作: 抢先更新,会更新版本号
        User user2 = userMapper.selectById(10L);
        user2.setAge(100);
        userMapper.updateById(user2);

        // 执行更新:更新失败,版本号不对
        userMapper.updateById(user1);
    }
# 测试结果
JDBC Connection [HikariProxyConnection@597623166 wrapping com.mysql.cj.jdbc.ConnectionImpl@38cedb7d] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id=? 
==> Parameters: 10(Long)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 10, Khighness10, 10, khighness10@qq.com, 2020-11-09 13:30:04, 2020-11-09 13:35:49, 2
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12a14b74]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a9344f5] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1434769862 wrapping com.mysql.cj.jdbc.ConnectionImpl@38cedb7d] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id=? 
==> Parameters: 10(Long)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 10, Khighness10, 10, khighness10@qq.com, 2020-11-09 13:30:04, 2020-11-09 13:35:49, 2
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a9344f5]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3234474] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1137013089 wrapping com.mysql.cj.jdbc.ConnectionImpl@38cedb7d] will not be managed by Spring
==>  Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? 
==> Parameters: Khighness10(String), 100(Integer), khighness10@qq.com(String), 2020-11-09 13:30:04.0(Timestamp), 2020-11-09 13:59:41.434(Timestamp), 3(Integer), 10(Long), 2(Integer)
<==    Updates: 1   // 插队更新,影响行数为1=>成功
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3234474]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58658f63] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1424043852 wrapping com.mysql.cj.jdbc.ConnectionImpl@38cedb7d] will not be managed by Spring
==>  Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? 
==> Parameters: Khighness10(String), 10(Integer), khighness10@qq.com(String), 2020-11-09 13:30:04.0(Timestamp), 2020-11-09 13:59:41.45(Timestamp), 3(Integer), 10(Long), 2(Integer)
<==    Updates: 0   // 执行更新,影响行数为0=>失败
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58658f63]

🔖分页查询

  • mybatis-plus配置类中增加分页拦截器
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()  {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        /* 分页插件 */
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }

}
  • 直接使用Page对象
    /**
     * 分页查询,使用page
     * 构造函数Page(current, size)
     * current: 页号
     * size: 页面大小
     */
    @Test
    void page() {
        Page<User> userPage = new Page<>(3, 3);
        userMapper.selectPage(userPage, null);
        log.info("总记录数量: {}", userPage.getTotal());
        log.info("第3页结果如下");
        userPage.getRecords().stream().forEach(System.out::println);
    }
# 测试结果
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d5556bf] was not registered for synchronization because synchronization is not active
2020-11-09 15:44:02.969  INFO 6560 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-11-09 15:44:03.107  INFO 6560 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@2068279617 wrapping com.mysql.cj.jdbc.ConnectionImpl@784223e9] will not be managed by Spring
==>  Preparing: SELECT COUNT(1) FROM user
==> Parameters: 
<==    Columns: COUNT(1)
<==        Row: 12
<==      Total: 1
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user LIMIT ?,?
==> Parameters: 6(Long), 3(Long)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 7, UnknownK, 3, unknownk@gmail.@32423.com, 2020-11-09 15:33:38, 2020-11-09 15:33:38, 1
<==        Row: 8, UnknownK, 3, unknownk@gmail.@32423.com, 2020-11-09 15:33:38, 2020-11-09 15:33:38, 1
<==        Row: 9, UnknownK, 3, unknownk@gmail.com, 2020-11-09 13:28:49, 2020-11-09 13:28:49, 1
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d5556bf]
2020-11-09 15:44:03.170  INFO 6560 --- [           main] top.parak.mapper.UserMapperTest          : 总记录数量: 12
2020-11-09 15:44:03.170  INFO 6560 --- [           main] top.parak.mapper.UserMapperTest          : 第3页结果如下
User(id=7, name=UnknownK, age=3, email=unknownk@gmail.@32423.com, createTime=Mon Nov 09 15:33:38 CST 2020, updateTime=Mon Nov 09 15:33:38 CST 2020, version=1)
User(id=8, name=UnknownK, age=3, email=unknownk@gmail.@32423.com, createTime=Mon Nov 09 15:33:38 CST 2020, updateTime=Mon Nov 09 15:33:38 CST 2020, version=1)
User(id=9, name=UnknownK, age=3, email=unknownk@gmail.com, createTime=Mon Nov 09 13:28:49 CST 2020, updateTime=Mon Nov 09 13:28:49 CST 2020, version=1)

📛逻辑删除

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

逻辑删除:不从数据库中移除,通过一个变量使其失效。实质上是更新,防止数据丢失。

  • 数据库添加字段
ALTER TABLE user ADD deleted 
  • 实体类添加属性和注解
    /* 逻辑删除 */
    @TableLogic
    private Integer deleted;
  • Application.properties中增加配置
# 逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

🔍性能分析

3.4.X版本中该插件已经移除。


💤条件构造器

@SpringBootTest
@Slf4j
public class WrapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    void test() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 姓名不为空
        // 邮箱不为空
        // 年龄大于等于18
        wrapper
                .isNotNull("name")
                .isNotNull("email")
                .ge("age", 18);
        userMapper.selectList(wrapper).stream().forEach(System.out::println);
    }

    @Test
    void test2() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name", "RubbishK");
        // 只能查询一个用户,结果多于1个会报错
        System.out.println(userMapper.selectOne(wrapper));
    }

    @Test
    void test3() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 查询年龄在13到19的用户数量
        wrapper.between("age", 13, 19);
        System.out.println(userMapper.selectCount(wrapper));
    }

    @Test
    void test4() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 模糊查询
        wrapper
                .likeRight("name", "K")  // K%
                .likeLeft("email", "@gmail.com");  // %@gmail.com
        userMapper.selectList(wrapper).stream().forEach(System.out::println);
    }

    @Test
    void test5() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 拼接sql语句
        wrapper.inSql("id", "select id from user where id < 3");
        userMapper.selectList(wrapper).stream().forEach(System.out::println);
    }

    @Test
    void test6() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 通过ID降序排序
        wrapper.orderByDesc("id");
        userMapper.selectList(wrapper).stream().forEach(System.out::println);

    }
}

🔰代码生成器

自动生成 entity、mapper、service、controller层的代码

package top.parak.generator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
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;
import java.util.List;

/**
 * <p> Project: Mybatis-plus </P>
 * <p> Package: top.parak.generator </p>
 * <p> FileName: ParaKCode <p>
 * <p> Description: <p>
 * <p> Created By IntelliJ IDEA </p>
 *
 * @author KHighness
 * @since 2020/11/12
 */

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

        // 全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        globalConfig.setOutputDir(projectPath + "/src/main/java");
        // 设置作者名
        globalConfig.setAuthor("KHighness");
        // 操作完成是否打开资源管理器
        globalConfig.setOpen(false);
        // 是否覆盖原有文件
        globalConfig.setFileOverride(false);
        // 去Service的I前缀
        globalConfig.setServiceName("%sService"); 
        globalConfig.setIdType(IdType.ID_WORKER);
        globalConfig.setDateType(DateType.ONLY_DATE);
        autoGenerator.setGlobalConfig(globalConfig);

        // 数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("KAG1823");
        autoGenerator.setDataSource(dataSourceConfig);

        // 包配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setModuleName("test");
        packageConfig.setParent("top.parak");
        packageConfig.setEntity("entity");
        packageConfig.setMapper("mapper");
        packageConfig.setService("service");
        packageConfig.setController("controller");
        autoGenerator.setPackageInfo(packageConfig);

        // 策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        // 映射表名
        strategyConfig.setInclude("user");
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        // Lombok实体类
        strategyConfig.setEntityLombokModel(true);
        // 驼峰命名
        strategyConfig.setRestControllerStyle(true);
        // 逻辑删除
        strategyConfig.setLogicDeleteFieldName("deleted");
        // 创建时间
        TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT);
        // 更新时间
        TableFill gmt_modify = new TableFill("update_time", FieldFill.INSERT);
        List<TableFill> tableFills = new ArrayList<>();
        tableFills.add(gmt_create);
        tableFills.add(gmt_modify);
        strategyConfig.setTableFillList(tableFills);
        // 乐观锁
        strategyConfig.setVersionFieldName("version");
        strategyConfig.setControllerMappingHyphenStyle(true);
        autoGenerator.setStrategy(strategyConfig);

        // 自定义配置
        InjectionConfig injectionConfig = new InjectionConfig() {
            @Override
            public void initMap() {
            }
        };

        // 执行生成
        autoGenerator.execute();
    }
}