Mybatis-Plus 常用操作

MyBatis-Plus系列推薦閱讀順序:


本文目錄結(jié)構(gòu)

一、SQL日志開關(guān)
二、常用注解
三、代碼生成器
四、分頁(yè)查詢
五、Mybatis-Plus Wrapper
六、自動(dòng)填充數(shù)據(jù)功能
七、邏輯刪除
八、樂觀鎖


一、SQL日志開關(guān)

配置文件application.properties,增加最后一行,執(zhí)行時(shí)會(huì)打印出 sql 語(yǔ)句。

spring.application.name=mybatis-plus
# 應(yīng)用服務(wù) WEB 訪問端口
server.port=8080
####數(shù)據(jù)庫(kù)連接池###
spring.datasource.url=jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=guo
spring.datasource.password=205010guo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

####輸出sql日志###
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

類似JPA的日志輸出配置:

jpa:
  show-sql:true#打印SQL。

二、常用注解

注解說明的官方文檔:https://mybatis.plus/guide/annotation.html

2.1【@TableName 】

    @TableName               用于定義表名
注:
    常用屬性:
        value                用于定義表名

2.2【@TableId】

    @TableId                 用于定義表的主鍵
注:
    常用屬性:
        value           用于定義主鍵字段名
        type            用于定義主鍵類型(主鍵策略 IdType)

   主鍵策略:
      IdType.AUTO          主鍵自增,系統(tǒng)分配,不需要手動(dòng)輸入
      IdType.NONE          未設(shè)置主鍵
      IdType.INPUT         需要自己輸入 主鍵值。
      IdType.ASSIGN_ID     系統(tǒng)分配 ID,用于數(shù)值型數(shù)據(jù)(Long,對(duì)應(yīng) mysql 中 BIGINT 類型)。
      IdType.ASSIGN_UUID   系統(tǒng)分配 UUID,用于字符串型數(shù)據(jù)(String,對(duì)應(yīng) mysql 中 varchar(32) 類型)。

2.3【@TableField】

    @TableField            用于定義表的非主鍵字段。
注:
    常用屬性:
        value                用于定義非主鍵字段名
        exist                用于指明是否為數(shù)據(jù)表的字段, true 表示是,false 為不是。
        fill                 用于指定字段填充策略(FieldFill)。
        
    字段填充策略:(一般用于填充 創(chuàng)建時(shí)間、修改時(shí)間等字段)
        FieldFill.DEFAULT         默認(rèn)不填充
        FieldFill.INSERT          插入時(shí)填充
        FieldFill.UPDATE          更新時(shí)填充
        FieldFill.INSERT_UPDATE   插入、更新時(shí)填充。

2.4【@TableLogic】

    @TableLogic           用于定義表的字段進(jìn)行邏輯刪除(非物理刪除)
注:
    常用屬性:
        value            用于定義未刪除時(shí)字段的值
        delval           用于定義刪除時(shí)字段的值

2.5【@Version】

    @Version             用于字段實(shí)現(xiàn)樂觀鎖

三、代碼生成器

3.1 AutoGenerator 簡(jiǎn)介

AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個(gè)模塊的代碼,極大的提升了開發(fā)效率?! ∨c mybatis 中的 mybatis-generator-core 類似。

3.2 添加依賴

        <!--  代碼生成器 依賴-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mp.version}</version>
        </dependency>
        <!-- 添加 模板引擎 依賴 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>

3.3 生成器代碼分析

Step1:
  創(chuàng)建一個(gè) 代碼生成器。用于生成代碼。
  此處不用修改。

// Step1:代碼生成器
AutoGenerator mpg = new AutoGenerator();

Step2:
  配置全局信息。指定代碼輸出路徑,以及包名、作者等信息。
  此處按需添加,projectPath 需要修改,setAuthor 需要修改。

// Step2:全局配置
GlobalConfig gc = new GlobalConfig(); 
// 填寫代碼生成的目錄(需要修改)
String projectPath = "E:\\myProject\\test\\test_mybatis_plus"; 
// 拼接出代碼最終輸出的目錄
gc.setOutputDir(projectPath + "/src/main/java"); 
// 配置開發(fā)者信息(可選)(需要修改)
gc.setAuthor("郭秀志 jbcode@126.com");
 // 配置是否打開目錄,false 為不打開(可選)
gc.setOpen(false); 
// 實(shí)體屬性 Swagger2 注解,添加 Swagger 依賴,開啟 Swagger2 模式(可選)
//gc.setSwagger2(true);
// 重新生成文件時(shí)是否覆蓋,false 表示不覆蓋(可選)
gc.setFileOverride(false);
 // 配置主鍵生成策略,此處為 ASSIGN_ID(可選)
gc.setIdType(IdType.ASSIGN_ID); 
// 配置日期類型,此處為 ONLY_DATE(可選)
gc.setDateType(DateType.ONLY_DATE); 
// 默認(rèn)生成的 service 會(huì)有 I 前綴
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);

Step3:
配置數(shù)據(jù)源信息。用于指定 需要生成代碼的 數(shù)據(jù)倉(cāng)庫(kù)、數(shù)據(jù)表。
setUrl、setDriverName、setUsername、setPassword均需修改。

// Step3:數(shù)據(jù)源配置(需要修改)
DataSourceConfig dsc = new DataSourceConfig(); 
// 配置數(shù)據(jù)庫(kù) url 地址
dsc.setUrl("jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8");
// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定數(shù)據(jù)庫(kù)名 
// 配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// 配置數(shù)據(jù)庫(kù)連接用戶名
dsc.setUsername("root"); 
// 配置數(shù)據(jù)庫(kù)連接密碼
dsc.setPassword("123456");
mpg.setDataSource(dsc);

Step4:
配置包信息。
setParent、setModuleName均需修改。其余按需求修改.

// Step:4:包配置
PackageConfig pc = new PackageConfig();
 // 配置父包名(需要修改)
pc.setParent("com.erbadagang.mybatis.plus"); 
// 配置模塊名(需要修改)
//pc.setModuleName("mybatis-plus-starter");
 // 配置 entity 包名
pc.setEntity("entity"); 
// 配置 mapper 包名
pc.setMapper("mapper"); 
// 配置 service 包名
pc.setService("service"); 
// 配置 controller 包名
pc.setController("controller");
mpg.setPackageInfo(pc);

Step5:
配置數(shù)據(jù)表映射信息。
setInclude 需要修改,其余按實(shí)際開發(fā)修改。

// Step5:策略配置(數(shù)據(jù)庫(kù)表配置)
StrategyConfig strategy = new StrategyConfig(); 
// 指定表名(可以同時(shí)操作多個(gè)表,使用 , 隔開)(需要修改)
strategy.setInclude("t_user"); 
// 配置數(shù)據(jù)表與實(shí)體類名之間映射的策略
strategy.setNaming(NamingStrategy.underline_to_camel); 
// 配置數(shù)據(jù)表的字段與實(shí)體類的屬性名之間映射的策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel); 
// 配置 lombok 模式
strategy.setEntityLombokModel(true); 
// 配置 rest 風(fēng)格的控制器(@RestController)
strategy.setRestControllerStyle(true); 
// 配置駝峰轉(zhuǎn)連字符
strategy.setControllerMappingHyphenStyle(true); 
// 配置表前綴,生成實(shí)體時(shí)去除表前綴 
// 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus,去除前綴后剩下為 user。
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);

t_user建表SQL:

/*
 Navicat Premium Data Transfer

 Source Server         : 上海
 Source Server Type    : MySQL
 Source Server Version : 50636
 Source Host           : 101.133.227.13:3306
 Source Schema         : orders_1

 Target Server Type    : MySQL
 Target Server Version : 50636
 File Encoding         : 65001

 Date: 10/07/2020 16:28:23
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `password` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `pwd_cipher` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

Step6:
  執(zhí)行代碼生成操作。
  此處不用修改。

// Step6:執(zhí)行代碼生成操作
mpg.execute();

完整代碼:

package com.erbadagang.mybatis.plus.mybatisplus;

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.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * AutoGenerationTest作用是:生成Mybatis-plus代碼,AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個(gè)模塊的代碼,極大的提升了開發(fā)效率。
 *
 * @ClassName: AutoGenerationTest
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/10 15:08
 * @Copyright:
 */
@SpringBootTest
public class AutoGenerationTest {
    @Test
    public void autoGenerate() {
        // Step1:代碼生成器
        AutoGenerator mpg = new AutoGenerator();

        // Step2:全局配置
        GlobalConfig gc = new GlobalConfig();
        // 填寫代碼生成的目錄(需要修改)
        String projectPath = "D:\\dev\\GitRepository\\mybatis-plus-starter";
        // 拼接出代碼最終輸出的目錄
        gc.setOutputDir(projectPath + "/src/main/java");
        // 配置開發(fā)者信息(可選)(需要修改)
        gc.setAuthor("郭秀志 jbcode@126.com");
        // 配置是否打開目錄,false 為不打開(可選)
        gc.setOpen(false);
        // 實(shí)體屬性 Swagger2 注解,添加 Swagger 依賴,開啟 Swagger2 模式(可選)
        //gc.setSwagger2(true);
        // 重新生成文件時(shí)是否覆蓋,false 表示不覆蓋(可選)
        gc.setFileOverride(false);
        // 配置主鍵生成策略,此處為 ASSIGN_ID(可選)
        gc.setIdType(IdType.AUTO);
        // 配置日期類型,此處為 ONLY_DATE(可選)
        gc.setDateType(DateType.ONLY_DATE);
        // 默認(rèn)生成的 service 會(huì)有 I 前綴
        gc.setServiceName("I%sService");
        mpg.setGlobalConfig(gc);

        // Step3:數(shù)據(jù)源配置(需要修改)
        DataSourceConfig dsc = new DataSourceConfig();
        // 配置數(shù)據(jù)庫(kù) url 地址
        dsc.setUrl("jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
        // dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定數(shù)據(jù)庫(kù)名
        // 配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        // 配置數(shù)據(jù)庫(kù)連接用戶名
        dsc.setUsername("guo");
        // 配置數(shù)據(jù)庫(kù)連接密碼
        dsc.setPassword("205010guo");
        mpg.setDataSource(dsc);

        // Step:4:包配置
        PackageConfig pc = new PackageConfig();
        // 配置父包名(需要修改)
        pc.setParent("com.erbadagang.mybatis.plus.mybatisplus");
        // 配置模塊名(需要修改)
        //pc.setModuleName("mybatis-plus-starter");
        // 配置 entity 包名
        pc.setEntity("entity");
        // 配置 mapper 包名
        pc.setMapper("mapper");
        // 配置 service 包名
        pc.setService("service");
        // 配置 controller 包名
        pc.setController("controller");
        mpg.setPackageInfo(pc);

        // Step5:策略配置(數(shù)據(jù)庫(kù)表配置)
        StrategyConfig strategy = new StrategyConfig();
        // 指定表名(可以同時(shí)操作多個(gè)表,使用 , 隔開)(需要修改)
        strategy.setInclude("t_user");//表名t_user
        // 配置數(shù)據(jù)表與實(shí)體類名之間映射的策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 配置數(shù)據(jù)表的字段與實(shí)體類的屬性名之間映射的策略
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 配置 lombok 模式
        strategy.setEntityLombokModel(true);
        // 配置 rest 風(fēng)格的控制器(@RestController)
        strategy.setRestControllerStyle(true);
        // 配置駝峰轉(zhuǎn)連字符
        strategy.setControllerMappingHyphenStyle(true);
        // 配置表前綴,生成實(shí)體時(shí)去除表前綴
        // 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus,去除前綴后剩下為 user。
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);

        // Step6:執(zhí)行代碼生成操作
        mpg.execute();
    }

}

3.4 測(cè)試生成的service

由于生成的Service接口及實(shí)現(xiàn)類有些問題,需要稍為改造一下:

  • Service接口:public interface ITUserService extends IService<TUser> 增加泛型:public interface ITUserService<TUser> extends IService<TUser> 。
  • 實(shí)現(xiàn)類:public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements IService<TUser> 實(shí)現(xiàn)接口由IService變成 ITUserService。

Junit 測(cè)試代碼:

    @Autowired
    private ITUserService<TUser> tUserService;

    @Test
    public void testService() {
        TUser user = new TUser();
        user.setUserName("trek");
        user.setPassword("888999");
        user.setPwdCipher("ewifwiEFafe==");
        if (tUserService.save(user)) {
            tUserService.list().forEach(System.out::println);
        } else {
            System.out.println("添加數(shù)據(jù)失敗");
        }
    }

測(cè)試結(jié)果:


表插入新數(shù)據(jù)

控制臺(tái)輸出信息:

==>  Preparing: INSERT INTO t_user ( user_name, password, pwd_cipher ) VALUES ( ?, ?, ? ) 
==> Parameters: trek(String), 888999(String), ewifwiEFafe==(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47acd13b]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e26f1ed] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@633514467 wrapping com.mysql.cj.jdbc.ConnectionImpl@297c9a9b] will not be managed by Spring
==>  Preparing: SELECT id,user_name,password,pwd_cipher FROM t_user 
==> Parameters: 
<==    Columns: id, user_name, password, pwd_cipher
<==        Row: 1, guo, bwMhZeGXyD98aToKQdXLcw==, null
<==        Row: 2, guo, bwMhZeGXyD98aToKQdXLcw==, null
<==        Row: 3, guo, 123456, bwMhZeGXyD98aToKQdXLcw==
<==        Row: 4, trek, 888999, ewifwiEFafe==
<==      Total: 4
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e26f1ed]
TUser(id=1, userName=guo, password=bwMhZeGXyD98aToKQdXLcw==, pwdCipher=null)
TUser(id=2, userName=guo, password=bwMhZeGXyD98aToKQdXLcw==, pwdCipher=null)
TUser(id=3, userName=guo, password=123456, pwdCipher=bwMhZeGXyD98aToKQdXLcw==)
TUser(id=4, userName=trek, password=888999, pwdCipher=ewifwiEFafe==)

四、分頁(yè)查詢

4.1 配置攔截器組件

MybatisPlusApplication啟動(dòng)類添加代碼:

     /**
     * 分頁(yè)插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

4.2 編寫分頁(yè)代碼

直接 new 一個(gè) Page 對(duì)象,對(duì)象需要傳遞兩個(gè)參數(shù)(當(dāng)前頁(yè),每頁(yè)顯示的條數(shù))。
調(diào)用 mybatis-plus 提供的分頁(yè)查詢方法,其會(huì)將 分頁(yè)查詢的數(shù)據(jù)封裝到 Page 對(duì)象中。


    @Test
    public void selectPage() {
        // 根據(jù)Wrapper 自定義條件查詢
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("age", "18");
        queryWrapper.orderByDesc("age");
        Page<User> userPage = new Page<User>(2, 2);
        // userPage.setCurrent(2L);  //當(dāng)前是第幾頁(yè) 默認(rèn)為1
        // userPage.setSize(2);  //每頁(yè)大小
        IPage<User> userIPage = userMapper.selectPage(userPage, queryWrapper);

        System.out.println("當(dāng)前頁(yè)" + userIPage.getCurrent());  //當(dāng)前頁(yè)
        System.out.println("總頁(yè)數(shù)" + userIPage.getPages()); //總頁(yè)數(shù)
        System.out.println("返回?cái)?shù)據(jù)" + userIPage.getRecords());  //返回?cái)?shù)據(jù)
        System.out.println("每頁(yè)大小" + userIPage.getSize());  //每頁(yè)大小
        System.out.println("滿足符合條件的條數(shù)" + userIPage.getTotal());  //滿足符合條件的條數(shù)
        System.out.println("下一頁(yè)" + userPage.hasNext());   //下一頁(yè)
        System.out.println("上一頁(yè)" + userPage.hasPrevious());  //上一頁(yè)
    }

運(yùn)行結(jié)果:
結(jié)果說明

控制臺(tái)System.out.println代碼部分日志輸出:

當(dāng)前頁(yè)2
總頁(yè)數(shù)2
返回?cái)?shù)據(jù)[User(id=4, name=Oliver, age=21, email=xds@erbadagang.com), User(id=2, name=xiu, age=20, email=specialized@erbadagang.com)]
每頁(yè)大小2
滿足符合條件的條數(shù)4
下一頁(yè)false
上一頁(yè)true

五、Mybatis-Plus Wrapper

參考上篇文章:MyBatis-Plus 條件構(gòu)造器(Wrapper)

5.1 刪除

    /**
     * <p>
     * 根據(jù)根據(jù) entity 條件,刪除記錄,QueryWrapper實(shí)體對(duì)象封裝操作類(可以為 null)
     * 下方獲取到queryWrapper后刪除的查詢條件為name字段為null的and年齡大于等于12的and email字段不為null的
     * 同理寫法條件添加的方式就不做過多介紹了。
     * </p>
     */
    @Test
    public void delete() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .isNull("name")
                .ge("age", 12)
                .isNotNull("email");
        int delete = userMapper.delete(queryWrapper);
        System.out.println("delete return count = " + delete);
    }

SQL輸出:

==>  Preparing: DELETE FROM user WHERE (name IS NULL AND age >= ? AND email IS NOT NULL) 
==> Parameters: 12(Integer)
<==    Updates: 0

5.2 selectOne

    /**
     * <p>
     * 根據(jù) entity 條件,查詢一條記錄,
     * 這里和上方刪除構(gòu)造條件一樣,只是seletOne返回的是一條實(shí)體記錄,當(dāng)出現(xiàn)多條時(shí)會(huì)報(bào)錯(cuò)
     * </p>
     */
    @Test
    public void selectOne() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "guo");

        User user = userMapper.selectOne(queryWrapper);
        System.out.println(user);
    }

SQL輸出:

==>  Preparing: SELECT id,name,age,email FROM user WHERE (name = ?) 
==> Parameters: guo(String)
<==    Columns: id, name, age, email
<==        Row: 1, Guo , 18, trek@erbadagang.com
<==      Total: 1

5.3 selectCount

   /**
     * <p>
     * 根據(jù) Wrapper 條件,查詢總記錄數(shù)
     * </p>
     *
     * @param queryWrapper 實(shí)體對(duì)象
     */
    @Test
    public void selectCount() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "guo");

        Integer count = userMapper.selectCount(queryWrapper);
        System.out.println(count);
    }

SQL輸出:

==>  Preparing: SELECT COUNT( 1 ) FROM user WHERE (name = ?) 
==> Parameters: guo(String)
<==    Columns: COUNT( 1 )
<==        Row: 1
<==      Total: 1

5.4 selectList


    /**
     * <p>
     * 根據(jù) entity 條件,查詢?nèi)坑涗?     * </p>
     *
     * @param queryWrapper 實(shí)體對(duì)象封裝操作類(可以為 null)為null查詢?nèi)?     */
    @Test
    public void selectListByEntity() {
        List<User> list = userMapper.selectList(null);//null為無條件

        System.out.println(list);
    }

    /**
     * <p>
     * 根據(jù) Wrapper 條件,查詢?nèi)坑涗?     * </p>
     *
     * @param queryWrapper 實(shí)體對(duì)象封裝操作類(可以為 null)
     */
    @Test
    public void selectListByMapper() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "guo");

        List<User> list = userMapper.selectList(queryWrapper);//null為無條件

        System.out.println(list);
    }

5.5 selectMaps

    @Test
    public void selectMaps() {
        Page<User> page = new Page<User>(1, 5);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        maps.forEach(map -> {
            System.out.println("name-->" + map.get("name"));
            System.out.println("email-->" + map.get("email"));
        });
        System.out.println(maps);
    }

返回類型List<Map<String, Object>>。Map的key為字段名稱,value為對(duì)應(yīng)的字段值。
控制臺(tái)輸出:

==>  Preparing: SELECT id,name,age,email FROM user 
==> Parameters: 
<==    Columns: id, name, age, email
<==        Row: 1, Guo , 18, trek@erbadagang.com
<==        Row: 2, xiu, 20, specialized@erbadagang.com
<==        Row: 3, zhi, 28, giant@erbadagang.com
<==        Row: 4, Oliver, 88, winspace@erbadagang.com
<==        Row: 5, Messi, 24, look@erbadagang.com
<==      Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@294aba23]
name-->Guo 
email-->trek@erbadagang.com
name-->xiu
email-->specialized@erbadagang.com
name-->zhi
email-->giant@erbadagang.com
name-->Oliver
email-->winspace@erbadagang.com
name-->Messi
email-->look@erbadagang.com
[{name=Guo , id=1, age=18, email=trek@erbadagang.com}, {name=xiu, id=2, age=20, email=specialized@erbadagang.com}, {name=zhi, id=3, age=28, email=giant@erbadagang.com}, {name=Oliver, id=4, age=88, email=winspace@erbadagang.com}, {name=Messi, id=5, age=24, email=look@erbadagang.com}]

六、自動(dòng)填充數(shù)據(jù)功能

添加、修改數(shù)據(jù)時(shí),每次都會(huì)使用相同的方式進(jìn)行填充。比如: 數(shù)據(jù)的創(chuàng)建時(shí)間、修改時(shí)間、操作者等。

6.1 數(shù)據(jù)庫(kù)準(zhǔn)備

Mybatis-plus 支持自動(dòng)填充這些字段的數(shù)據(jù)。給之前的數(shù)據(jù)表新增3個(gè)字段:創(chuàng)建時(shí)間、修改時(shí)間、操作人。
SQL語(yǔ)句:

ALTER TABLE `orders_1`.`user` 
ADD COLUMN `create_time` datetime(0) COMMENT '創(chuàng)建時(shí)間' AFTER `email`,
ADD COLUMN `update_time` datetime(0) COMMENT '修改時(shí)間' AFTER `create_time`,
ADD COLUMN `operator` varchar(20) COMMENT '操作人' AFTER `update_time`;

6.2 重新生成代碼

并使用 代碼生成器重新生成代碼,注意修改生成器配置為可覆蓋老代碼。

        // 重新生成文件時(shí)是否覆蓋,false 表示不覆蓋(可選)
        gc.setFileOverride(true);

6.3 修改entity

使用@TableField注解,標(biāo)注需要進(jìn)行填充的字段。

package com.erbadagang.mybatis.plus.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 
 * </p>
 *
 * @author 郭秀志 jbcode@126.com
 * @since 2020-07-11
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class User implements Serializable {

    private static final long serialVersionUID=1929834928304L;

    /**
     * 主鍵ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年齡
     */
    private Integer age;

    /**
     * 郵箱
     */
    private String email;

    /**
     * 創(chuàng)建時(shí)間
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    /**
     * 修改時(shí)間
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    /**
     * 操作人
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String operator;


}

填充策略FieldFill.INSERT_UPDATE表示插入和更新都進(jìn)行自動(dòng)填充。

6.4 自定義MetaObjectHandler

自定義一個(gè)類,實(shí)現(xiàn) MetaObjectHandler 接口,并重寫方法。添加 @Component 注解,交給 Spring 去管理。

package com.erbadagang.mybatis.plus.mybatisplus.handler;

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

import java.util.Date;

/**
 * @description 自定義的數(shù)據(jù)填充handler,分別寫insert和update的寫入策略。
 * @ClassName: MyFillDataMetaObjectHandler
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/11 9:39
 * @Copyright:
 */
@Component
public class MyFillDataMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "operator", String.class, "梅西愛騎車");
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "operator", String.class, "梅西愛騎車");
    }
}

6.5 測(cè)試

6.5.1 插入測(cè)試


    /**
     * 測(cè)試插入的自動(dòng)填充數(shù)據(jù)功能。
     */
    @Test
    public void testAutoFillInsert() {
        User user = new User();
        user.setId(0l);
        user.setName("崔克");
        user.setAge(18);
        user.setEmail("trek@erbadagang.cn");

        int id = userMapper.insert(user);//自動(dòng)返回插入的id

        System.out.println(id);
    }

運(yùn)行測(cè)試用例,如果報(bào)錯(cuò):Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
需要把id列勾上自增。

自增

輸出的SQL信息:

==>  Preparing: INSERT INTO user ( name, age, email, create_time, update_time, operator ) VALUES ( ?, ?, ?, ?, ?, ? ) 
==> Parameters: 崔克(String), 18(Integer), trek@erbadagang.cn(String), 2020-07-11 09:57:43.386(Timestamp), 2020-07-11 09:57:43.388(Timestamp), 梅西愛騎車(String)
<==    Updates: 1
表中數(shù)據(jù)

6.5.2 更新測(cè)試

更新name為英文的trek,age為28。

    /**
     * 測(cè)試更新的自動(dòng)填充數(shù)據(jù)功能。
     */
    @Test
    public void testAutoFillUpdate() {
        User user = new User();
        user.setId(7l);
        user.setName("trek");
        user.setAge(28);
        user.setEmail("trek@erbadagang.cn");

        int id = userMapper.updateById(user);//自動(dòng)返回插入的id

        System.out.println(id);
    }

運(yùn)行測(cè)試。
輸出的SQL信息,只更新了update_time沒更新create_time字段:

==>  Preparing: UPDATE user SET name=?, age=?, email=?, update_time=?, operator=? WHERE id=? 
==> Parameters: trek(String), 28(Integer), trek@erbadagang.cn(String), 2020-07-11 10:05:30.249(Timestamp), 梅西愛騎車(String), 7(Long)
<==    Updates: 1

如果入庫(kù)的時(shí)間跟上面打印的SQL不一致,需要在jdbc連接加入時(shí)區(qū)設(shè)置:
jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

七、邏輯刪除

刪除數(shù)據(jù),可以通過物理刪除,也可以通過邏輯刪除。

  • 物理刪除指的是直接將數(shù)據(jù)從數(shù)據(jù)庫(kù)中刪除,不保留。
  • 邏輯刪除指的是修改數(shù)據(jù)的某個(gè)字段,使其表示為已刪除狀態(tài),而非刪除數(shù)據(jù),保留該數(shù)據(jù)在數(shù)據(jù)庫(kù)中,但是查詢時(shí)不顯示該數(shù)據(jù)(查詢時(shí)過濾掉該數(shù)據(jù))。

7.1 表結(jié)構(gòu)

給數(shù)據(jù)表增加一個(gè)字段:delete_flag,用于表示該數(shù)據(jù)是否被邏輯刪除。
SQL語(yǔ)句:

ALTER TABLE `orders_1`.`user` 
ADD COLUMN `delete_flag` tinyint(1) COMMENT '邏輯刪除(0 未刪除、1 刪除)' AFTER `operator`;

7.3 使用邏輯刪除。

可以定義一個(gè)自動(dòng)填充規(guī)則,初始值為 0。0 表示未刪除, 1 表示刪除。
在Entity類新增:

    /**
     * 邏輯刪除(0 未刪除、1 刪除)
     */
    @TableLogic(value = "0", delval = "1")//定義邏輯刪除功能。
    @TableField(fill = FieldFill.INSERT)//定義在insert的時(shí)候自動(dòng)填充功能
    private Integer deleteFlag;

@TableLogic定義邏輯刪除功能,若去除 TableLogic 注解,再執(zhí)行 Delete 時(shí)進(jìn)行物理刪除,直接刪除這條數(shù)據(jù)。
@TableField定義在自動(dòng)填充功能。

在自動(dòng)填充規(guī)則MyFillDataMetaObjectHandler類的insertFill方法添加:

@Override
public void insertFill(MetaObject metaObject) {
......
    this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0);
}

7.4 測(cè)試

新增一條閃電牌自行車數(shù)據(jù):

        User user = new User();
        user.setId(0l);
        user.setName("閃電");
        user.setAge(18);
        user.setEmail("specialized@erbadagang.cn");

        int id = userMapper.insert(user);//自動(dòng)返回插入的id

delete_flag字段為自動(dòng)填充代碼定義的默認(rèn)值0,當(dāng)然也可以使用數(shù)據(jù)庫(kù)定義默認(rèn)值。


表層面定義的初始值0

新增數(shù)據(jù)delete_flag值:
delete_flag=0

刪除數(shù)據(jù):

    //這次使用IUserService而不是mapper進(jìn)行測(cè)試
    @Autowired
    private IUserService userService;

    /**
     * 邏輯刪除測(cè)試。
     */
    @Test

    public void testDelete() {
        if (userService.removeById(8)) {
            System.out.println("刪除數(shù)據(jù)成功");
            userService.list().forEach(System.out::println);
        } else {
            System.out.println("刪除數(shù)據(jù)失敗");
        }
    }

執(zhí)行測(cè)試,輸出的日志:

==>  Preparing: UPDATE user SET delete_flag=1 WHERE id=? AND delete_flag=0 
==> Parameters: 8(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@22bdb1d0]
刪除數(shù)據(jù)成功

==>  Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag FROM user WHERE delete_flag=0 
==> Parameters: 
<==      Total: 0

可以看到更新delete_flag=1的操作,以及查詢時(shí)自動(dòng)加上了WHERE delete_flag=0的判斷。

表數(shù)據(jù)變化:


delete_flage變?yōu)?

八、樂觀鎖

8.1 基礎(chǔ)知識(shí)

(1)首先認(rèn)識(shí)一下: 讀問題、寫問題
  操作數(shù)據(jù)庫(kù)數(shù)據(jù)時(shí),遇到的最基本問題就是 讀問題與寫問題。
  讀問題 指的是從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)時(shí)遇到的問題,比如:臟讀、幻讀、不可重復(fù)讀。
臟讀、幻讀、不可重復(fù)讀 參考地址
  寫問題 指的是數(shù)據(jù)寫入數(shù)據(jù)庫(kù)時(shí)遇到的問題,比如:丟失更新(多個(gè)線程同時(shí)對(duì)某條數(shù)據(jù)更新,無論執(zhí)行順序如何,都會(huì)丟失其他線程更新的數(shù)據(jù))

(2)如何解決寫問題?
  樂觀鎖、悲觀鎖就是為了解決 寫問題而存在的。
    樂觀鎖:總是假設(shè)最好的情況,每次讀取數(shù)據(jù)時(shí)認(rèn)為數(shù)據(jù)不會(huì)被修改(即不加鎖),當(dāng)進(jìn)行更新操作時(shí),會(huì)判斷這條數(shù)據(jù)是否被修改,未被修改,則進(jìn)行更新操作。若被修改,則數(shù)據(jù)更新失敗,可以對(duì)數(shù)據(jù)進(jìn)行重試(重新嘗試修改數(shù)據(jù))。
    悲觀鎖:總是假設(shè)最壞的情況,每次讀取數(shù)據(jù)時(shí)認(rèn)為數(shù)據(jù)會(huì)被修改(即加鎖),當(dāng)進(jìn)行更新操作時(shí),直接更新數(shù)據(jù),結(jié)束操作后釋放鎖(此處才可以被其他線程讀?。?。

(3)樂觀鎖、悲觀鎖使用場(chǎng)景?
  樂觀鎖一般用于讀比較多的場(chǎng)合,盡量減少加鎖的開銷。
  悲觀鎖一般用于寫比較多的場(chǎng)合,盡量減少 類似 樂觀鎖重試更新引起的性能開銷。

(4)樂觀鎖兩種實(shí)現(xiàn)方式
方式一:通過版本號(hào)機(jī)制實(shí)現(xiàn)。
  在數(shù)據(jù)表中增加一個(gè) version 字段。
  取數(shù)據(jù)時(shí),獲取該字段,更新時(shí)以該字段為條件進(jìn)行處理(即set version = newVersion where version = oldVersion),若 version 相同,則更新成功(給新 version 賦一個(gè)值,一般加 1)。若 version 不同,則更新失敗,可以重新嘗試更新操作。

方式二:通過 CAS 算法實(shí)現(xiàn)。
  CAS 為 Compare And Swap 的縮寫,即比較交換,是一種無鎖算法(即在不加鎖的情況實(shí)現(xiàn)多線程之間的變量同步)。
  CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存值(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存地址里面的值 V 和 A 的值是一樣的,那么就將內(nèi)存里面的值更新成B。若 V 與 A 不一致,則不執(zhí)行任何操作(可以通過自旋操作,不斷嘗試修改數(shù)據(jù)直至成功修改)。即 V == A ? V = B : V = V。
  CAS 可能導(dǎo)致 ABA 問題(兩次讀取數(shù)據(jù)時(shí)值相同,但不確定值是否被修改過),比如兩個(gè)線程操作同一個(gè)變量,線程 A、線程B 初始讀取數(shù)據(jù)均為 A,后來 線程B 將數(shù)據(jù)修改為 B,然后又修改為 A,此時(shí)線程 A 再次讀取到的數(shù)據(jù)依舊是 A,雖然值相同但是中間被修改過,這就是 ABA 問題??梢约右粋€(gè)額外的標(biāo)志位 C,用于表示數(shù)據(jù)是否被修改。當(dāng)標(biāo)志位 C 與預(yù)期標(biāo)志位相同、且 V == A 時(shí),則更新值 B。

(5)mybatis-plus 實(shí)現(xiàn)樂觀鎖(通過 version 機(jī)制)
實(shí)現(xiàn)思路:
  Step1:取出記錄時(shí),獲取當(dāng)前version
  Step2:更新時(shí),帶上這個(gè)version
  Step3:執(zhí)行更新時(shí), set version = newVersion where version = oldVersion
  Step4:如果version不對(duì),就更新失敗

(6)mybatis-plus 代碼實(shí)現(xiàn)樂觀鎖

8.2 MP實(shí)現(xiàn)樂觀鎖

配置樂觀鎖插件。
啟動(dòng)類MybatisPlusApplication新增如下代碼(類似分頁(yè)插件),將 OptimisticLockerInterceptor通過@Bean交給 Spring 管理。

/**
 * 樂觀鎖插件
 * @return 樂觀鎖插件的實(shí)例
 */
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    return new OptimisticLockerInterceptor();
}

8.3 定義一個(gè)數(shù)據(jù)庫(kù)字段 version

ALTER TABLE `orders_1`.`user` 
ADD COLUMN `version` int COMMENT '版本號(hào)(用于樂觀鎖, 默認(rèn)為 1)' AFTER `delete_flag`;

8.4 實(shí)體類

使用@Version注解標(biāo)注對(duì)應(yīng)的實(shí)體類??梢酝ㄟ^@TableField進(jìn)行數(shù)據(jù)自動(dòng)填充。

/**
 * 版本號(hào)(用于樂觀鎖, 默認(rèn)為 1)
 */
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;

8.5 自動(dòng)填充規(guī)則

在自動(dòng)填充規(guī)則MyFillDataMetaObjectHandler類的insertFill方法添加:

@Override
public void insertFill(MetaObject metaObject) {
......
    //樂觀鎖version初始化值為1
    this.strictInsertFill(metaObject, "version", Integer.class, 1);
}

8.6 測(cè)試

    /**
     * 樂觀鎖測(cè)試
     */
    @Test
    public void testVersion() {
        User user = new User();
        user.setName("Look");
        user.setAge(8);
        user.setEmail("look@erbadagang.cn");
        userService.save(user);//新增數(shù)據(jù)
        userService.list().forEach(System.out::println);//查詢數(shù)據(jù)

        user.setName("梅花");
        userService.update(user, null);//修改數(shù)據(jù)
        userService.list().forEach(System.out::println);//查詢數(shù)據(jù)
    }

運(yùn)行結(jié)果(語(yǔ)句增加了我的注釋):

##插入數(shù)據(jù),version=1
==>  Preparing: INSERT INTO user ( name, age, email, create_time, update_time, operator, delete_flag, version ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? ) 
==> Parameters: Look(String), 8(Integer), look@erbadagang.cn(String), 2020-07-11 12:03:50.712(Timestamp), 2020-07-11 12:03:50.715(Timestamp), 梅西愛騎車(String), 0(Integer), 1(Integer)
<==    Updates: 1
##查詢數(shù)據(jù),讀取的version為1
==>  Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag,version FROM user WHERE delete_flag=0 
==> Parameters: 
<==    Columns: id, name, age, email, create_time, update_time, operator, delete_flag, version
<==        Row: 9, Look, 8, look@erbadagang.cn, 2020-07-11 12:03:51, 2020-07-11 12:03:51, 梅西愛騎車, 0, 1
<==      Total: 1
##更新數(shù)據(jù),條件是version=1如果此時(shí)被其他程序更新了,這里條件不滿足不會(huì)更新數(shù)據(jù)。
##version的值自動(dòng)+1,現(xiàn)在是2。
==>  Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, operator=?, version=? WHERE delete_flag=0 AND (version = ?) 
==> Parameters: 梅花(String), 8(Integer), look@erbadagang.cn(String), 2020-07-11 12:03:50.712(Timestamp), 2020-07-11 12:03:50.715(Timestamp), 梅西愛騎車(String), 2(Integer), 1(Integer)
<==    Updates: 1
##再次查詢version為2。
==>  Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag,version FROM user WHERE delete_flag=0 
==> Parameters: 
<==    Columns: id, name, age, email, create_time, update_time, operator, delete_flag, version
<==        Row: 9, 梅花, 8, look@erbadagang.cn, 2020-07-11 12:03:51, 2020-07-11 12:03:51, 梅西愛騎車, 0, 2
<==      Total: 1
##查詢出來的最新數(shù)據(jù),delete_flag=0
User(id=9, name=梅花, age=8, email=look@erbadagang.cn, createTime=Sat Jul 11 12:03:51 CST 2020, updateTime=Sat Jul 11 12:03:51 CST 2020, operator=梅西愛騎車, deleteFlag=0, version=2)

底線


本文源代碼使用 Apache License 2.0開源許可協(xié)議,可從Gitee代碼地址通過git clone命令下載到本地或者通過瀏覽器方式查看源代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容