背景

Mybatis Plus QueryWrapperlambda用起来很便当,比方Wrappers.<Order>lambdaQuery().eq(Order::getOrderCode, 'test')
然而在须要对SQL做一些非凡解决时,比方distinctsum时,无奈用到lambda,只能硬编码字段的数据库字段名,例如Wrappers.<Order>query().select("distinct order_code"),这种在代码里硬编码数据库字段名,给人感觉十分的不标准,那是否能够像lambdaQuery那样,不硬编码也能够获取到数据库字段名呢?相似这样子:

String columName = columnResolver.getColumn(Order::getOrderCode);Wrappers.<Order>query().select("distinct " + columName);

思路

Mybatis Pluslambda既然在惯例的SQL下既然能够做到获取数据库字段名,那是否能够复用它的代码呢?
这就须要先看下Mybatis Plus是怎么实现的。

原理

初始化

Mybatis在我的项目启动时,会生成每个Mapper对应的BeanMybatis Plus在解析Mapper时,会解析实体类信息,生成TableInfo,解析入口是TableInfoHelper.initTableInfo

public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {    TableInfo targetTableInfo = TABLE_INFO_CACHE.get(clazz);    final Configuration configuration = builderAssistant.getConfiguration();    if (targetTableInfo != null) {        Configuration oldConfiguration = targetTableInfo.getConfiguration();        if (!oldConfiguration.equals(configuration)) {            // 不是同一个 Configuration,进行从新初始化            targetTableInfo = initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);            TABLE_INFO_CACHE.put(clazz, targetTableInfo);        }        return targetTableInfo;    }    return TABLE_INFO_CACHE.computeIfAbsent(clazz, key -> initTableInfo(configuration, builderAssistant.getCurrentNamespace(), key));}

先从缓存TABLE_INFO_CACHE里获取信息,如果没有,就通过initTableInfo解析信息并放入缓存。

private synchronized static TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {    /* 没有获取到缓存信息,则初始化 */    TableInfo tableInfo = new TableInfo(clazz);    tableInfo.setCurrentNamespace(currentNamespace);    tableInfo.setConfiguration(configuration);    GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);    /* 初始化表名相干 */    final String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo);    List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList();    /* 初始化字段相干 */    initTableFields(clazz, globalConfig, tableInfo, excludePropertyList);    /* 主动构建 resultMap */    tableInfo.initResultMapIfNeed();    /* 缓存 lambda */    LambdaUtils.installCache(tableInfo);    return tableInfo;}

通过initTableName办法初始化表名信息,再通过initTableFields办法初始化字段相干信息,最初放入缓存中。

private static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {    /* 数据库全局配置 */    GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();    ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory();    //TODO @咩咩 有空一起来撸完这反射模块.    Reflector reflector = reflectorFactory.findForClass(clazz);    List<Field> list = getAllFields(clazz);    // 标记是否读取到主键    boolean isReadPK = false;    // 是否存在 @TableId 注解    boolean existTableId = isExistTableId(list);    // 是否存在 @TableLogic 注解    boolean existTableLogic = isExistTableLogic(list);    List<TableFieldInfo> fieldList = new ArrayList<>(list.size());    for (Field field : list) {        if (excludeProperty.contains(field.getName())) {            continue;        }        /* 主键ID 初始化 */        if (existTableId) {            TableId tableId = field.getAnnotation(TableId.class);            if (tableId != null) {                if (isReadPK) {                    throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName());                } else {                    initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId, reflector);                    isReadPK = true;                    continue;                }            }        } else if (!isReadPK) {            isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, reflector);            if (isReadPK) {                continue;            }        }        final TableField tableField = field.getAnnotation(TableField.class);        /* 有 @TableField 注解的字段初始化 */        if (tableField != null) {            fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic));            continue;        }        /* 无 @TableField 注解的字段初始化 */        fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic));    }    /* 字段列表 */    tableInfo.setFieldList(fieldList);    /* 未发现主键注解,提醒正告信息 */    if (!isReadPK) {        logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));    }}

initTableFields办法通过获取注解TableIdTableField配置来生成字段相干信息,TableField解析信息逻辑在TableFieldInfo构造函数内。

public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, TableField tableField,                      Reflector reflector, boolean existTableLogic) {    field.setAccessible(true);    this.field = field;    this.version = field.getAnnotation(Version.class) != null;    this.property = field.getName();    this.propertyType = reflector.getGetterType(this.property);    this.isPrimitive = this.propertyType.isPrimitive();    this.isCharSequence = StringUtils.isCharSequence(this.propertyType);    this.fieldFill = tableField.fill();    this.withInsertFill = this.fieldFill == FieldFill.INSERT || this.fieldFill == FieldFill.INSERT_UPDATE;    this.withUpdateFill = this.fieldFill == FieldFill.UPDATE || this.fieldFill == FieldFill.INSERT_UPDATE;    this.update = tableField.update();    JdbcType jdbcType = tableField.jdbcType();    final Class<? extends TypeHandler> typeHandler = tableField.typeHandler();    final String numericScale = tableField.numericScale();    String el = this.property;    if (JdbcType.UNDEFINED != jdbcType) {        this.jdbcType = jdbcType;        el += (COMMA + "jdbcType=" + jdbcType.name());    }    if (UnknownTypeHandler.class != typeHandler) {        this.typeHandler = (Class<? extends TypeHandler<?>>) typeHandler;        if (tableField.javaType()) {            String javaType = null;            TypeAliasRegistry registry = tableInfo.getConfiguration().getTypeAliasRegistry();            Map<String, Class<?>> typeAliases = registry.getTypeAliases();            for (Map.Entry<String, Class<?>> entry : typeAliases.entrySet()) {                if (entry.getValue().equals(propertyType)) {                    javaType = entry.getKey();                    break;                }            }            if (javaType == null) {                javaType = propertyType.getName();                registry.registerAlias(javaType, propertyType);            }            el += (COMMA + "javaType=" + javaType);        }        el += (COMMA + "typeHandler=" + typeHandler.getName());    }    if (StringUtils.isNotBlank(numericScale)) {        el += (COMMA + "numericScale=" + numericScale);    }    this.el = el;    this.initLogicDelete(dbConfig, field, existTableLogic);    String column = tableField.value();    if (StringUtils.isBlank(column)) {        column = this.property;        if (tableInfo.isUnderCamel()) {            /* 开启字段下划线申明 */            column = StringUtils.camelToUnderline(column);        }        if (dbConfig.isCapitalMode()) {            /* 开启字段全大写申明 */            column = column.toUpperCase();        }    }    String columnFormat = dbConfig.getColumnFormat();    if (StringUtils.isNotBlank(columnFormat) && tableField.keepGlobalFormat()) {        column = String.format(columnFormat, column);    }    this.column = column;    this.sqlSelect = column;    if (tableInfo.getResultMap() == null && !tableInfo.isAutoInitResultMap() &&        TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) {        /* 未设置 resultMap 也未开启主动构建 resultMap, 字段规定又不合乎 mybatis 的主动封装规定 */        String propertyFormat = dbConfig.getPropertyFormat();        String asProperty = this.property;        if (StringUtils.isNotBlank(propertyFormat)) {            asProperty = String.format(propertyFormat, this.property);        }        this.sqlSelect += (" AS " + asProperty);    }    this.insertStrategy = this.chooseFieldStrategy(tableField.insertStrategy(), dbConfig.getInsertStrategy());    this.updateStrategy = this.chooseFieldStrategy(tableField.updateStrategy(), dbConfig.getUpdateStrategy());    this.whereStrategy = this.chooseFieldStrategy(tableField.whereStrategy(), dbConfig.getSelectStrategy());    if (StringUtils.isNotBlank(tableField.condition())) {        // 细粒度条件管制        this.condition = tableField.condition();    }    // 字段是否注入查问    this.select = tableField.select();}

因为MybatisConfiguration默认开启驼峰转下划线模式:this.mapUnderscoreToCamelCase = true,即@TableField如果没有配置value属性,则数据库字段名默认辨认为下划线格局

在初始化配置信息,将信息放入缓存之后,后续的查问就能够用到这些信息了。

数据执行流程

Wrappers.<Order>lambdaQuery().eq(Order::getOrderCode, 'test')中的Order::getOrderCode传入的其实是SFunction对象,通过AbstractLambdaWrapper.columnsToString办法将SFunction转为column名称。

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

LambdaUtils是解析的外围代码类。

public final class LambdaUtils {    public static <T> SerializedLambda resolve(SFunction<T, ?> func) {        Class<?> clazz = func.getClass();        String name = clazz.getName();        return Optional.ofNullable(FUNC_CACHE.get(name))                .map(WeakReference::get)                .orElseGet(() -> {                    SerializedLambda lambda = SerializedLambda.resolve(func);                    FUNC_CACHE.put(name, new WeakReference<>(lambda));                    return lambda;                });    }}    public class SerializedLambda implements Serializable {    /**     * 通过反序列化转换 lambda 表达式,该办法只能序列化 lambda 表达式,不能序列化接口实现或者失常非 lambda 写法的对象     *     * @param lambda lambda对象     * @return 返回解析后的 SerializedLambda     */    public static SerializedLambda resolve(SFunction<?, ?> lambda) {        if (!lambda.getClass().isSynthetic()) {            throw ExceptionUtils.mpe("该办法仅能传入 lambda 表达式产生的合成类");        }        try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {            @Override            protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {                Class<?> clazz;                try {                    clazz = ClassUtils.toClassConfident(objectStreamClass.getName());                } catch (Exception ex) {                    clazz = super.resolveClass(objectStreamClass);                }                return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;            }        }) {            return (SerializedLambda) objIn.readObject();        } catch (ClassNotFoundException | IOException e) {            throw ExceptionUtils.mpe("This is impossible to happen", e);        }    }}        public class SerializationUtils {        /**     * Serialize the given object to a byte array.     *     * @param object the object to serialize     * @return an array of bytes representing the object in a portable fashion     */    public static byte[] serialize(Object object) {        if (object == null) {            return null;        }        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {            oos.writeObject(object);            oos.flush();        } catch (IOException ex) {            throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);        }        return baos.toByteArray();    } }       
  1. 先听过SerializationUtils.serializeSFunction序列化,获取类字节。
  2. 再通过重组成SerializedLambda.resolve对象,通过lambda.getClass().isSynthetic()确保SFunction肯定是个lambda表达式
  3. 最初放入FUNC_CACHE缓存。
学到了一手 ^_^

解析进去的lambda对象信息是这样的:

public abstract class AbstractLambdaWrapper<T, Children extends AbstractLambdaWrapper<T, Children>>    private String getColumn(SerializedLambda lambda, boolean onlyColumn) {        Class<?> aClass = lambda.getInstantiatedType();        tryInitCache(aClass);        String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());        ColumnCache columnCache = getColumnCache(fieldName, aClass);        return onlyColumn ? columnCache.getColumn() : columnCache.getColumnSelect();    }    }
  1. 从初始化时创立的缓存里,获取对象缓存列表
  2. 调用PropertyNamer.methodToProperty(lambda.getImplMethodName())从办法中提取属性名,例如从getCodeName提取到codeName
  3. 依据属性名从缓存中获取对应的列信息。

以上就是数据执行流程,通过LambdaWrapper.columnToString可用获取到属性的数据库字段名,然而columnToString办法是protected,无奈间接应用,能够通过继承的形式来扩大 。

实现

  1. 扩大类继承AbstractLambdaWrapper,重写columnToString办法, 然而内容是调用父类的办法,只是把办法从protected改为public
  2. 因为mybatis-plus会在我的项目启动时初始化信息,也就是扩大类必须是在相干的类初始化实现之后能力用,所以也要把扩大类退出到spring容器中。
@Componentpublic class MybatisPlusColumnResolver {    public <T> ColumnResolver<T> create() {        return new ColumnResolver<>();    }    /**     * @author      * @date      */    public static class ColumnResolver<T> extends AbstractLambdaWrapper<T, ColumnResolver<T>> {        @Override        protected ColumnResolver<T> instance() {            return null;        }        @Override         public String columnsToString(SFunction<T, ?>... columns) {            return super.columnsToString(columns);        }        @Override         public String columnsToString(boolean onlyColumn, SFunction<T, ?>... columns) {            return super.columnsToString(onlyColumn, columns);        }        @Override         public String columnToString(SFunction<T, ?> column) {            return super.columnToString(column);        }        @Override         public String columnToString(SFunction<T, ?> column, boolean onlyColumn) {            return super.columnToString(column, onlyColumn);        }    }}

应用

    @Autowired    private MybatisPlusColumnResolver lambdaColumnResolvers;        ColumnResolver<Order> columnResolver = lambdaColumnResolvers.create();     Wrappers.<OmsOrder>query().select("distinct " + columnResolver.columnToString(OmsOrder::getCodeName));