Mybatis-Plus的填坑之路 - Lynwood/wunian7yulian

Mybatis-Plus 我來填坑~

簡介:

​ 此Demo 主要應用SpringBoot 來展現Mybatis-Plus 特性, 以及在開發過程當中可能應用到的插件的演示。java

源碼:https://github.com/wunian7yulian/MybatisPlusDemomysql

本文:https://wunian7yulian.github.io/MybatisPlusDemo/git



  • 目的:sql

    主要藉此作爲突破口, 一是將自我學習成文記錄下來, 二是將Demo 慢慢作成一個本身或者面向大衆的後端腳手架工具。數據庫

  • 規劃apache

    分享-實踐-填坑-總結-腳手架-分享-實踐......windows

目錄

1、簡單介紹

mybatis-plus圖像

官方說明 :

Mybatis-Plus(簡稱MP)是一個Mybatis的加強工具,在 Mybatis 的基礎上只作加強不作改變,爲簡化開發而生!

  • 潤物無聲

    只作加強不作改變,引入它不會對現有工程產生影響。

  • 效率至上

    只需簡單配置,便可快速進行 CRUD 操做,從而節省大量時間。

  • 豐富功能

    熱加載、代碼生成、分頁、性能分析等功能包羅萬象。

成績:

MyBatis-Plus 榮獲【2018年度開源中國最受歡迎的中國軟件】 TOP5 ​

最新版本:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.0.7.1</version>
</dependency>

開發層面MyBatis-Plus特點

  1. 代碼生成
  2. 條件構造器

Mybatis-Plus中的Plus

like官方簡介說明 MP 和Mybatis 就像是 遊戲中的p1 和p2 同樣 兄弟搭配 幹活不累 、

我在使用中明顯感受到 其實他更像是 馬里奧和蘑菇 吃了蘑菇 咱們跳的高度更高了一些。

2、MP的特性

  • 無侵入:只作加強不作改變,引入它不會對現有工程產生影響,如絲般順滑
  • 損耗小:啓動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操做
  • 強大的 CRUD 操做:內置通用 Mapper、通用 Service,僅僅經過少許配置便可實現單表大部分 CRUD 操做,更有強大的條件構造器,知足各種使用需求
  • 支持 Lambda 形式調用:經過 Lambda 表達式,方便的編寫各種查詢條件,無需再擔憂字段寫錯
  • 內置代碼生成器:採用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
  • 內置分頁插件:基於 MyBatis 物理分頁,開發者無需關心具體操做,配置好插件以後,寫分頁等同於普通 List 查詢
  • 內置全局攔截插件:提供全表 delete 、 update 操做智能分析阻斷,也可自定義攔截規則,預防誤操做
  • 支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB二、H二、HSQL、SQLite、Postgre、SQLServer200五、SQLServer 等多種數據庫
  • 支持主鍵自動生成:支持多達 4 種主鍵策略(內含分佈式惟一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
  • 支持 XML 熱加載Mapper 對應的 XML 支持熱加載,對於簡單的 CRUD 操做,甚至能夠無 XML 啓動

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類便可進行強大的 CRUD 操做
  • 支持自定義全局通用操做:支持全局通用方法注入( Write once, use anywhere )
  • 支持關鍵詞自動轉義:支持數據庫關鍵詞(order、key......)自動轉義,還可自定義關鍵詞
  • 內置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啓用該功能,能快速揪出慢查詢
  • 內置 Sql 注入剝離器:支持 Sql 注入剝離,有效預防 Sql 注入攻擊

3、MP框架結構

框架

4、簡單的入門Demo(Mysql)

Demo代碼地址:

https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/simpledemo

Demo 環境:

​ windows 7

​ jdk 1.8.0.40

​ idea Ultimate

​ maven 3.3.9

初始化:

數據庫:MySql

建立數據庫mp_demo_db設置字符集 utf-8

DDL:
-- 建立簡單表格
DROP TABLE IF EXISTS user;

CREATE TABLE `user` (
  `id` bigint(20) PRIMARY KEY AUTO_INCREMENT  COMMENT '主鍵ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年齡',
  `email` varchar(50) DEFAULT NULL COMMENT '郵箱'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DML:
-- 初始化數據
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');

工程:

​ 爲了方便快捷 選用 SpringBoot 工程做爲Demo支撐

第一步、建立工程

1546839886488

輸入項目包名 並添加mysql模塊 建立完畢。

第二步、引入依賴座標
<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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</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.7.1</version>
        </dependency>
        <!--手動添加模板引擎-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.20</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.0.6</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <version>3.0.7.1</version>
        </dependency>
第三步、配置數據源

application.yml 配置文件中添加相關配置:

# DataSource Config
spring:
  datasource:
    # 這裏若是有錯誤是由於 maven mysql包 選擇了 runtime 形式的 scope  能夠不用管它 繼續下一步就好
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf8
    username: root
    password: 123456
第四步、添加mybatis掃描位置
@SpringBootApplication
@MapperScan("com.lynwood.mp.simpledemo.mapper")
public class SimpledemoApplication {

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

}
第五步、pojo及mapper

pojo:

import lombok.Data;

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

mapper:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lynwood.mp.simpledemo.model.User;

public interface UserMapper extends BaseMapper<User> {

}
第六步、測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        List<User> userList = userMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
    }

}

運行結果:

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='Bill', age=24,email='test5@baomidou.com'}

5、核心功能

核心一-簡便之-代碼生成器(AutoGenerator)

​ AutoGenerator 是 MyBatis-Plus 的代碼生成器,經過 AutoGenerator 能夠快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提高了開發效率。

Demo代碼地址:

https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/simpledemo

Demo 環境:

​ 同上

工程:

第一步、建立模塊

​ 建立了autogenerator_demo模塊(記得添加mysql模塊 )做爲演示代碼生成器功能配置。

第二步、引入依賴座標
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.7.1</version>
</dependency>
<!--手動添加模板引擎-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.20</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.0.6</version>
</dependency>

注意:MP 3.0.3 以後移除了自動模板引擎依賴,須要手動添加對應模板引擎

第三步、編寫代碼生成器

​ 直接複製就行啦!

package com.lynwood.mp.autogenerator_demo;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

//注意引入GlobalConfig 使用 import com.baomidou.mybatisplus.generator.config.*;

public class GeneratorCode {
    /**
     * 讀取控制檯內容
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("請輸入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("請輸入正確的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代碼生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("Lynwood");
        gc.setOpen(false);
        mpg.setGlobalConfig(gc);

        // 數據源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp_demo_db?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模塊名"));
        pc.setParent("com.lynwood.mp.autogenerator_demo");
        mpg.setPackageInfo(pc);

        // 自定義配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 若是模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 若是模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定義輸出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定義配置會被優先輸出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定義輸出文件名
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定義輸出模板
        // templateConfig.setEntity();
        // templateConfig.setService();
        // templateConfig.setController();

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setSuperEntityClass(null);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setSuperControllerClass(null);
        strategy.setInclude(scanner("表名"));
        strategy.setSuperEntityColumns(null);
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}
第四步、運行測試

1546926876784

在控制檯輸入:

1546926951091

就能夠生成代碼啦!

都包含 :

1546927063917

發現問題: 若是是多模塊項目 生成的文件會直接到了父項目目錄下

​ 緣由是:在代碼的全局配置中 String projectPath = System.getProperty("user.dir"); 獲取Working Directory時 返回的是項目路徑,並不是模塊路徑!

解決方法:

​ 咱們能夠設定運行參數選項

1546927538730

Working Directory 調整爲 當前模塊目錄 再次運行就ok了!

  • 由於沒有引入mvc 模塊 以致於@Controller 會飄紅 再Demo中就沒有將生成代碼傳入 大可拉取代碼本地使用!

  • 相關代碼生成器的配置:官方生成器配置 可配置項過多沒法詳細介紹 有相關使用會說起

核心二 - 清晰之-CRUD接口

Demo代碼地址:

https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/mp_crud_demo

​ Mybatis-Plus 爲咱們提供了豐富的 增刪改查接口

​ 咱們能夠分爲三類 Mapper的CRUD接口Service的CRUD接口mapper層選裝件接口

1546932099260

​ 確實比較豐富 , 下面會以具備表明性的例子來使用 演示 做爲Demo主要內容

工程:

第一步、建立模塊

​ 建立mp_crud_demo模塊 選擇mysql

第二步、引入依賴座標

​ 由於須要生成表對應Pojo 還須要代碼生成器

​ 而後咱們將上面的直接拷貝一下

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.7.1</version>
</dependency>
<!--手動添加模板引擎-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.20</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.0.6</version>
</dependency>

​ 再添加 lombok 依賴(因:生成代碼中的實體默認是使用@Data 等註解的)

<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <optional>true</optional>
</dependency>
第三步、生成代碼
  • 複製上一Demo模塊生成器代碼
  • 更改模塊名稱使之對應 pc.setParent("com.lynwood.mp.mp_crud_demo");
  • 設置Working Directory當前模塊 防止文件輸出位置錯誤
  • 運行獲取代碼
  • 刪除沒必要要用到的controller層
第四步、其餘配置須要
  • 設定mybatis掃描位置

    MpCrudDemoApplication添加註解@MapperScan("com.lynwood.mp.mp_crud_demo.*.mapper**")

    注意 掃描包的位置

  • 配置數據源

    # DataSource Config
    spring:
      datasource:
      # 這裏若是有錯誤是由於 maven mysql包 選擇了 runtime 形式的 scope  能夠不用管它 繼續下一步就好
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf-8
        username: root
        password: 123456
第五步、對MP探個究竟!

userMapper做爲範例

  • 查看UserMapper:

    打開UserMapper.java源代碼:

    public interface UserMapper extends BaseMapper<User> {
    }

    不難發現它繼承了BaseMapper<User> 接口

    打開com.baomidou.mybatisplus.core.mapper.BaseMapper<T>

    查看當前接口的結構(Structure):

    1547002387987

    原來是MP 將以前的mybatis裏面每一個mapper的全部方法 通過泛型進行提煉到了一個BaseMapper 接口中,咱們只須要將本身的mapper 繼承此接口且將泛型指定即可得到強大的CRUD功能!

  • 再去查看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.lynwood.mp.mp_crud_demo.business.mapper.UserMapper">

</mapper>

徹底丟棄了Mybatis中字段映射以及一段段複雜的配置!


發現問題: 我雖然看到了方法 可是並無一條SQL 那麼它是怎樣作到查詢的呢?
探索:
  • 首先 在項目搭建的時候添加了 座標:
<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.7.1</version>
        </dependency>

咱們進入mybatis-plus-boot-starter的pom 發現他幫咱們裝配了對應版本的mybatis-plus 而且依賴了其餘 例如: spring-boot-autoconfigure``spring-boot-starter-jdbc等;

由於瞭解SpringBoot配置入口是一個@Configuration

那麼打開mybatis-plus-boot-starterjar看到:MybatisPlusAutoConfiguration.java:

...
public class MybatisPlusAutoConfiguration {
 ...
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
     ...
         
        if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false, false).length > 0) {
            ISqlInjector iSqlInjector = (ISqlInjector)this.applicationContext.getBean(ISqlInjector.class);
            globalConfig.setSqlInjector(iSqlInjector);
        }

        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
 }
...
}

看到它在設置sqlSessionFactory的時候爲咱們指定了一個com.baomidou.mybatisplus.core.injector.ISqlInjector.class 做爲factory.globalConfig.sqlInjector(SQL注入器)

而後 咱們打開:ISqlInjector的實現類AbstractSqlInjector

...
    public abstract class AbstractSqlInjector implements ISqlInjector {
        ...
            public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
            ...
            List<AbstractMethod> methodList = this.getMethodList();
            Assert.notEmpty(methodList, "No effective injection method was found.", new Object[0]);
       
            methodList.forEach((m) -> {
                m.inject(builderAssistant, mapperClass);
            });
            ...
        }
    }

methodList 是全部的方法的一個集合 其元素類型都是AbstractMethod.class類型的, 而且對每一個方法都進行了inject(...),

那麼查看inject()方法源碼:

public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
   ...
        this.injectMappedStatement(mapperClass, modelClass, tableInfo);
    }
}

原來最終調用injectMappedStatement()方法

然而

public abstract MappedStatement injectMappedStatement(Class<?> var1, Class<?> var2, TableInfo var3);

抽象的 須要本身的子類去實現

1547011199397

咱們其中一個 SelectById.class 做爲例子查看

public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
        SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty()), Object.class);
        return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo);
    }

再去查看 SqlMethod.SELECT_BY_ID;:

SELECT_BY_ID("selectById", "根據ID 查詢一條數據", "SELECT %s FROM %s WHERE %s=#{ %s}"),

soga~

結束 最後實際上 MP將SQL語句 封裝了固定的模板 com.baomidou.mybatisplus.core.enums.SqlMethod 從而提供給了咱們便捷的使用!

第六步、使用它

​ 咱們使用非Wrapper的具備表明性的接口做爲Demo的演示

​ Wrapper 在後面會有單獨的Demo演示

Mapper - 簡單的CRUD:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MpCrudDemoApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void simplenessMapperCURD() {
        //增長
        User addUser = new User();
        addUser.setAge(18);
        addUser.setEmail("wunian_@hotmail.com");
        addUser.setName("Lynwood");
        userMapper.insert(addUser); // insert 以後是將id裝配到實體對象裏的
        System.out.println("add:\n" + addUser);
        // User(id=1082883152404103169, name=Lynwood, age=18, email=wunian_@hotmail.com)
        // id = 1082883152404103169  之因此這麼長是由於  MP底層給咱們本身以uuid 的形式添加了 user對象的id屬性

        //修改
        User updateUser = new User();
        updateUser.setId(1082883152404103169L);
        updateUser.setName("ok?");
        userMapper.updateById(updateUser);
        System.out.println("update:\n" + updateUser);
        //  User(id=1082883152404103169, name=ok?, age=null, email=null)
        // 刷新數據庫 更改爲功 可是沒有講其餘對象進行裝配

        //查詢
        User selectUser = userMapper.selectById(1082883152404103169L);
        System.out.println("select:\n" + selectUser);
        //User(id=1082883152404103169, name=ok?, age=18, email=wunian_@hotmail.com)

        //刪除
        int i = userMapper.deleteById(1082883152404103169L);
        if (i==1){
            System.out.println("delete:\n" + "刪除成功!");
            //刷新庫 刪除成功
        }
    }
}
發現問題: 雖然MP替我生成了 uuid 做爲主鍵,可是仍是想用數據庫自增形式主鍵怎麼辦?
解決:

MP提供的主鍵策略有:

  • AUTO 數據庫ID自增
  • INPUT 用戶輸入ID
  • ID_WORKER 全局惟一ID,Long類型的主鍵
  • ID_WORKER_STR 字符串全局惟一ID
  • UUID 全局惟一ID,UUID類型的主鍵
  • NONE 該類型爲未設置主鍵類型

MP的主鍵策略默認使用的是ID_WORKER (詳情:https://mybatis.plus/config/#idtype)

在User 中設定 id字段@TableId(type = IdType.AUTO)

或者 全局設置 使用主鍵策略,在yaml 添加:

mybatis-plus:
  global-config:
    db-config:
      id-type: auto

重要: 由於剛纔的測試插入了id爲:1082883152404103169的 數據 咱們須要將自增序列首先恢復正常! 不然下一個id爲1082883152404103170 看上去也是亂的!! 當心坑哈~

delete from user;
alter table user auto_increment= 1;

嘗試 增長操做 輸出:

add:
User(id=1, name=Lynwood, age=18, email=wunian_@hotmail.com)
Mapper - 批量的CRUD接口:

​ 填充測試數據:

DELETE FROM user;
INSERT INTO user ( name, age, email) VALUES
( 'Lynwood',18,'wunian_@hotmail.com')
( 'Jone', 18, 'test1@baomidou.com'),
( 'Jack', 20, 'test2@baomidou.com'),
( 'Tom', 28, 'test3@baomidou.com'),
( 'Sandy', 21, 'test4@baomidou.com'),
( 'Billie', 24, 'test5@baomidou.com');

測試:

@Test
    public void batchMapperCURD() {
        // 多id 查詢
        List<Long> idList = new ArrayList<>();
        idList.add(1L);
        idList.add(3L);
        List<User> userList = userMapper.selectBatchIds(idList);// id的多個查詢
        System.out.println("selectBatch:" );
        userList.forEach(System.out::println);
        //User(id=1, name=Lynwood, age=18, email=wunian_@hotmail.com)
        //User(id=3, name=Jack, age=20, email=test2@baomidou.com)

        // 多條件  查詢
        Map<String,Object> stringObjectMap = new HashMap<>();
        stringObjectMap.put("age",18);
        stringObjectMap.put("id",2);
        List<User> selectByMap = userMapper.selectByMap(stringObjectMap);// 字段-值 鍵值對集合 做爲 '且' 關係
        System.out.println("selectByMap:" );
        selectByMap.forEach(System.out::println);
        //User(id=2, name=Jone, age=18, email=test1@baomidou.com)
        
        // 多id 刪除
        int deleteCount = userMapper.deleteBatchIds(idList);// id的多個刪除
        System.out.println("deleteBatch:\n"+ deleteCount );
        // 2
        
        // 多條件 刪除
        int deleteCount2 = userMapper.deleteByMap(stringObjectMap);// id的多個查詢
        System.out.println("deleteByMap:\n"+ deleteCount2 );
        // 1
    }

​ 注意使用*ByMap() 方法時 條件是 且關係就ok了!

Mapper - 選裝組件:

查看了MP做者說的:

1547021955044

中的 案例 以及 源碼註釋 以後

做者在源碼註釋中是這麼寫的....:

/**
 * <p> 批量新增數據,自選字段 insert </p>
 * <p> 不一樣的數據庫支持度不同!!!  只在 mysql 下測試過!!!  只在 mysql 下測試過!!!  只在 mysql 下測試過!!! </p>
 * <p> 除了主鍵是 <strong> 數據庫自增的未測試 </strong> 外理論上均可以使用!!! </p>
 * <p> 若是你使用自增有報錯或主鍵值沒法回寫到entity,就不要跑來問爲何了,由於我也不知道!!! </p>
 * <p>
 * 本身的通用 mapper 以下使用:
 * int insertBatchSomeColumn(List<T> entityList);
 *
 * <li> 注意1: 不要加任何註解 !! </li>
 * <li> 注意2: 自選字段 insert !!,若是個別字段在 entity 裏爲 null 可是數據庫中有配置默認值, insert 後數據庫字段是爲 null 而不是默認值 </li>
 *
 * <p>
 * 經常使用的構造入參:
 * </p>
 */

┓( ´∀` )┏ ~

而後分析其做用 以爲既然有wrapper的強大條件構造器 決定再也不分析 想了解能夠點擊:

源碼註釋

不過 案例 說明了MP的一些能夠本身擴展的一個流程 :

  1. 第一步: 在UserMapper添加方法

    /** 清空表數據 */
    void clearTable();
  2. 第二步:自定義實現

    在於business同級下建立mp_injector/methods目錄並建立類名CLearTable.java 要與添加的方法名 相同!

    且要實現com.baomidou.mybatisplus.core.injector.AbstractMethod 完成自定義擴展

    public class ClearTable extends AbstractMethod {
        @Override
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            /* 執行 SQL */
            String sql = "delete from " + tableInfo.getTableName();
            /* mapper 接口方法名一致 */
            String method = "clearTable";
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
        }
    }
  3. 第三步:將自定義實現 添加到MP方法列表

    mp_injector下建立MySqlInjector.java 來進行對MP的擴展操做:

    須要繼承com.baomidou.mybatisplus.core.injector.DefaultSqlInjector 而且重寫 MP獲取方法列表的方法

    @Component
    public class MySqlInjector extends DefaultSqlInjector {
        @Override
        public List<AbstractMethod> getMethodList() {
            List<AbstractMethod> methodList = super.getMethodList();
            //增長了 自定義方法
            methodList.add(new ClearTable());
            return methodList;
        }
    }
  4. 第四步:測試

    @Test
    public void myInjectorMapperCURD() {
        userMapper.clearTable();
    }

    查庫 所有刪除了

  5. 總結:

    不難發現其實際上就是 在上文 對MP探個究竟 中探索到的 全部方法的原理

Service - CRUD:

​ 非Wrapper部分基本與Mapper 接口一致 只是將接口提到了service層.

​ 略~

核心三 - 強大之 -Wrapper條件構造器

Demo代碼地址:

https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/mp_wrapper_demo

工程:

第一 -->第四步 同上一工程步驟
  • 注意更改生成器中的 包名模塊名

  • 複製yaml 時注意增長 MP主鍵策略配置:

mybatis-plus:
  global-config:
    db-config:
      id-type: auto
第五步、探索Wrapper

​ 備:關於 java8 lambda 表達式 默認爲熟悉

Wrapper含義:

1547101878214

版本不一樣:

由於MP 對外已經有了兩個大的版本 2.x 和3.x版本

在2.x版本中 EntityWrapper做爲Wrapper 的主要繼承實現,

例:

EntityWrapper<User> ew = new EntityWrapper<User>();
    ew.setEntity(new User(1));
    ew.where("user_name={0}", "'zhangsan'").and("id=1")
            .orNew("user_status={0}", "0").or("status=1")
            .notLike("user_nickname", "notvalue")
            .andNew("new=xx").like("hhh", "ddd")
            .andNew("pwd=11").isNotNull("n1,n2").isNull("n3")
            .groupBy("x1").groupBy("x2,x3")
            .having("x1=11").having("x3=433")
            .orderBy("dd").orderBy("d1,d2");
    System.out.println(ew.getSqlSegment());

實際上 此包裝其實是使用的是 數據庫字段 不是Pojo 裏面的成員變量

在3.x 升級中對Wrapper進行了改動:

  • 全面支持了jdk8的Lambda的使用

  • Wrapper<T>實現類的改動

    1.EntityWrapper<T>改名爲QueryWrapper<T>
    2.新增一個實現類UpdateWrapper<T>用於update方法

  • BaseMapper<T>的改動

    1.去除了insertAllColumn(T entity)方法
    2.去除了updateAllColumn(T entity)方法
    3.新增update(T entity, Wrapper<T> updateWrapper)方法

在3.x 中將 全部的操做劃分紅 查詢QueryWrapperUpdateWrapper 而且將其共有的方法作了一層抽離AbstractWrapper

3.x Wrapper主要繼承結構:

1547103652530

咱們查看四個實現類:QueryWrapperUpdateWrapperLambdaQueryWrapperLambdaUpdateWrapper 的一個整體抽象: AbstractWrapper

它將共有的提高到這一層並作了實現,

其實主要是對SQL語言全部DML語句中公用的一些關鍵字作了統一的接口

AbstractWrapper接口:

1547104354429

AbstractWrapper接口:

1547104548508

UpdateWrapper接口:

1547104637008

第六步、使用接口
##打印SQL日誌配置

爲了方便查看最後MP封裝轉換的最終SQL,在yml配置文件中添加配置:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
一> 編寫AbstractWrapper測試類:

Demo 中有 連接:https://github.com/wunian7yulian/MybatisPlusDemo/blob/master/mp_wrapper_demo/src/test/java/com/lynwood/mp/mp_wrapper_demo/MpWrapperDemoApplicationAbstractWrapperTests.java

...
@RunWith(SpringRunner.class)
@SpringBootTest
public class MpWrapperDemoApplicationAbstractWrapperTests {
    @Autowired
    private UserMapper userMapper;

    /**
     *  測試 allEq()  等同於 WHERE name = ? AND age = ?
     *  +
     *  使用 selectList() 返回多個指定類型對象 的集合
     *
     */
    @Test
    public void abstractWrapperTest_allEq() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        HashMap<String,Object> param = new HashMap<>();
        param.put("name","Lynwood");
        param.put("age","18");
        userQueryWrapper.allEq(param);
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
         *    ==>  Preparing: SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
         *    ==> Parameters: Lynwood(String), 18(String)
         */
    }

    /**
     *  測試 eq()  等同於 WHERE name = ?
     *  +
     *  使用selectOne()
     *      當返回多條會報錯
     */
    @Test
    public void abstractWrapperTest_eq() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("name","Lynwood");
        User user = userMapper.selectOne(userQueryWrapper);
        System.out.println(user);
        /**
         * 輸出:
         *           Preparing: SELECT id,name,age,email FROM user WHERE name = ?
         *           Parameters: Lynwood(String)
         */
    }

    /**     not equals 縮寫 ~~
     *
     *  測試 ne()  等同於 WHERE age <> ?
     *  +
     *  使用 selectObjs() 返回多個 非指定類型對象 的集合
     *
     */
    @Test
    public void abstractWrapperTest_ne() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.ne("age", 18);
        List<Object> objectList = userMapper.selectObjs(userQueryWrapper);
        objectList.forEach(System.out::println);
        /**
         * 輸出:
         *    ==>  Preparing: SELECT id,name,age,email FROM user WHERE age <> ?
         *      ==> Parameters: 18(Integer)
         */
    }

    /***************************************************** 多接口連用 * 默認AND 關係*******************************/
    /**     測試
     *      gt() : greater than       等同於     >
     *      ge() : greater equals     等同於     >=
     *      lt() : less than          等同於     <
     *      le() : less equals        等同於     <=
     *
     *  使用 selectMaps() 返回多個 指定到Map作封裝 的集合
     *
     */
    @Test
    public void abstractWrapperTest_gt_ge_lt_le() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.ge("age", 21);
        userQueryWrapper.le("age", 24);
        List<Map<String, Object>> mapList = userMapper.selectMaps(userQueryWrapper);
        mapList.forEach(System.out::println);
        /**
         * 輸出:
         *      ==>  Preparing: SELECT id,name,age,email FROM user WHERE age >= ? AND age <= ?
         *      ==> Parameters: 21(Integer), 24(Integer)
         */
    }

    /**
     *  測試
     *      between()       等同於 WHERE age between ? and ?
     *      notBetween()    等同於 WHERE age not between ? and ?
     *  +
     *  使用 selectList() 返回多個 非指定類型對象 的集合
     *
     */
    @Test
    public void abstractWrapperTest_between() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .between("age", 18,24)
                .notBetween("id",0,11);// 支持鏈式調用

        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE age BETWEEN ? AND ? AND id NOT BETWEEN ? AND ?
                 ==> Parameters: 18(Integer), 24(Integer), 0(Integer), 11(Integer)
         */
    }

    /**
     *  測試
     *      like()              等同於 LIKE '%?%'
     *      notLike()           等同於 NOT LIKE '%?%'
     *      likeLeft()          等同於 LIKE '%?'
     *      likeRight()         等同於 LIKE '?%'
     *  +
     *  使用 selectCount()   返回查詢到的條數
     */
    @Test
    public void abstractWrapperTest_like() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .like("email", "test")
                .notLike("email","test4")
                .likeRight("name","J") // J%
                .likeLeft("name","e"); //%e
        Integer selectCount = userMapper.selectCount(userQueryWrapper);
        System.out.println(selectCount);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE email LIKE ? AND email NOT LIKE ? AND name LIKE ? AND name LIKE ?
                 ==> Parameters: %test%(String), %test4%(String), J%(String), %e(String)
         */
    }

    /**
     *  測試
     *      null()              等同於 is null
     *      isNotNull()         等同於 is not null
     *
     */
    @Test
    public void abstractWrapperTest_null() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
               .isNotNull("name");
        Integer selectCount = userMapper.selectCount(userQueryWrapper);
        System.out.println(selectCount);
        /**
         * 輸出:
             ==>  Preparing: SELECT COUNT(1) FROM user WHERE name IS NOT NULL
             ==> Parameters:
             <==    Columns: COUNT(1)
         */
    }

    /**
     *  測試
     *      in()              等同於 IN (?,?)
     *      notIn()           等同於 NOT IN (?,?)
     *      inSql()           等同於 IN (sql)
     *      notInSql()        等同於 NOT IN (sql)
     */
    @Test
    public void abstractWrapperTest_in() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        List<Long> idLists = new ArrayList<>();
        idLists.add(11L);
        idLists.add(13L);
        userQueryWrapper
               // .in("id",idLists);
                .inSql("id","select id from user where id<=15");
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE id IN (select id from user where id<=15)
                 ==> Parameters:
         */
    }

    /**
     *  測試
     *      groupBy() .. 支持多參數    等同於 GROUP BY age,name,id
     */
    @Test
    public void abstractWrapperTest_group() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .groupBy("age","name","id");
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
             ==>  Preparing: SELECT id,name,age,email FROM user GROUP BY age,name,id
             ==> Parameters:
         */
    }

    /**
     *  測試
     *      orderBy()     .. 支持多參數    等同於 關於 condition
     *      orderByDesc() .. 支持多參數    等同於 ORDER BY age DESC , id DESC
     *      orderByAsc()  .. 支持多參數    等同於 ORDER BY age ASC , id ASC
     */
    @Test
    public void abstractWrapperTest_order() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .orderByAsc("age","id");
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
             ==>  Preparing: SELECT id,name,age,email FROM user ORDER BY age ASC , id ASC
             ==> Parameters:
         */
    }

    /**
     *  測試
     *      having()     .. 支持多參數    等同於 HAVING sum(id)> ?
     */
    @Test
    public void abstractWrapperTest_having() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .groupBy("age")
                .having("sum(id)>{0}",20);// 替換裏面的參數
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user GROUP BY age HAVING sum(id)>?
                 ==> Parameters: 20(Integer)
         */
    }

    /**
     *  測試
     *      or()       等同於 or
     *      and()       等同於 and
     */
    @Test
    public void abstractWrapperTest_or_and() {
        // 簡單 or 和and
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .eq("id",11)
                .or()  // 默認and()
//                .and() // 兩個不能連用
                .eq("id",13);
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE id = ? OR id = ?
                 ==> Parameters: 11(Integer), 13(Integer)
         */

        // 嵌套的 or 和and
        QueryWrapper<User> userQueryWrapper1 = new QueryWrapper<>();
        userQueryWrapper1
                .eq("id",14)
                .or(i -> i.eq("name", "Lynwood").ne("age", "18"));
        List<User> userList1 = userMapper.selectList(userQueryWrapper1);
        userList1.forEach(System.out::println);
        /**輸出:
         *
         * ==>  Preparing: SELECT id,name,age,email FROM user WHERE id = ? OR ( name = ? AND age <> ? )
         * ==> Parameters: 14(Integer), Lynwood(String), 18(String)
         */
        /**!!!!!!!!!!!!  .or(i -> i.eq("name", "Lynwood").ne("age", "18"));  解釋  見下文發現問題*/
    }

    /**
     *  測試
     *      apply()    動態傳參防止 sql注入    佔位符 替換 以值的形式添加
     */
    @Test
    public void abstractWrapperTest_apply() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
               .apply("id>{0}",13);
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE id>?
                 ==> Parameters: 13(Integer)
         */
    }

    /**
     *  測試
     *      last()   在sql 最後追加
     *
     *      最經常使用 last("limit 1")
     */
    @Test
    public void abstractWrapperTest_last() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
               .last("limit 2");
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user limit 2
                 ==> Parameters:
         */
    }

    /**
     *  測試
     *      exists()  拼接 EXISTS ( sql語句 )
     *      notExists()  拼接 NOT EXISTS ( sql語句 )
     */
    @Test
    public void abstractWrapperTest_exists() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .exists("SELECT id,name,age,email FROM user where id= 222");
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE EXISTS (SELECT id,name,age,email FROM user where id= 222)
                 ==> Parameters:
                 <==      Total: 0
         */
    }

    /**
     *  測試
     *      nested()  正常嵌套  (無 or and 模式嵌套)
     */
    @Test
    public void abstractWrapperTest_nested() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper
                .nested(i -> i.eq("name", "Lynwood").eq("age", "18"));
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
        /**
         * 輸出:
                 ==>  Preparing: SELECT id,name,age,email FROM user WHERE ( name = ? AND age = ? )
                 ==> Parameters: Lynwood(String), 18(String)
         */
    }
}
發現問題: boolean conditionR columnFunction<This, This> func 三個參數相關問題
說明及使用:

​ Demo 中有 測試連接:https://github.com/wunian7yulian/MybatisPlusDemo/blob/master/mp_wrapper_demo/src/test/java/com/lynwood/mp/mp_wrapper_demo/MpWrapperDemoApplicationAbstractWrapperParamTests.java

1.關於入參: boolean condition
  • 如下出現的第一個入參boolean condition表示該條件是否加入最後生成的sql中,默認是true

使用ne() 方法舉例 源碼 :

@Override
    public This ne(boolean condition, R column, Object val) {
        return addCondition(condition, column, NE, val);
    }

測試編寫:

/**
     *  測試 boolean condition
     */
    @Test
    public void abstractWrapperTest_Condition() {
        Integer age = new Random().nextInt(25);
        System.out.println("年齡: " + age);
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        boolean condition = age<=18;
        userQueryWrapper.ne(condition,"name","Lynwood");
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
    }

​ 生成的隨機數age 小於18歲的 不要讓他查到name="Lynwood"的信息

再此看來等同於*mapper.xml配置

WHERE 1=1  
<if test="age <= 18">
    AND name <> #{name}
</if>

幫咱們提入到了每一個方法中,而且省去咱們關係 是否寫AND關鍵字或者 必須有 WHERE 1=1這些難看卻又沒必要要的配置

2.關於入參: R column
  • 如下方法在入參中出現的R泛型,在普通wrapper中是String,在LambdaWrapper中是函數(例:Entity::getId,Entity爲實體類,getId爲字段idgetMethod)
  • 如下方法入參中的R column均表示數據庫字段,當RString時則爲數據庫字段名(字段名是數據庫關鍵字的本身用轉義符包裹!)!而不是實體類數據字段名!!!

查看源碼 發現實際上 eq()、ne()... 等須要傳入R column的方法實際最後都是調用addCondition(...) 方法

1547174994254

R column參數作了 columnToString(column)操做

AbstractWrapper實際中的此方法是抽象的:

/**
     * 獲取 columnName
     */
    protected abstract String columnToString(R column);

1547175323184

而咱們再去查看咱們使用的 QueryWrapperUpdateWrapper 中的具體實現都爲:

@Override
    protected String columnToString(String column) {
        return column;
    }

其實是轉換成了String類型, 那麼爲何使用了泛型呢?

咱們再返回去查看他的另外一個實現類AbstractLambdaWrapper 中的實現:

@Override
protected String columnToString(SFunction<T, ?> column) {
    return getColumn(LambdaUtils.resolve(column));
}

原來這裏使用泛型 爲了擴展Lambda方式使用!

lambda

  • 獲取 LambdaWrapper
    QueryWrapper中是獲取LambdaQueryWrapper
    UpdateWrapper中是獲取LambdaUpdateWrapper

測試:

/**
     *  測試 R column
     */
    @Test
    public void abstractWrapperTest_Column() {
      QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
//        userQueryWrapper.ne("name","Lynwood");
        userQueryWrapper.lambda().eq(User::getName,"Lynwood");
        
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
    }
3.關於入參: Function<This, This> func

其實再測試AbstractWrapper中的 or/and 嵌套中已經使用過了

or()舉例

​ 須要傳入的是 Function的實現Function的定義是:

/**
 * Represents a function that accepts one argument and produces a result.
 *  表示接受一個參數執行生成結果的函數對象。
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

在咱們使用過程當中 每每是爲了嵌套 咱們傳入的多是另外一個Wrapper 讓他和當前的Wrapper去連接/嵌套起來

那麼在:

userQueryWrapper.ne("name","Lynwood")
                        .or(i -> i.eq("age", "18"));

中 咱們實際上傳入了一個匿名的Wrapper對象,那麼這個匿名的Wrapper對象怎麼符合了Function的要求呢

咱們查看這個i變量 : 1547179827539

他的類型是與調用對象一致的 查看 QueryWrapper<User>的繼承關係:

1547179917168

實際上咱們使用的全部接口都去間接繼承了ISqlSegment 接口 ,而它:

package com.baomidou.mybatisplus.core.conditions;
@FunctionalInterface
public interface ISqlSegment extends Serializable {

是被@FunctionalInterface 標識的 與:

package java.util.function;
@FunctionalInterface
public interface Function<T, R> {

是同樣的, 因此咱們使用它能夠這樣簡便的使用啦:

測試:

@Test
    public void abstractWrapperTest_Func() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.ne("name","Lynwood");
        userQueryWrapper.or(new Function<QueryWrapper<User>, QueryWrapper<User>>() {
            @Override
            public QueryWrapper<User> apply(QueryWrapper<User> userQueryWrapper) {
                return userQueryWrapper.eq("age", "18");
            }
        });
        // 等同與
        // userQueryWrapper.ne("name","Lynwood")
        //                        .or(i -> i.eq("age", "18"));
        List<User> userList = userMapper.selectList(userQueryWrapper);
        userList.forEach(System.out::println);
    }

二> 編寫QueryWrapper與UpdateWrapper測試類:

Demo 中有 連接:https://github.com/wunian7yulian/MybatisPlusDemo/blob/master/mp_wrapper_demo/src/test/java/com/lynwood/mp/mp_wrapper_demo/MpWrapperDemoApplicationQueryAndUpdateWrapperTests.java

其實在上面的全部測試中其返回值都是徹底的裝配到了實體對象中,可是有的時候 好比有個字段很大 很消耗內存,同時也用不到,那麼我如何把他取消掉.設置返回值爲咱們須要的幾個字段就好

select()測試

注意坑 在借用 lambda() 使用 lambda特性時!
/**
     *  測試 QueryWrapper.select()
     *
     */
 @Test
    public void abstractWrapperTest_Select() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
//        userQueryWrapper.eq("age",18)
//                .select("id","name");
        userQueryWrapper.eq("age",18)
                .select(User.class,i -> false);//只返回id
        List<User> userList = userMapper.selectList(userQueryWrapper);
            // SELECT id FROM user WHERE age = ?
        userList.forEach(System.out::println);

        //前方有坑 注意: 注意!!!!!!!!!!!

        //關於LambdaQueryWrapper的使用這樣用是沒問題的
        LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda();
        lambdaQueryWrapper.select(User::getId,User::getAge);
        List<User> userList1 = userMapper.selectList(lambdaQueryWrapper);
            //SELECT id,age FROM user
        userList1.forEach(System.out::println);

        // 這樣設置是無效的!!!!
        QueryWrapper<User> userQueryWrapper2 = new QueryWrapper<>();
        userQueryWrapper2.lambda().select(User::getId,User::getAge);
        List<User> userList2 = userMapper.selectList(userQueryWrapper2);
            // SELECT id,name,age,email FROM user
        userList2.forEach(System.out::println);
    }

set() setSql() 測試

/**
     *  測試 UpdateWrapper.set()
     *  測試 UpdateWrapper.setSql()
     */
    @Test
    public void abstractWrapperTest_Set() {
        UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
        // 更新id==11 的age 爲19
        userUpdateWrapper.set("age",19).eq("id",11);
        // 附加更新內容 name 設置
        User user = new User();
        user.setName("Lynwood1");
        userMapper.update(user,userUpdateWrapper);
        /**
         *          ==>  Preparing: UPDATE user SET name=?, age=? WHERE id = ?
         *          ==> Parameters: Lynwood1(String), 19(Integer), 11(Integer)
         *          <==    Updates: 1
         */

        UpdateWrapper<User> userUpdateWrapper1 = new UpdateWrapper<>();
        userUpdateWrapper1.setSql("name='wunian7yulian'").eq("id",11);
        // 附加更新內容爲空時  不能傳入null  須要傳入空實體對象
        userMapper.update(new User(),userUpdateWrapper1);
        /**
         *      ==>  Preparing: UPDATE user SET name='wunian7yulian' WHERE id = ?
         *      ==> Parameters: 11(Integer)
         *      <==    Updates: 1
         */
    }

6、插件及擴展

Demo代碼地址:

https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/mp_plugin_demo

初始化:

​ 由於下面演示須要其餘字段,因此從新建立一個便於Demo的合適的格:

-- 建立簡單表格
DROP TABLE IF EXISTS user;

CREATE TABLE `user` (
  `id` bigint(20) PRIMARY KEY AUTO_INCREMENT  COMMENT '主鍵ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年齡',
  `email` varchar(50) DEFAULT NULL COMMENT '郵箱'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO user ( name, age, email) VALUES
('Jone', 18, 'test1@test.com'),
('Jack', 20, 'test2@test.com'),
('Tom', 28, 'test3@test.com'),
('Sandy', 21, 'test4@test.com'),
('Billie', 24, 'test5@test.com'),
('Lynwood', 18, 'wunian_@hotmail.com');

工程:

第一 -->第四步、同上

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf-8
    username: root
    password: 123456

mybatis-plus:
  global-config:
    db-config:
      id-type: auto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第五步、插件使用

分頁插件:

​ 以前作列表或者報表功能時使用最多的是pageHelper插件作Mybatis的分頁查詢,MP爲了方便將分頁作了一個相關的模塊com.baomidou.mybatisplus.extension.plugins.pagination供咱們分頁查詢時去使用.

一、建立一個MP插件的配置類:

在business目錄下建立config目錄 並在該目錄下建立MybatisPlusConfig.java 添加@Configuration註解聲明

@Configuration
public class MybatisPlusConfig {
    
}
二、配置分頁插件
@Configuration
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}
三、編寫測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class MpPluginDemoApplicationTests {
    @Autowired
    private UserMapper userMapper;
    /**
     * 測試分頁插件
     */
    @Test
    public void test_page() {
        IPage<User> userIPage = userMapper.selectPage(
                  new Page<User>()
                        .setCurrent(1) //設置當前查詢頁
                        .setSize(3) // 設置每頁條數
                        .setDesc("age"),//使用page 進行排序
                new QueryWrapper<User>()
                        .lambda()
                        .likeRight(User::getEmail, "test")
                        .select(User::getId, User::getName, User::getAge)
        );

        List<User> records = userIPage.getRecords();
        records.forEach(System.out::println);
        /**
         * ==>  Preparing: SELECT id,name,age FROM user WHERE email LIKE ? ORDER BY age DESC LIMIT ?,?
         * ==> Parameters: test%(String), 0(Long), 3(Long)
         */
    }
}

完美! 不過由於page對象在3.0.6版本尚未支持lambda方式 page上在設置排序字段時 仍是會有一些魔法值出現,不過咱們也是能夠直接將 排序 寫在wrapper裏的. 有強迫症的童鞋等待3.1.0版本吧.

邏輯刪除插件:
一、yaml添加邏輯值制定配置:
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf-8
    username: root
    password: 123456

mybatis-plus:
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1 # 邏輯已刪除值(默認爲 1)
      logic-not-delete-value: 0 # 邏輯未刪除值(默認爲 0)
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
二、配置邏輯插件:

在MybatisPlusConfig中添加插件bean

@Configuration
public class MybatisPlusConfig {
  ...
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }
}
三、標識邏輯字段:

User中的 del字段上添加@TableLogic註解

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
  ...
    /**
     * 邏輯刪除 0-未刪除 1-刪除
     */
    @TableLogic
    private Integer del;
    ...
}
四、編寫測試
/**
     * 測試邏輯刪除插件
     */ 
    @Test
    public void test_logic_delete() {
        userMapper.delete(new QueryWrapper<User>()
                .lambda()
                .notLike(User::getEmail, "test")
                .select(User::getId, User::getName, User::getAge));
        /**
         * ==>  Preparing: UPDATE user SET del=1 WHERE del=0 AND email NOT LIKE ?
         * ==> Parameters: %test%(String)
         */
        //那麼查詢呢?
        List<User> userList = userMapper.selectList(null);
        userList.forEach(System.out::println);
        //發現查詢也幫咱們設置了過濾字段  del = 0
        /**
         * ==>  Preparing: SELECT id,name,age,email,del,version FROM user WHERE del=0
         * ==> Parameters:
         */

        //那麼我想查出這個刪除的用戶 更改成 正常呢?
        List<Map<String, Object>> mapList = userMapper.selectMaps(
                new QueryWrapper<User>()
                        .lambda()
                        .eq(User::getDel, 1)
                        .select(User.class,i -> false)
        );
        Assert.assertNotNull(mapList);
        Assert.assertEquals(mapList.size(),1);
        /**  select id from user where del = 0 and del = 1
         * java.lang.AssertionError:
         * Expected :0
         * Actual   :1
         */
        // 失敗了
        //

    }

詢問MP開發者後 開發者說明 由於大部分狀況在刪除以後不會恢復 因此沒有設定相關恢復或者跳過 當前 篩選的 接口

樂觀鎖:
主要適用場景

當要更新一條記錄的時候,但願這條記錄沒有被別人更新

特別說明:

  • 支持的數據類型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整數類型下 newVersion = oldVersion + 1
  • newVersion 會回寫到 entity
  • 僅支持 updateById(id)update(entity, wrapper) 方法
  • 在 update(entity, wrapper) 方法下, wrapper 不能複用!!!

樂觀鎖實現方式:

  • 取出記錄時,獲取當前version

  • 更新時,帶上這個version

  • 執行更新時, set version = newVersion where version = oldVersion

  • 若是version不對,就更新失敗

一、配置樂觀鎖插件:

MybatisPlusConfig添加:

@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    return new OptimisticLockerInterceptor();
}

二、添加樂觀鎖版本字段註解:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
  ...
    /**
     * 樂觀鎖版本字段
     */
    @Version
    private Integer version;
}

三、編寫測試類:

/**
     * 測試樂觀鎖插件
     */
    @Test
    public void test_Optimistic_Locking() throws InterruptedException {
        User oldUser = userMapper.selectById(1);
        oldUser.setAge(19);
        System.out.println("模擬中途有人更改"+ "begin....");
        Thread.sleep(10000);
        System.out.println("模擬中途有人更改"+ "end....");
        if(userMapper.updateById(oldUser)==1){
            System.out.println("success \n"+ oldUser);
            //success
            //User(id=1, name=Jone, age=19, email=test1@test.com, del=0, version=2)
        }else {
            System.out.println("fail \n");
            //==>  Preparing: UPDATE user SET name=?, age=?, email=?, version=? WHERE id=? AND version=? AND del=0
            //==> Parameters: Jone(String), 19(Integer), test1@test.com(String), 3(Integer), 1(Long), 2(Integer)
            //<==    Updates: 0
            //fail
        }
    }

其餘插件 如 熱加載等 若有須要 再作更新

七 、總結

MP優秀於簡化了Mybatis大部分XML配置 將他歸總到起來生成一個強大的Wrapper

現有MP也是有了兩個版本大的版本2.x與3.x ,酌情使用比較合理 3.x雖然有了Lambda的支持可是還不完善,

後續版本的更新應該也會去查漏現版本的缺陷.

對於插件,像樂觀鎖分頁邏輯刪真的是貼心,不過也有相對應場景不適用問題,好比上面說到的邏輯刪除的我還要查是查不到的,仍是須要去自定義sql的

我相信 有了mp這種強大的利器,對於一個微型項目 或者微服務 來講真的是爽,寫起來真的是很是有效率

我的--

​ 做爲頭一個被我收拾的應用層框架,看到了開發者的用心和初心, 看來排上名並非那麼絕非偶然,也並不是那麼"功力深厚",只要你有想法開始作,總有一個好的結果.

下一框架尋找中...

參考文檔:

MyBatis-Plus 官方文檔 : https://mp.baomidou.com/

MyBatis-Plus 配置進階 : https://mp.baomidou.com/config/

MyBatis-Plus 代碼生成器配置 : https://mp.baomidou.com/config/generator-config.html

MyBatis-Plus sql注入原理 : https://www.liangzl.com/get-article-detail-19831.html

Mybatis-Plus 使用全解 : https://www.jianshu.com/p/7299cba2adec

相關文章
相關標籤/搜索