【问题标题】:Cyclic references when converting with MapStruct. Overflow error. Context does not work使用 MapStruct 转换时的循环引用。溢出错误。上下文不起作用
【发布时间】:2022-02-08 02:36:10
【问题描述】:

我有 2 个实体,具有一对一关联(ProfileEntity 和 VCardEntity)

实体电子名片:

@Entity
@Table(name = "vcard")
@AllArgsConstructor
@NoArgsConstructor
@Data
@SequenceGenerator(name="vcard_id_seq_generator", sequenceName="vcard_id_seq", allocationSize = 1)
public class VCardEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "vcard_id_seq_generator")
    @Column(name="vcard_id")
    Long id;
    String account;
    @Column(name = "first_name")
    String firstName;
    @Column(name = "last_name")
    String lastName;
    @Column(name = "pbxinfo_json")
    String pbxInfoJson;
    @Column(name = "avatar_id")
    String avatarId;
    @OneToOne(mappedBy = "vcard")
    ProfileEntity profile;
}

实体简介:

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Data
@Table(name = "profile")
public class ProfileEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "profile_id")
    private Long profileId;

    private String account;
    @Column(name = "product_id")
    private String productId;
    
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "vcard_id", referencedColumnName = "vcard_id")
    private VCardEntity vcard;
}

我使用 map 结构如下:

public class CycleAvoidingMappingContext {
    private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();

    @BeforeMapping
    public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
        return targetType.cast(knownInstances.get(source));
    }
    
    @BeforeMapping
    public void storeMappedInstance(Object source, @MappingTarget Object target) {
        knownInstances.put( source, target );
    }
}

@Mapper(componentModel = "spring")
public interface EntityToProfile {
    ProfileEntity profileToEntity(Profile profile, @Context CycleAvoidingMappingContext context);
    Profile entityToProfile(ProfileEntity entity, @Context CycleAvoidingMappingContext context);
}

@Mapper(componentModel = "spring")
public interface EntityToVCard {
    VCard entityToVcard(VCardEntity entity, @Context CycleAvoidingMappingContext context);
    VCardEntity vcardToEntity(VCard vcard, @Context CycleAvoidingMappingContext context);
}

最后我在我的服务中调用映射:

@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultChatService implements ChatService {
    private final ProfileRepository profileRepository;
    private final EntityToProfile entityToProfileMapper;
    private final EntityToVCard entityToVCardMapper;

    @Override
    public List<Profile> findAllProfile(Optional<Long> id) {
        if (id.isPresent()) {
            Optional<ProfileEntity> result = profileRepository.findById(id.get());
            if (result.isPresent()) {
                Profile profile = entityToProfileMapper.entityToProfile(result.get(), new CycleAvoidingMappingContext());
                return Stream.of(profile).collect(Collectors.toList());
            }
        }
        return new ArrayList<Profile>();
    }
}

我得到了错误 ERROR 15976 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]:Servlet.service() 用于路径 [] 上下文中的 servlet [dispatcherServlet] 引发异常 [Handler发送失败;嵌套异常是 java.lang.StackOverflowError] 的根本原因 java.lang.StackOverflowError: null

有什么想法可以解决吗? 在我看来,我所做的一切都是在这里写的 Prevent Cyclic references when converting with MapStruct 但它对我不起作用

【问题讨论】:

    标签: java spring mapstruct


    【解决方案1】:

    找到了解决方案,我所要做的就是将我的模型从 @Value 更改为 @Data

    给出的例子:

    @Data
    public class Profile {
        Long profileId;
        String account;
        String productId;
        VCard vcard;
    }
    

    @Data
    public class VCard {
        Long id;
        String account;
        String firstName;
        String lastName;
        String pbxInfoJson;
        String avatarId;
        Profile profile;
    }
    

    否则 mapstruct 无法生成正确的映射代码。它试图在创建对象后将实例存储在 knownInstances 中,例如 Profile。但是因为 @value 在创建对象(不可变对象)后没有提供设置属性的方法,所以它必须先创建所有设置,然后使用所有 args 构造函数,这首先导致映射配置文件,而后者又试图做同样的事情并在将 VCard 对象存储在 knownInstances 中之前先映射 vcard。 这就是循环引用问题无法解决的原因

    正确生成的代码:

    public Profile entityToProfile(ProfileEntity entity, CycleAvoidingMappingContext context) {
            Profile target = context.getMappedInstance( entity, Profile.class );
            if ( target != null ) {
                return target;
            }
    
            if ( entity == null ) {
                return null;
            }
    
            Profile profile = new Profile();
    
            context.storeMappedInstance( entity, profile );
    
            profile.setAccount( entity.getAccount() );
            profile.setProductId( entity.getProductId() );
            profile.setDeviceListJson( entity.getDeviceListJson() );
            profile.setLastSid( entity.getLastSid() );
            profile.setBalanceValue( entity.getBalanceValue() );
            profile.setBalanceCurrency( entity.getBalanceCurrency() );
            profile.setStatusJson( entity.getStatusJson() );
            profile.setData( entity.getData() );
            profile.setMissedCallsCount( entity.getMissedCallsCount() );
            profile.setFirstCallSid( entity.getFirstCallSid() );
            profile.setLastMissedCallSid( entity.getLastMissedCallSid() );
            profile.setRemoveToCallSid( entity.getRemoveToCallSid() );
            profile.setOutgoingLines( entity.getOutgoingLines() );
            profile.setFeatures( entity.getFeatures() );
            profile.setPermissions( entity.getPermissions() );
            profile.setVcard( vCardEntityToVCard( entity.getVcard(), context ) );
    
            return profile;
        }
    }
    

    如您所见,首先,它将对象保存在 context.storeMappedInstance( entity, profile );然后填充属性。

    【讨论】:

    • 它是否与 Lombok 的 @Builder 一起使用?
    猜你喜欢
    • 2016-07-13
    • 2016-11-13
    • 2019-07-29
    • 2021-06-22
    • 1970-01-01
    • 2016-05-02
    • 2021-09-05
    • 2014-01-04
    相关资源
    最近更新 更多