学会mybatis-plus的使用,做一个快乐的Curd-BOY

1,542 阅读5分钟

前言

对于mybatis ,很多后端开发已经很熟悉了,因为现在大部分公司用的框架就是mybatis,而Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具。(很多公司也在用这个框架)

在项目里面,你经常是不是这样书写:(如查询) Wrappers.query().lambda().eq(Entity::getXX, entity2.getXX());

网上想找到Mybatis-Plus的文档和案例,其实很简单,在Mybatis-Plus的官网上或者有很多博客上都能找到的。但你有木有相关它是怎么能实现不需要再写xml了(针对写sql),就能针对性的查询/新增/修改/删除的?当你遇到lambda表达式时,会不会想到他是怎么把这个Get方法传入的?下面就来谈谈Mybatis-Plus是怎么使用lambda表达式,自动生成对应的sql语句的。

代码分析

基于Mybatis-Plus的3.0.6 版本,这个框架用到了工厂模式和组合模式 以及拦截过滤器模式。

file

首先:Wrappers.query() 或者Wrappers.update() 其实就是在创建一个QueryWrapper 或UpdateWrapper。然后调用lambda方法就是创建LambdaUpdateWrapper 或者 LambdaUpdateWrapper

file

如图,需要重点关注的是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就是代表就是返回自身(这里字面是这个意思,实际也是这样弄的),在3.3.2版本里面这个This用Children给取代了。

在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层调用方法时)

file file file

自己实现这样的功能 (记录处理列和 对象所有的数据库字段 和串写的方式)

这个里面解析lambda等相关工具从mybatis-plus里面挪了出来,部分功能一重写,还原一个无依赖的项目。

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);
    }

}

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

总结:其实本文也并没有深入源码,只是让大致了解这个框架的原理。

【纸上得来终觉浅,绝知此事要躬行】

(多看看优秀的代码,这样你的代码才会有进步哦,不要做一个只会curd的boy哦)

源码地址:gitee.com/ten-ken/myb…

欢迎关注我的公众号:程序员ken,程序之路,让我们一起探索,共同进步。