IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Mybatisplus映射实体类改变字段顺序映射出错问题解决 -> 正文阅读

[大数据]Mybatisplus映射实体类改变字段顺序映射出错问题解决

问题描述

xml内容如下

<select id="XXX" resultType="XXX.XX.AppResubscribeVO">
        SELECT app.app_name,
               app_id,
               spec.spec_name,
               ...

实体类如下

@Data
@AllArgsConstructor
public class AppResubscribeVO
{
    @ApiModelProperty(value = "应用名")
    private String appName;

    @ApiModelProperty(value = "规格名")
    private String specName;

    @ApiModelProperty(value = "应用ID")
    private Integer appId;
   ...

如图,xml的字段(app_id,spec_name)和实体的字段(specName,appId)顺序不一致,且两个字段的类型也不一致,分别是String和Integer。此时又加了注解@AllArgsConstructor全参构造函数,则没有默认的无参构造函数。这种情况就会导致映射实体出错。

源码剖析

此问题的核心源码mybatisplus是
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
其他过程源码略
我们来看一下这个方法
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
      throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor() ) {
      return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
      return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
  }

逻辑很简单,关键有一行metaType.hasDefaultConstructor()
见名知意,如果元数据类型有默认的构造方法,就走objectFactory.create(resultType);
没有并且shouldApplyAutomaticMappings(resultMap, false)就走

createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);

这两个方法这里不具体展开了。简单来说第一个方法按照字段名称映射,允许映射时字段不一致,第二个是按照字段自然顺序映射的,不允许不一致。

实际验证,就是实体类有无参构造方法时走第一个,没有时走第二个。
现在我们来看一下

final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);

metaType.hasDefaultConstructor()
具体分步骤如下

创建MetaClass

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }

MetaClass构造方法

private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    this.reflector = reflectorFactory.findForClass(type);
  }

findForClass()调用Reflector构造方法

@Override
  public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {
      return new Reflector(type);
    }
  }

Reflector构造方法

public Reflector(Class<?> clazz) {
    type = clazz;
    addDefaultConstructor(clazz);
    addGetMethods(clazz);
    addSetMethods(clazz);
    addFields(clazz);
    readablePropertyNames = getMethods.keySet().toArray(new String[0]);
    writablePropertyNames = setMethods.keySet().toArray(new String[0]);
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }

这里有一行关键的addDefaultConstructor(clazz);看一下

private void addDefaultConstructor(Class<?> clazz) {
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
      .findAny().ifPresent(constructor -> this.defaultConstructor = constructor);
  }

可以看到,就是找到clazz的无参构造方法,并设置为defaultConstructor

metaType.hasDefaultConstructor()
就是reflector的hasDefaultConstructor()

public boolean hasDefaultConstructor() {
    return reflector.hasDefaultConstructor();
  }

判断有没有defaultConstructor

public boolean hasDefaultConstructor() {
    return defaultConstructor != null;
  }

总结

实体类上添加了@AllArgsConstructor,导致实体类没有无参构造方法。所以映射时走的按照字段自然顺序映射。了解了原理,如果想要调换顺序也很简单了。

解决方案

1.去掉@AllArgsConstructor。
如果不需要@AllArgsConstructor,直接去掉就好了,这样无参构造方法自然就默认有了。
2.手动添加@NoArgsConstructor.
手动添加一个无参构造方法。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:41:02  更:2022-02-01 20:43:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:50:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码