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

Spring Boot更新实体报错:Association实例标识被修改问题咨询

问题:Spring Boot修改非主键属性时触发标识符变更异常

异常信息

identifier of an instance of Association was altered from Association@42c3ddac to Association@5f8c91f9

场景描述

仅尝试修改Association实体的非主键属性statut为true并保存,未修改任何主键相关内容,但保存时触发上述异常。

接口代码

@PutMapping("/accept")
public ResponseEntity<SoumissionDto> setAssociationToAccepted(@RequestBody AssociationDto associationDto) {
    try {
        Canvas canvas = canvasService.getCanvasByCodeInvitation(associationDto.getCodeInvitation());
        Canvas canvasSoumis = canvasService.getCanvasById(associationDto.getGuidcanvasIndividuel());
        Association updatedAssociation = associationService.getAssociationByCanvasCollabAndCanvas(canvas.getGUIDCanvas(), canvasSoumis.getGUIDCanvas());
        if (updatedAssociation == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } else {
            System.out.println("j'ai eu cette asso id :  "+ updatedAssociation.getCanvas().getGUIDCanvas().toString()+"  id collab  " + updatedAssociation.getCanvasEquipe().getGUIDCanvas().toString());
            updatedAssociation.setStatut(true);
            
            Association returnAssociation = associationService.updateAssociation(updatedAssociation);
            SoumissionDto soumissionDto = createSoumissionDtoFromAssociation(returnAssociation);
            return new ResponseEntity<>(soumissionDto, HttpStatus.OK);
        }
    } catch (Exception e) {
        System.out.println("J'ai une erreur : "+e.getMessage());
        System.out.println("stack : "+ Arrays.toString(e.getStackTrace()));
        System.out.println("cause : "+e.getCause());

        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Association实体类定义

@Entity
@Table(name = "Association")
@IdClass(Association.class)
public class Association {

    @Id
    @ManyToOne
    @JoinColumn(name = "canvas_collectif", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonBackReference
    private Canvas canvas_collectif;

    @Id
    @ManyToOne
    @JoinColumn(name = "canvas", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonBackReference
    private Canvas canvas;

    @Column(name = "statut", nullable = false, length = 200)
    private boolean statut;
    
    // 常规getter和setter
}

数据库结构

Association表主键由canvas_id和canvas_collectif_id组成,statut为布尔类型字段。

完整堆栈信息

org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:321),
org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233),
org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566),
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743),
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711),
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654),
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407),
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119),
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184),
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137),
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184),
org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:163),
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184),
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97),
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184),
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218),
jdk.proxy4/jdk.proxy4.$Proxy125.save(Unknown Source),
com.cogiteo.canvas.entitiescontroller.association.service.AssociationService.updateAssociation(AssociationService.java:33),
com.cogiteo.canvas.entitiescontroller.association.controller.AssociationController.setAssociationToAccepted(AssociationController.java:84),
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method),
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77),
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43),
java.base/java.lang.reflect.Method.invoke(Method.java:568),
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207),
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152),
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117),
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884),
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797),
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87),
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080),
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973),
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003),
org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:917),
jakarta.servlet.http.HttpServlet.service(HttpServlet.java:734),
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880),
jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814),
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223),
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158),
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53),
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185),
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158),
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100),
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116),
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185),
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158),
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93),
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116),
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185),
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158),
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201),
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116),
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185),
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158),
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197),
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97),
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542),
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119),
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92),
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78),
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357),
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400),
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65),
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861),
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739),
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52),
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191),
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659),
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61),
java.base/java.lang.Thread.run(Thread.java:833)

排查与解决方案

核心原因

  1. @IdClass使用错误:实体类中@IdClass(Association.class)不符合JPA规范,@IdClass需要指定一个独立的、实现Serializable的复合主键类,而非实体类本身。这种错误配置导致Hibernate无法正确跟踪主键的一致性,在更新时误判主键实例发生变更。
  2. 字段名不匹配:Controller中调用updatedAssociation.getCanvasEquipe(),但实体类中对应的主键字段是canvas_collectif,字段名不一致可能导致实体加载时主键关联异常。

解决步骤

  1. 创建独立复合主键类
import java.io.Serializable;
import java.util.Objects;

public class AssociationId implements Serializable {
    private Canvas canvas_collectif;
    private Canvas canvas;

    public AssociationId() {}

    public AssociationId(Canvas canvas_collectif, Canvas canvas) {
        this.canvas_collectif = canvas_collectif;
        this.canvas = canvas;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AssociationId that = (AssociationId) o;
        return Objects.equals(canvas_collectif, that.canvas_collectif) &&
                Objects.equals(canvas, that.canvas);
    }

    @Override
    public int hashCode() {
        return Objects.hash(canvas_collectif, canvas);
    }

    // 生成getter和setter
}
  1. 修正实体类的@IdClass配置
@Entity
@Table(name = "Association")
@IdClass(AssociationId.class) // 替换为独立主键类
public class Association {

    @Id
    @ManyToOne
    @JoinColumn(name = "canvas_collectif", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonBackReference
    private Canvas canvas_collectif;

    @Id
    @ManyToOne
    @JoinColumn(name = "canvas", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonBackReference
    private Canvas canvas;

    @Column(name = "statut", nullable = false)
    private boolean statut;
    
    // 修正getter方法名,确保Controller中调用的是getCanvas_collectif()
    public Canvas getCanvas_collectif() {
        return canvas_collectif;
    }

    // 其他getter和setter
}
  1. 修正Controller中的字段调用
    将Controller中的updatedAssociation.getCanvasEquipe().getGUIDCanvas().toString()改为updatedAssociation.getCanvas_collectif().getGUIDCanvas().toString(),确保字段名匹配。

  2. 确保更新方法正确
    AssociationService中的updateAssociation方法直接使用JPA的save即可,因为从数据库加载的实体处于托管状态,修改后save会自动执行更新:

public Association updateAssociation(Association association) {
    return associationRepository.save(association);
}

内容的提问来源于stack exchange,提问作者Ikarus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:10:54