Mapstruct对象插入数据库某个字段总是为空的bug详解

目录
  • 前言
  • 如何调试Maven插件
  • 源码解析

前言

在一次需求的开发过程中,发现一个对象插入数据库时某个字段总是为空。

版本:lombok:1.18.24、mapstruct:1.5.2.Final

简化后的代码如下:

@Autowired
    private PersonService personService;
    public void test1(){
        Person person = personService.findById(1L);
        PersonDto personDto = PersonMapper.INSTANCE.personToPersonDto(person);
        personService.insert(personDto);
    }

这么简单的逻辑按理说不会出幺蛾子啊,我先排查了数据库里person id=1的记录发现值是有的啊,然后又排查了我的insert方法,也是没问题的。

经过一段时间的排查,才发现是

PersonDto personDto = PersonMapper.INSTANCE.personToPersonDto(person);

这行代码的问题。证据截图如下:

前面的时候addTeacherNum还有值,转化后怎么又没值了呢?

大家看到这里肯定猜测是不是我属性名不对,或者属性类型不对。我甚至还删除了之后用复制的方式来保证没有手敲敲错的情况。

完全是一模一样的属性啊。

我们知道mapstruct是编译时通过我们的PersonMapper接口来实现实现类,实现类里是setter、getter方法来实现的。于是我打开了PersonMapper的实现类准备一探究竟:

public class PersonMapperImpl implements PersonMapper {
    public PersonMapperImpl() {
    }
    public PersonDto personToPersonDto(Person person) {
        if (person == null) {
            return null;
        } else {
            PersonDtoBuilder personDto = PersonDto.builder();
            personDto.name(person.getName());
            return personDto.build();
        }
    }
}

竟然没有对我这个属性addTeacherNum进行赋值。这让我百思不得其解。只能去看看源码,试图找出原因。

如何调试Maven插件

前面我们提到mapstruct是在代码编译的时候就开始生成代码了,于是我们需要对maven编译期进行调试。方法如下:

  • maven debug命令
mvnDebug clean compile
  • idea远程debug

新建一个remote,然后修改端口为8000,然后在执行maven命令的同时,启动这个remote即可。

源码解析

断点应该打在哪里呢?

我们查看mapstruct的结构,一般先从配置的文件入手

找到了这个MappingProcessor类,我们可以看到这里面有个process方法,里面又调用了如下的这个方法:

private void processMapperTypeElement(ProcessorContext context, TypeElement mapperTypeElement) {
    Object model = null;
    for ( ModelElementProcessor<?, ?> processor : getProcessors() ) {
        try {
            model = process( context, processor, mapperTypeElement, model );
        }
        catch ( AnnotationProcessingException e ) {
           //省略
        }
    }
}

这段代码其实就是调用getProcessors()方法拿到多个processor,然后遍历调用。而这个getProcessors()就是从配置文件里通过SPI的方式加载对象。

这里面我们重点关注这个Processor:MapperCreationProcessor。它的process方法如下:

@Override
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
    this.elementUtils = context.getElementUtils();
    this.typeUtils = context.getTypeUtils();
    this.messager =
        new MapperAnnotatedFormattingMessenger( context.getMessager(), mapperTypeElement, context.getTypeUtils() );
    this.options = context.getOptions();
    this.versionInformation = context.getVersionInformation();
    this.typeFactory = context.getTypeFactory();
    this.accessorNaming = context.getAccessorNaming();
    MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() );
    List<MapperReference> mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions );
    MappingBuilderContext ctx = new MappingBuilderContext(
        typeFactory,
        elementUtils,
        typeUtils,
        messager,
        accessorNaming,
        context.getEnumMappingStrategy(),
        context.getEnumTransformationStrategies(),
        options,
        new MappingResolverImpl(
            messager,
            elementUtils,
            typeUtils,
            typeFactory,
            new ArrayList<>( sourceModel ),
            mapperReferences,
            options.isVerbose()
        ),
        mapperTypeElement,
        //sourceModel is passed only to fetch the after/before mapping methods in lifecycleCallbackFactory;
        //Consider removing those methods directly into MappingBuilderContext.
        Collections.unmodifiableList( sourceModel ),
        mapperReferences
    );
    this.mappingContext = ctx;
    return getMapper( mapperTypeElement, mapperOptions, sourceModel );
}

getMapper里面有一段这个方法引起我的注意:

List<MappingMethod> mappingMethods = getMappingMethods( mapperOptions, methods );

猜测这段就是获取要写入的set、get方法。 于是一路跟踪:

发现mapstruct里面把方法分为了下面四类,而我的addTeacherNum属性通过lombok生成的方法methodType被分到了ADDER里面。

而在生成我的Mapper实现类的时候它会只过滤setter方法。

List<Accessor> candidates = new ArrayList<>( getSetters() );

至此真相大白。

以上就是Mapstruct中对象插入数据库某个字段总是为空的bug详解的详细内容,更多关于Mapstruct插入数据字段为空的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java MapStruct解了对象映射的毒

    前言 MVC模式是目前主流项目的标准开发模式,这种模式下框架的分层结构清晰,主要分为Controller,Service,Dao.分层的结构下,各层之间的数据传输要求就会存在差异,我们不能用一个对象来贯穿3层,这样不符合开发规范且不够灵活. 我们常常会遇到层级之间字段格式需求不一致的情况,例如数据库中某个字段是datetime日期格式,这个时间戳在数据库中的存储值为2020-11-06 23:59:59.999999,但是传递给前端的时候要求接口返回yyyy-MM-dd的格式,或者有些数据在数据

  • SpringBoot + MapStruct 属性映射工具的使用详解

    1. MapStruct 是什么? 截取下官方的原话 我给翻译了一下 说白了 当你的对象A有几十个属性 而另一个对象B 与A比较只有一些细微的差别 那么这时候只需要映射过去即可 而不需要疯狂的调用set方法 进行属性的拷贝 这就是这个工具给我们带来的最大便利 官方github链接 点击跳转 2. 引入依赖 采用Mapstruct的 最新版本 1.4.2.Final SpringBoot版本不要选新版的 我对比了下 2.3.0 和 2.5.4 后者会出现属性映射为null的情况 已经将问题反馈了

  • MapStruct表达式应用及避坑详解

    目录 前言 遇到的问题 发现原因 结语 前言 生成的映射代码使用简单的方法调用,因此速度快,类型安全且易于理解.MapStruct的表达式功能是为了处理特殊对象属性的映射问题,比如DTO中的status属性转换成PO中的status需要进一步的处理,这个时候就需要用到表达式功能了.这里不再赘述关于MapStruct的使用问题,更多的使用教程可参考文档 MapStruct官方文档:https://mapstruct.org/documentation/stable/reference/html/#

  • Java实体映射工具MapStruct使用方法详解

    目录 1.序 2.简单用例 3.使用详解 1)关于接口注解@Mapper几种属性用法详解 2) 其他方法级别注解 总结 1.序 通常在后端开发中经常不直接返回实体Entity类,经过处理转换返回前端,前端提交过来的对象也需要经过转换Entity实体才做存储:通常使用的BeanUtils.copyProperties方法也比较粗暴,不仅效率低下(使用反射)而且仅映射相同名的属性,多数情况下还需要手动编写对应的转换方法实现. 插件MapStruct以接口方法结合注解优雅实现对象转换,MapStruc

  • java设计模式七大原则之依赖倒转原则详解

    目录 1.什么是依赖倒转原则? 2.代码案例 3.依赖关系传递的三种方式和案例举例 3.1 接口传递 3.2 构造方法传递 3.3 setter方法传递 4.依赖倒转原则总结 1.什么是依赖倒转原则? 高层模块不应该依赖低层模块,二者都应该依赖其抽象. 抽象不应该依赖细节,细节应该依赖抽象. 依赖倒转 (倒置) 的中心思想是面向接口编程. 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多.以抽象为基础搭建的架构比以细节为基础的架构要稳定的多.在Java中,抽象指的是接口

  • Mapstruct对象插入数据库某个字段总是为空的bug详解

    目录 前言 如何调试Maven插件 源码解析 前言 在一次需求的开发过程中,发现一个对象插入数据库时某个字段总是为空. 版本:lombok:1.18.24.mapstruct:1.5.2.Final 简化后的代码如下: @Autowired private PersonService personService; public void test1(){ Person person = personService.findById(1L); PersonDto personDto = Perso

  • Android使用Realm数据库实现App中的收藏功能(代码详解)

    前 言 App数据持久化功能是每个App必不可少的功能,而Android最常用的数据持久化方式主要有以下的五种方式: 使用SharedPreferences存储数据: 文件存储数据: SQLite数据库存储数据: 使用ContentProvider存储数据: 网络存储数据. 其中前四种都是缓存数据到本地,这篇主要讲的是使用第三种方式来实现App中的收藏功能,不过不用Android原生自带SQLite数据库来存储数据,而是使用第三方的Realm数据库来来存储数据. Realm 本质上是一个嵌入式数

  • MySQL数据库基础学习之JSON函数各类操作详解

    目录 前言 一.JSON语法规则 二.JSON函数 1.JSON_CONTAINS(json_doc,value)函数 2.JSON_SEARCH()函数 3.JSON_PRETTY(json_doc)函数 4.JSON_DEPTH(json_doc)函数 5.JSON_LENGTH(json_doc[,path])函数 6.JSON_KEYS(json_doc[,path])函数 7. JSON_INSERT(json_doc,path,val[,path,val] ...)函数 8.JSON

  • MySQL数据库设计之利用Python操作Schema方法详解

    弓在箭要射出之前,低声对箭说道,"你的自由是我的".Schema如箭,弓似Python,选择Python,是Schema最大的自由.而自由应是一个能使自己变得更好的机会. Schema是什么? 不管我们做什么应用,只要和用户输入打交道,就有一个原则--永远不要相信用户的输入数据.意味着我们要对用户输入进行严格的验证,web开发时一般输入数据都以JSON形式发送到后端API,API要对输入数据做验证.一般我都是加很多判断,各种if,导致代码很丑陋,能不能有一种方式比较优雅的验证用户数据呢

  • MySQL数据库学习之排序与单行处理函数详解

    目录 1.排序 2.单行处理函数 内容转小写 内容转大写 取子串 字符串拼接 求长度 去除前后空白 四舍五入 生成随机数 空转换 1.排序 示例表内容见此篇文章 Mysql支持数据排序操作,例如,现在我们按照工资从小到大进行排序操作: mysql> select ename,sal from emp order by sal; +--------+---------+ | ename | sal | +--------+---------+ | SMITH | 800.00 | | JAMES

  • 基于Java class对象说明、Java 静态变量声明和赋值说明(详解)

    先看下JDK中的说明: java.lang.Object java.lang.Class<T> Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class tha

  • 判断python对象是否可调用的三种方式及其区别详解

    查找资料,基本上判断python对象是否为可调用的函数,有三种方法 使用内置的callable函数 callable(func) 用于检查对象是否可调用,返回True也可能调用失败,但是返回False一定不可调用. 官方文档:https://docs.python.org/3/library/functions.html?highlight=callable#callable 判断对象类型是否是FunctionType type(func) is FunctionType # 或者 isinst

  • Oracle数据库服务器修改操作系统时间的注意事项详解

    Oracle 数据库服务器修改操作系统时间的注意事项: 对单机或者ha 1.对数据库本身而言,其实是没有影响的.因为scn不依赖于os时间 2.对app(应用程序)而言,若是app中使用了sysdate之类的,那确实是有影响的. 基于这个情况,我们一般推荐:改os时间 不往之前的时间去改,而是往今后的时间去改. 推荐:安装oracle10g时候注意事项&修改oracle数据库字符集编码 [安装oracle10g时候注意事项:1. 关闭网络连接2.--修改oracle数据库字符集编码:先用syst

  • C++类与对象深入之运算符重载与const及初始化列表详解

    目录 一:运算符重载 相等运算符重载 赋值运算符重载 小于运算符重载 二:const成员 const修饰类的成员函数 三:cin.cout重载 四:初始化列表 构造函数赋初值 初始化列表 explicit关键字 一:运算符重载 C++为了增强代码的可读性引入了运算符的重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型以及参数列表,其返回值类型与参数列表与普通函数类似. 函数名字为:关键字operator后面接需要重载的运算符符号 函数原型:返回值类型 operator操作符(参数列表)

  • MYSQL插入数据时检查字段值是否重复的方法详解

    项目需求 现有一张u_ps的车位信息表,对应每个小区的车位id ps_id自增长,每个车位又对应车位编号num,车位id由自增长,车位编号可以根据自己小区情况手动添加设置,但是不能重复,车位编号不能重复.这就需要在新增车位信息的时候代码检验新增的该num在u_ps表中是否存在,存在则抛出异常,不存在则做插入操作. 问题关键 不同的小区community_id中车位num可以重复,如果分开设计表设置唯一约束是可行的,但是我们的需求是将所有的小区车位信息放在同一个表中,这就涉及到在插入一条数据的时候

随机推荐