You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试事务机制时保存嵌套实体触发DuplicateKeyException异常的解决方法求助

测试事务机制时保存嵌套实体触发DuplicateKeyException异常的解决方法求助

我正在测试Spring事务的工作机制,写了一个三层关联实体的简单示例(BettingProgram → Event → Market),但在手动管理事务保存Market时,遇到了如下异常:

org.springframework.dao.DuplicateKeyException: A different object with the same identifier value was already associated with the session: [com.gestionebettingoffer.model.Event#EventId(idBettingProgram=784, idEvent=1726)]

想请教大家这个问题该怎么解决?


实体类代码

BettingProgram.java

public class BettingProgram {
    @Id
    private int id;
    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "date", nullable = false)
    private LocalDate date;
    @JsonManagedReference
    @OneToMany(mappedBy = "bettingProgram", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Event> events;
}

Event.java

public class Event {
    @EmbeddedId
    private EventId eventId;
    @JsonBackReference
    @ManyToOne
    @MapsId("idBettingProgram")
    @JoinColumn(name = "idBettingProgram")
    private BettingProgram bettingProgram;
    @Column(name = "type", nullable = false)
    private String type;
    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "dateEvent", nullable = false)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    private LocalDateTime dateEvent;
    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "event", cascade = CascadeType.ALL)
    private List<Market> marketList;
}

Market.java

public class Market {
    @EmbeddedId
    private MarketId marketId;
    @JsonBackReference
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name = "idEvent", referencedColumnName = "idEvent"),
        @JoinColumn(name = "idBettingProgram", referencedColumnName = "idBettingProgram")
    })
    @Valid
    @MapsId("eventId")
    private Event event;
    @Column(name = "name", nullable = false)
    private String name;
    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false)
    private Status status;
}

事务相关业务方法

下面是负责实体插入的核心业务方法,我通过txManager手动管理事务:

public BettingProgramDTO createBettingProgramWithManualTransaction(BettingProgramDTO bettingProgramDTO) throws CustomException {
    log.info("Betting program da creare : {}", bettingProgramDTO);
    BettingProgram bettingProgramCreated = methodCreaBettingPAndEvent(bettingProgramDTO);
    BettingProgramDTO bettingProgramDTOCreated = bettingProgramMapper.toDTO(bettingProgramCreated);
    TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
    try {
        List<MarketDTO> marketDTOList = methodCreaMArketSelection(bettingProgramCreated.getEvents().getFirst(), bettingProgramDTO.getEvents().getFirst().getMarketList());
        bettingProgramDTOCreated.getEvents().getFirst().setMarketList(marketDTOList);
    } catch (Exception e) {
        log.error("{} Got an error:{}", LogUtil.errorLogString(), e.getMessage(), e);
        txManager.rollback(txStatus);
    }
    txManager.commit();
    return bettingProgramDTOCreated;
}

public BettingProgram methodCreaBettingPAndEvent(BettingProgramDTO bettingProgramDTO) throws CustomException {
    Optional<BettingProgram> optBettingProgram = bettingProgramRepository.findById(bettingProgramDTO.getId());
    if (optBettingProgram.isPresent()){
        throw new NotValidFieldException("Betting program già esistente.");
    }
    BettingProgram bettingProgram = bettingProgramMapper.toEntityWithoutSon(bettingProgramDTO);
    BettingProgram bettingProgramInserted = bettingProgramRepository.save(bettingProgram);
    EventDTO eventDTO = bettingProgramDTO.getEvents().getFirst();
    eventDTO.setIdBettingProgram(bettingProgramInserted.getId());
    log.info("Event da creare : {}", eventDTO);
    Event eventCreated = eventMapper.toEntityWithoutSon(eventServiceTransactional.createEventWithTransaction(eventDTO));
    bettingProgramInserted.getEvents().add(eventCreated);
    return bettingProgramInserted;
}

public List<MarketDTO> methodCreaMArketSelection(Event event, List<MarketDTO> marketDTO) throws CustomException {
    List<MarketDTO> marketDTOList = new ArrayList<>();
    for (MarketDTO marketDTOToCreate : marketDTO){
        marketDTOToCreate.setIdBettingProgram(event.getEventId().getIdBettingProgram());
        marketDTOToCreate.setIdEvent(event.getEventId().getIdEvent());
        log.info("Market dto da creare : {}", marketDTOToCreate);
        MarketDTO marketDTOCreated = marketServiceTransactional.createMarketWithTransaction(event, marketDTOToCreate);
        marketDTOList.add(marketDTOCreated);
    }
    return marketDTOList;
}

我的疑问

我猜测问题可能和手动事务的边界、JPA持久化上下文的关联冲突有关?比如Event实体的引用在不同的事务/持久化上下文中重复关联了?有没有大佬能帮我分析下具体原因,并且给出可行的解决办法?


内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:14:40