JPA How to update a foreign key whose primary key is compositeId

Viewed 159

I use SpringBoot with JPA to perform create and update requests using the following service method. As the create requests work like a charm, the update request will result in an IllegalArgumentException from within the framework if any of the two references to PaymentReason are changed in any way (That is if I try to set paymentReasonWriteOff to null or to set it to an actual instance of PaymentReason if it was null before ).

The service method throwing the error:

    public Long createOrUpdateRemindLevelConfig(RemindLevelConfig remindLevelConfig) {

        var paymReasonWriteOff = paymentReasonRepository.findById(
                remindLevelConfig.getPaymentReasonWriteOff().getPaymentReasonId());
        var paymReasonWriteOffFees = paymentReasonRepository.findById(
                remindLevelConfig.getPaymentReasonWriteOffFees().getPaymentReasonId());

        if (paymReasonWriteOff.isPresent()) {
            remindLevelConfig.setPaymentReasonWriteOff(paymReasonWriteOff.get());
        } else {
            remindLevelConfig.setPaymentReasonWriteOff(null);
        }
        if (paymReasonWriteOffFees.isPresent()) {
            remindLevelConfig.setPaymentReasonWriteOffFees(paymReasonWriteOffFees.get());
        } else {
            remindLevelConfig.setPaymentReasonWriteOffFees(null);
        }
        // will fail if any reference to PaymentReason was changed
        return remindLevelConfigRepository.save(remindLevelConfig).getId();
    }

The entity I want to update (simplified):

@Entity
@Table(name = "C_REMINDPARA")
@NoArgsConstructor
@Getter
@Setter
public class RemindLevelConfig {

    @Column(name = "ID")
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "P_PAYMREASON_ID_WRITE_OFF", referencedColumnName = "ID")
    @ManyToOne
    @NotFound(action = NotFoundAction.IGNORE)
    private PaymentReason paymentReasonWriteOff;

    @JoinColumn(name = "P_PAYMREASON_ID_WRITE_OFF_FEES", referencedColumnName = "ID")
    @ManyToOne
    @NotFound(action = NotFoundAction.IGNORE)
    private PaymentReason paymentReasonWriteOffFees;
}

The class whose reference I cannot change: Note that the primary Key is an Embedded Id but in RemindLevelConfig the natural Id is used to reference this entity.

@Entity
@Getter
@Setter
@Table(name = "p_paymreason")
public class PaymentReason implements Serializable {

    @EmbeddedId
    private PaymentReasonId paymentReasonId;

    @OneToMany(mappedBy = "paymentReasonWriteOff")
    private Collection<RemindLevelConfig> remindLevelConfigsWriteOff;
    @OneToMany(mappedBy = "paymentReasonWriteOffFees")
    private Collection<RemindLevelConfig> remindLevelConfigsWriteOffFees;

    @Column(name = "ID")
    @NaturalId
    private Long id;
}

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Embeddable
@EqualsAndHashCode
public class PaymentReasonId implements Serializable {

    @Column(name = "I_COMPANY")
    private Long companyId;

    @Column(name = "I_CUST_COMPANY")
    private Long customerCompanyId;

    @Column(name = "I_REASONCODE")
    private Integer reasonCode;
}

When I try to update a RemindLevelConfig changing either paymentReasonWriteOff or paymentReasonWriteOffFees using the mentioned service function I get the following exception:

java.lang.IllegalArgumentException: Can not set java.lang.Long field backend.models.composite_keys.PaymentReasonId.companyId to backend.models.PaymentReason 

    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167) ~[na:na] 
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171) ~[na:na] 
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58) ~[na:na] 
    at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36) ~[na:na] 
    at java.base/java.lang.reflect.Field.get(Field.java:416) ~[na:na] 
    at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:71) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue(AbstractComponentTuplizer.java:59) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.type.ComponentType.getPropertyValue(ComponentType.java:419) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.type.ComponentType.isDirty(ComponentType.java:262) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:335) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:345) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:316) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:4586) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:585) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:242) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:169) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:232) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:92) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1352) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:443) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3202) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2370) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final] 
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:534) ~[spring-orm-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at com.sun.proxy.$Proxy154.save(Unknown Source) ~[na:na] 
    at de.osp.paymentbackend.services.RemindLevelConfigService.createOrUpdateRemindLevelConfig(RemindLevelConfigService.java:83) ~[classes/:na] 
    at de.osp.paymentbackend.controllers.RemindLevelConfigController.updateRemindLevelConfig(RemindLevelConfigController.java:55) ~[classes/:na] 
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] 
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] 
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na] 
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE] 

    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
 at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
at
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 
    at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:920) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE] 

I have no idea why it even would think of trying to modify the primary key in this case since I did not intend to create a new instance of PaymentReason. I just want to change the reference to it. I guess the reason of the exception is due to some mapping issue which I could not figure out so far. Also the service function does work when I create a new RemindLevelConfig.

Any help and suggestions would be appreciated!

0 Answers
Related