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)
排查与解决方案
核心原因
@IdClass使用错误:实体类中@IdClass(Association.class)不符合JPA规范,@IdClass需要指定一个独立的、实现Serializable的复合主键类,而非实体类本身。这种错误配置导致Hibernate无法正确跟踪主键的一致性,在更新时误判主键实例发生变更。- 字段名不匹配:Controller中调用
updatedAssociation.getCanvasEquipe(),但实体类中对应的主键字段是canvas_collectif,字段名不一致可能导致实体加载时主键关联异常。
解决步骤
- 创建独立复合主键类
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 }
- 修正实体类的
@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 }
修正Controller中的字段调用
将Controller中的updatedAssociation.getCanvasEquipe().getGUIDCanvas().toString()改为updatedAssociation.getCanvas_collectif().getGUIDCanvas().toString(),确保字段名匹配。确保更新方法正确
AssociationService中的updateAssociation方法直接使用JPA的save即可,因为从数据库加载的实体处于托管状态,修改后save会自动执行更新:
public Association updateAssociation(Association association) { return associationRepository.save(association); }
内容的提问来源于stack exchange,提问作者Ikarus
相关产品推荐
相关产品推荐

