文章
问答
冒泡
orika 中解决UnmappableEnum抛错的错误

在使用orika 做bean工具的时候,如果beanA的某属性类型为Integer ,要转换为beanB的同名属性的类型是一个enum,这个时候,就会出现如下报错:

Error occurred: ma.glasnost.orika.MappingException: Encountered mapping of enum to object (or vise-versa); sourceType=Integer, destinationType=GenderEnum


Caused by: ma.glasnost.orika.MappingException: Encountered mapping of enum to object (or vise-versa); sourceType=Integer, destinationType=GenderEnum
at ma.glasnost.orika.impl.generator.specification.UnmappableEnum.generateMappingCode(UnmappableEnum.java:41)
at ma.glasnost.orika.impl.generator.SourceCodeContext.mapFields(SourceCodeContext.java:759)



我们找到这个UnmappableEnum类,可以看到此处的抛错代码

public class UnmappableEnum extends AbstractSpecification {

    public boolean appliesTo(FieldMap fieldMap) {
        return fieldMap.getBType().isEnum() && !fieldMap.getAType().isEnum() && !fieldMap.getAType().isString();
    }

    public String generateMappingCode(FieldMap fieldMap, VariableRef source, VariableRef destination, SourceCodeContext code) {
        throw new MappingException("Encountered mapping of enum to object (or vise-versa); sourceType="+
                source.type() + ", destinationType=" + destination.type());
    }

}



下面我们顺藤摸瓜,找到默认的代码生成策略类DefaultCodeGenerationStrategy,可以看到这段代码

public DefaultCodeGenerationStrategy() {

    this.specifications = new CopyOnWriteArrayList<Specification>(
            Arrays.asList(
                    new ConvertArrayOrCollectionToArray(),
                    new ConvertArrayOrCollectionToCollection(),
                    new Convert(),
                    new CopyByReference(),
                    new ApplyRegisteredMapper(),
                    new EnumToEnum(),
                    new StringToEnum(),
                    new UnmappableEnum(),
                    new ArrayOrCollectionToArray(),
                    new ArrayOrCollectionToCollection(),
                    new MapToMap(),
                    new MapToArray(),
                    new MapToCollection(),
                    new ArrayOrCollectionToMap(),
                    new StringToStringConvertible(),
                    new AnyTypeToString(),
                    new MultiOccurrenceElementToObject(),
                    new ObjectToMultiOccurrenceElement(),
                    new PrimitiveAndObject(),
                    new ObjectToObject()));

    this.aggregateSpecifications = new CopyOnWriteArrayList<AggregateSpecification>(
            Arrays.asList(new MultiOccurrenceToMultiOccurrence()));

}



由代码逻辑可以看到,默认策略下,枚举只支持Enum和String 两种类型的转换,并不支持其他类型转枚举的。一般情况下,我们数据库记录的应该是枚举的name值,但是,如果遇到某些历史遗留问题 ,暂时不能改变,怎么办?只能让orika支持枚举转换。

我们来试试,在不改动orika的情况下,怎么通过配置,让其支持Integer转Enum。
环境:group: 'com.gitlab.haynes', name: 'orika-spring-boot-starter', version: '1.26.0'

UserA

@Data
@With
@AllArgsConstructor
@NoArgsConstructor
public class UserA {
    private String name;
    private Integer age;
    private Integer gender;
}



UserB

@Data
@With
@AllArgsConstructor
@NoArgsConstructor
public class UserB {
    private String name;
    private Integer age;
    private GenderEnum gender;
}



GenderEnum

@AllArgsConstructor
public enum GenderEnum {
    MALE(1,"男"),
    FEMALE(2,"女");

    @Getter
    @Setter
    private Integer value;

    @Setter
    @Getter
    private String label;
}



我们在UserA中的gender属性是Integer的,UserB中的gender属性是Enum的。直接转换,这里会报开头我们说的错误。

@Test
public void t1(){
    UserA userA = new UserA().withName("test").withAge(10).withGender(1);
    UserB userB = mapperFacade.map(userA, UserB.class);
    System.out.println(userB);
}



我们来解决这个问题
1.通过自定义mapper的方式
在GenderEnum添加一个静态函数,通过value可以获得对应的enum

@AllArgsConstructor
public enum GenderEnum {
    MALE(1,"男"),
    FEMALE(2,"女");

    @Getter
    @Setter
    private Integer value;

    @Setter
    @Getter
    private String label;

    public static GenderEnum of(Integer value){
        if(Objects.isNull(value)){
            return null;
        }
        for (GenderEnum item: GenderEnum.values()){
            if(Objects.equals(item.getValue(),value)){
                return item;
            }
        }
        return null;
    }
}





写一个configurer自己,默认情况下不去给gender属性复制,后面自己给他手动赋值。

@Component
public class UserMapping implements OrikaMapperFactoryConfigurer {
    @Override public void configure(MapperFactory mapperFactory) {
        mapperFactory.classMap(UserA.class,UserB.class).exclude("gender").byDefault().customize(
                new CustomMapper<UserA, UserB>() {
                    @Override public void mapAtoB(UserA userA, UserB userB, MappingContext context) {
                        userB.setGender(GenderEnum.of(userA.getGender()));
                    }
                }).register();
    }
}



测试结果如下

UserB(name=test, age=10, gender=MALE)



2.注册全局的converter
如果GenderEnum会在很多地方使用的话,在User对象转换中指定,就不太通用了。我们可以注册成为全局的Converter。

@Component
public class GenderConverter extends CustomConverter<Integer,GenderEnum> {
    @Override public GenderEnum convert(Integer source, Type<? extends GenderEnum> destinationType,
            MappingContext mappingContext) {
        return GenderEnum.of(source);
    }
}


@RequiredArgsConstructor
@Component
public class GlobalMapperFactoryConfigurer implements OrikaMapperFactoryConfigurer {

    private final List<Converter<?,?>> converters;

    @Override public void configure(MapperFactory orikaMapperFactory) {
        ConverterFactory converterFactory = orikaMapperFactory.getConverterFactory();
        if(!CollectionUtils.isEmpty(converters)){
            converters.forEach(converterFactory::registerConverter);
        }
    }
}



运行测试代码:

UserB(name=test, age=10, gender=MALE)

orika

关于作者

落雁沙
非典型码农
获得点赞
文章被阅读