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插入数据字段为空的资料请关注我们其它相关文章!