對於mybatis ,不少後端開發已經很熟悉了,由於如今大部分公司用的框架就是mybatis,而Mybatis-Plus(簡稱MP)是一個 Mybatis 的加強工具。(不少公司也在用這個框架)java
在項目裏面,你常常是否是這樣書寫:(如查詢) Wrappers.<Entity>query().lambda().eq(Entity::getXX, entity2.getXX());git
網上想找到Mybatis-Plus的文檔和案例,其實很簡單,在Mybatis-Plus的官網上或者有不少博客上都能找到的。但你有木有相關它是怎麼能實現不須要再寫xml了(針對寫sql),就能針對性的查詢/新增/修改/刪除的?當你遇到lambda表達式時,會不會想到他是怎麼把這個Get方法傳入的?下面就來談談Mybatis-Plus是怎麼使用lambda表達式,自動生成對應的sql語句的。程序員
首先:Wrappers.<Entity>query() 或者Wrappers.<Entity>update() 其實就是在建立一個QueryWrapper 或UpdateWrapper。而後調用lambda方法就是建立LambdaUpdateWrapper 或者 LambdaUpdateWrappersql
如圖,須要重點關注的是Compare(接口)和AbstractWrapper(類),在Compare接口裏面。
數據庫
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就是表明就是返回自身(這裏字面是這個意思,實際也是這樣弄的),在3.3.2版本里面這個This用Children給取代了。express
在AbstractWrapper類裏面,其實已經實現了eq方法(以下圖),這個類實現我把其餘實現接口去掉了,只留下了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),而不該該是泛型R?
不要着急。AbstractLambdaWrapper (實現了AbstractWrapper類,此時 AbstractWrapper類的泛型R用接口SFunction來具體化「取代了」,這個SFunction指定了必須是泛型T裏面的方法,這點要注意,若是沒有指定泛型可能會報Object is not a functional interface 這樣的錯誤)。緩存
public abstract class AbstractLambdaWrapper<T, This extends AbstractLambdaWrapper<T, This>> extends AbstractWrapper<T, SFunction<T, ?>, Children> { //省略 }
調用上面的addCondition方法,實際會解析這個"接口",這個是使用流讀取,方法在LambdaUtils裏面,若是你有須要能夠在項目中直接使用這個方法,
這就是看源碼的好處。其實這部分就是把當前對象的「數據庫"對於列存入緩存(map),將對應列和值也就進行存儲。以便到最後面生成sql。(其實在mapper層調用方法時)mybatis
這個裏面解析lambda等相關工具從mybatis-plus裏面挪了出來,部分功能一重寫,還原一個無依賴的項目。app
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.接口實現類
AbstractWrapper 類 全部核心方法的實現,這裏沒有判斷是否是SFunction,直接強轉的,實際項目必需要判斷哦
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() { } //實際實現 @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; } /** * 功能描述: 獲取當前實體的「數據庫」字段 * @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); } }
3.記錄列
3.枚舉類
4.註解類
5.工具類
工具類的lambda解析的接口,我是指定了解析「繼承」了Function這個接口,纔會被解析,mybatis-plus裏面是寫死了 解析SFunction,這樣限制性很大,而後脫離了mybatis-plus框架這個解析類的不少功能就用不了。
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.測試:
8.其餘
另外在own包下我也仿寫了一個這樣的串寫lambda的示例,全部的測試案例在LambadaTest裏面doun都能找到。
總結:其實本文也並無深刻源碼,只是讓大體瞭解這個框架的原理。
【紙上得來終覺淺,絕知此事要躬行】
(多看看優秀的代碼,這樣你的代碼纔會有進步哦,不要作一個只會curd的boy哦)
源碼地址:[https://gitee.com/ten-ken/myb...
歡迎關注個人公衆號:程序員ken,程序之路,讓咱們一塊兒探索,共同進步。