學(xué)會mybatis-plus的使用,做一個快樂的Curd-BOY

前言

對于mybatis ,很多后端開發(fā)已經(jīng)很熟悉了,因?yàn)楝F(xiàn)在大部分公司用的框架就是mybatis,而Mybatis-Plus(簡稱MP)是一個 Mybatis 的增強(qiáng)工具。(很多公司也在用這個框架)

在項(xiàng)目里面,你經(jīng)常是不是這樣書寫:(如查詢) Wrappers.<Entity>query().lambda().eq(Entity::getXX, entity2.getXX());

網(wǎng)上想找到Mybatis-Plus的文檔和案例,其實(shí)很簡單,在Mybatis-Plus的官網(wǎng)上或者有很多博客上都能找到的。但你有木有相關(guān)它是怎么能實(shí)現(xiàn)不需要再寫xml了(針對寫sql),就能針對性的查詢/新增/修改/刪除的?當(dāng)你遇到lambda表達(dá)式時(shí),會不會想到他是怎么把這個Get方法傳入的?下面就來談?wù)凪ybatis-Plus是怎么使用lambda表達(dá)式,自動生成對應(yīng)的sql語句的。

代碼分析

基于Mybatis-Plus的3.0.6 版本,這個框架用到了工廠模式和組合模式 以及攔截過濾器模式。
file

首先:Wrappers.<Entity>query() 或者Wrappers.<Entity>update() 其實(shí)就是在創(chuàng)建一個QueryWrapper 或UpdateWrapper。然后調(diào)用lambda方法就是創(chuàng)建LambdaUpdateWrapper 或者 LambdaUpdateWrapper

file

如圖,需要重點(diǎn)關(guān)注的是Compare(接口)和AbstractWrapper(類),在Compare接口里面。


file

public interface Compare<This, R> extends Serializable {
  
    default This eq(R column, Object val) {
        return this.eq(true, column, val);
    }

    This eq(boolean condition, R column, Object val);
}

這里面的This就是代表就是返回自身(這里字面是這個意思,實(shí)際也是這樣弄的),在3.3.2版本里面這個This用Children給取代了。

在AbstractWrapper類里面,其實(shí)已經(jīng)實(shí)現(xiàn)了eq方法(如下圖),這個類實(shí)現(xiàn)我把其他實(shí)現(xiàn)接口去掉了,只留下了Compare接口。(這樣看起來比較清晰)

public abstract class AbstractWrapper<T, R, This extends AbstractWrapper<T, R, This>> extends Wrapper<T> implements Compare<This, R>{
     public This eq(boolean condition, R column, Object val) {
        return this.addCondition(condition, column, SqlKeyword.EQ, val);
    }
}
  

可能你很疑惑為什么eq /ne 這些方法里面可以直接傳遞lambda的方法引用(Entity:getXX),而不應(yīng)該是泛型R?
不要著急。AbstractLambdaWrapper (實(shí)現(xiàn)了AbstractWrapper類,此時(shí) AbstractWrapper類的泛型R用接口SFunction來具體化“取代了”,這個SFunction指定了必須是泛型T里面的方法,這點(diǎn)要注意,如果沒有指定泛型可能會報(bào)Object is not a functional interface 這樣的錯誤)。

public abstract class AbstractLambdaWrapper<T, This extends AbstractLambdaWrapper<T, This>>
    extends AbstractWrapper<T, SFunction<T, ?>, Children> {
        //省略
}

調(diào)用上面的addCondition方法,實(shí)際會解析這個"接口",這個是使用流讀取,方法在LambdaUtils里面,如果你有需要可以在項(xiàng)目中直接使用這個方法,
這就是看源碼的好處。其實(shí)這部分就是把當(dāng)前對象的“數(shù)據(jù)庫"對于列存入緩存(map),將對應(yīng)列和值也就進(jìn)行存儲。以便到最后面生成sql。(其實(shí)在mapper層調(diào)用方法時(shí))

file

file

file

自己實(shí)現(xiàn)這樣的功能 (記錄處理列和 對象所有的數(shù)據(jù)庫字段 和串寫的方式)

這個里面解析lambda等相關(guān)工具從mybatis-plus里面挪了出來,部分功能一重寫,還原一個無依賴的項(xiàng)目。

1.繼承接口

package interfaces;

import java.io.Serializable;

/**
 * <ul>
 * <li>Title: Compare</li>
 * </ul>
 * @author 程序員ken
 * @date 2021/4/28 0028 下午 14:48
 */
public interface Compare<This, R> extends Serializable {

    This eq(boolean var1, R var2, Object var3);

    default This eq(R column, Object val) {
        return this.eq(true, column, val);
    }

    This ne(boolean var1, R var2, Object var3);

    default This ne(R column, Object val) {
        return this.ne(true, column, val);
    }

    This gt(boolean var1, R var2, Object var3);

    default This gt(R column, Object val) {
        return this.gt(true, column, val);
    }


    This lt(boolean var1, R var2, Object var3);

    default This lt(R column, Object val) {
        return this.lt(true, column, val);
    }

}

2.接口實(shí)現(xiàn)類

AbstractWrapper 類 所有核心方法的實(shí)現(xiàn),這里沒有判斷是不是SFunction,直接強(qiáng)轉(zhuǎn)的,實(shí)際項(xiàng)目必須要判斷哦

package wrapper;


/**
 * <ul>
 * <li>Title: AbstractWrapper</li>
 * <li>Description: TODO </li>
 * </ul>
 *
 * @author 程序員ken
 * @date 2021/4/28 0028 下午 16:23
 */
//extends Wrapper<T>
public abstract class AbstractWrapper<T, R, This extends  AbstractWrapper>
        implements Compare<This, R> {

    protected MergeSegmentList expression;
    protected Map<String, Object> paramNameValuePairs;

    public Class<T> entityClass;
    private Map<String, String> columnMap = null;
    private boolean initColumnMap = false;



    public AbstractWrapper() {
    }

    //實(shí)際實(shí)現(xiàn)
    @Override
    public This eq(boolean condition, R column, Object val) {
        String fileName = columnToString((SFunction) column);
        MergeSegment segment = new MergeSegment();
        segment.setColumName(fileName);
        segment.setColumValue(val);
        segment.setMatchCondition(MatchCondition.EQ);
        expression.add(segment);
        paramNameValuePairs.putIfAbsent(fileName,val);
        return (This)this;
    }

    @Override
    public This ne(boolean condition, R column, Object val) {
        String fileName = columnToString((SFunction) column);
        MergeSegment segment = new MergeSegment();
        segment.setColumName(fileName);
        segment.setColumValue(val);
        segment.setMatchCondition(MatchCondition.NE);
        expression.add(segment);
        paramNameValuePairs.putIfAbsent(fileName,val);
        return (This)this;
    }

    @Override
    public This gt(boolean condition, R column, Object val) {
        String fileName = columnToString((SFunction) column);
        MergeSegment segment = new MergeSegment();
        segment.setColumName(fileName);
        segment.setColumValue(val);
        segment.setMatchCondition(MatchCondition.GT);
        expression.add(segment);
        paramNameValuePairs.putIfAbsent(fileName,val);
        return (This)this;
    }

    @Override
    public This lt(boolean condition, R column, Object val) {
        String fileName = columnToString((SFunction) column);
        MergeSegment segment = new MergeSegment();
        segment.setColumName(fileName);
        segment.setColumValue(val);
        segment.setMatchCondition(MatchCondition.LT);
        expression.add(segment);
        paramNameValuePairs.putIfAbsent(fileName,val);
        return (This)this;
    }

    /***
     * 功能描述:  獲取字段信息
     * @return: java.lang.String
     * @author: 程序員ken
     * @date: 2021/4/28 21:34
    */
    protected String columnToString(SFunction<T, ?> column) {
        SerializedLambda resolve = LambdaUtils.resolve(column);
        return this.getColumn(resolve);
    }


    private String getColumn(SerializedLambda lambda) {
        String fieldName = resolveFieldName(lambda.getImplMethodName());
        if (!this.initColumnMap || !this.columnMap.containsKey(fieldName)) {
            String entityClassName = lambda.getImplClassName();
            try{
                Class<T> aClass = (Class<T>)Class.forName(entityClassName.replaceAll("\\\\", "."));
                if(entityClass==null){
                    entityClass = aClass;
                }
                this.columnMap =  getColumnMap(aClass);
                //3.0.6 支持 ==>3.3.2 不支持
                //this.columnMap = LambdaUtils.getColumnMap(entityClassName);
                this.initColumnMap = true;
            }catch (Exception ex){

            }
        }
        return fieldName;
    }

    /**
     * 功能描述: 獲取當(dāng)前實(shí)體的“數(shù)據(jù)庫”字段
     * @param aClass
     * @return: java.util.Map<java.lang.String,java.lang.String>
     * @author: 程序員ken
     * @date: 2021/4/29 0029 下午 12:39
    */
    protected  Map<String,String> getColumnMap(Class<?> aClass){
        Map<String,String> map = new HashMap<String,String>();
        //ClassLoader classLoader = aClass.getClassLoader();
        Field[] declaredFields = aClass.getDeclaredFields();
        TableField tableField =null;
        for (Field field:declaredFields) {
             tableField = field.getAnnotation(TableField.class);

             if(!(tableField!=null && !tableField.exist())){
                 map.putIfAbsent(field.getName(),field.getName());
             }
        }

        return map;
    }

    public static String resolveFieldName(String getMethodName) {
        if (getMethodName.startsWith("get")) {
            getMethodName = getMethodName.substring(3);
        } else if (getMethodName.startsWith("is")) {
            getMethodName = getMethodName.substring(2);
        }
        return firstToLowerCase(getMethodName);
    }

    public static String firstToLowerCase(String param) {
        return param==null || "".equals(param.trim()) ? "" :
                param.substring(0, 1).toLowerCase() + param.substring(1);
    }

}

file

file

3.記錄列

file

file

3.枚舉類

file

4.注解類

file

5.工具類
工具類的lambda解析的接口,我是指定了解析“繼承”了Function這個接口,才會被解析,mybatis-plus里面是寫死了 解析SFunction,這樣限制性很大,然后脫離了mybatis-plus框架這個解析類的很多功能就用不了。

file

6.接口

package interfaces;

import java.io.Serializable;
import java.util.function.Function;

/**
 * <ul>
 * <li>Title: SFunction</li>
 * <li>Description: TODO </li>
 * </ul>
 *
 * @author 程序員ken
 * @date 2021/4/28 0028 下午 14:33
 */

@FunctionalInterface
public interface SFunction<T, R> extends Function<T,R>, Serializable {
}

7.測試:


file

8.其他

另外在own包下我也仿寫了一個這樣的串寫lambda的示例,所有的測試案例在LambadaTest里面doun都能找到。

file

總結(jié):其實(shí)本文也并沒有深入源碼,只是讓大致了解這個框架的原理。

【紙上得來終覺淺,絕知此事要躬行】

(多看看優(yōu)秀的代碼,這樣你的代碼才會有進(jìn)步哦,不要做一個只會curd的boy哦)

源碼地址:https://gitee.com/ten-ken/mybatis_plus

歡迎關(guān)注我的公眾號:程序員ken,程序之路,讓我們一起探索,共同進(jìn)步。

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

相關(guān)閱讀更多精彩內(nèi)容

  • MyBatis-Plus MyBatis-Plus是一個MyBatis的增強(qiáng)工具,在MyBatis的基礎(chǔ)上只做增強(qiáng)...
    禹王穆閱讀 968評論 0 0
  • 一、MyBatis-Plus 主要特性二、常用的CRUD接口三、條件構(gòu)造器四、MyBatis-Plus 實(shí)現(xiàn)表的增...
    Ada54閱讀 2,105評論 0 8
  • MyBatis-Plus MyBatis-Plus(簡稱 MP)是一個 MyBatis的增強(qiáng)工具,在 MyBati...
    松松木tell閱讀 782評論 0 0
  • Mybatis-Plus(MP)在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,簡化開發(fā)、提高效率。 本篇是根據(jù)My...
    h2coder閱讀 11,848評論 4 41
  • MybatisPlus 特性 無侵入:只做增強(qiáng)不做改變,引入它不會對現(xiàn)有工程產(chǎn)生影響,如絲般順滑 損耗?。簡蛹磿?..
    njitzyd閱讀 678評論 0 0

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