AForsberg
AForsberg

Reputation: 1194

Post-upgrade, @MapsId is throwing an error when saving an existing entity, but otherwise works fine

I'm working on upgrading a Spring Boot 1.5.21 project (Java 8u221) to Spring Boot 2.1.9 (Java 11.0.2-open). In both cases we are using a gradle build with the spring boot starters and dependency resolver so the versions of the underlying Spring, JPA, and Hibernate libraries are spring-managed.

The project has an optional one-to-one mapping in which the child entity gets its ID from the parent's generated ID. In the Spring Boot 1 version of the project, the relationship was configured like this:

@Entity
@Table(name = "PARENT_OBJECT", schema = "MYSCHEMA")
public class ParentObject implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PARENT_OBJECT_ID_SEQ")
    @SequenceGenerator(name = "PARENT_OBJECT_ID_SEQ", sequenceName = "MYSCHEMA.PARENT_OBJECT_ID_SEQ")
    protected Long id;

    @OneToOne(optional = true, mappedBy = "parentObject", cascade = CascadeType.ALL, orphanRemoval = true)
    @Valid
    protected ChildObject childObject;

    // Other fields and methods
}

@Entity
@Table(name = "CHILD_OBJECT", schema = "MYSCHEMA")
public class ChildObject implements Serializable {
    @Id
    @Column(name = "parent_object_id", unique = true, nullable = false, insertable = true, updatable = false)
    private Long parentObjectId;

    @OneToOne
    @MapsId
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ParentObject parentObject;

    // Other fields and methods
}

When I updated to Spring Boot 2, I had to update a lot of JPA settings, and one thing in particular that was complained about was that the generated queries were looking for 'PARENTOBJECT_ID' instead of 'PARENT_OBJECT_ID', so I looked into it a bit, found an article explaining how to fix column names for one-to-one mappings with @JoinColumn, and updated the child object's annotations for the parentObject field to be the following:

@OneToOne
@MapsId
@JoinColumn(name = "parent_object_id")
@JsonIgnore
private ParentObject parentObject;

I removed the @PrimaryKeyJoinColumn entirely as the documentation seems to suggest that you should use @MapsId if you want Hibernate to handle the assignment of IDs, and if you're going to manage them yourself, THAT'S when you would use @PrimaryKeyJoinColumn.

This configuration worked well for all of my tests, except for 2: the UPDATE integration tests for the Controller (The POST tests worked just fine, when the ParentObject was created for the first time with their associated ChildObject). The error I get for just these two tests is:

org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]; nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]

The strange thing is that all the repository integration tests for these entities and their associations pass, as well as all the other controller integration tests, including the one where a new ParentObject is POSTed with its new ChildObject. I looked all over for a possible solution, but every article I read seemed to suggest that the configuration I used should work. I also tried out alternative configurations that suggested things like using @PrimaryKeyJoinColumn and setting the ID field myself, using ID Generators on the ChildObjects ID (even though the point of @MapsId is to tell the system to use the ID of the ParentObject), and usually what I'd end up with is more failing tests with the following error:

org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save(): org.mycompany.myproject.mymodule.mysubmodule.ChildObject; nested exception is org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): org.mycompany.myproject.mymodule.mysubmodule.ChildObject

Though sometimes I'd get the original error of not being able to 'assign id from null'. At this point, I'm at a loss as to how this is configured incorrectly or what other factor is screwing with things. I'm willing to try any and all suggestions at this point.

For completeness' sake, the following is a bunch of code snippets from the app. Relevant line numbers from the stack trace are noted with a comment. I omitted the controller code as there's nothing remarkable about it; both the POST and PUT methods call the same service method; they're just at different endpoints and the PUT first checks that the object exists in the DB before calling the service save method. Here is the service method that calls the JPA repo for the ParentObject:

@Service
@Transactional(readOnly = true)
public class ParentObjectServiceImpl implements ParentObjectService {
    // other fields and other methods

    @Autowired
    private ParentObjectRepository parentObjectRepository;

    @Transactional(readOnly = false)
    @Override
    public ParentObject saveParentObject(final ParentObject parentObject) {
        parentObject.prepForPersistence();
        return parentObjectRepository.save(parentObject); //Line 93
    }
}

And here is my JPA repository:

public interface ParentObjectRepository extends CrudRepository<ParentObject, Long> {
    // custom methods, no override for save though
}

And here is the prepForPersistence() method of ParentObject:

public void prepForPersistence() {
    if(childObject != null) {
        childObject.setParentObject(this);
        // In some iterations of the code in trying to solve this, I also had the following line
        //childObject.setParentObjectId(this.id);
    }
}

Here are two of the tests (one that passes and one that fails):

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyApplication.class)
@WebAppConfiguration
// This profile disabled csrf for testing, and sets some env variables
@ActiveProfiles("integration-test")
@Transactional
public class ParentObjectControllerTest {
    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply(springSecurity())
            .build();
    } 

    // This test PASSES
    @Test
    @WithMockUser(roles = {"MY_APP_ADMIN"}, username = TEST_USER)
    @Sql(scripts = "/db-scripts/bootstrap.sql")
    public void testPostParentObjectWithChildObject() {
        final ChildObject childObject = new ChildObject();
        // set some properties on childObject that don't relate to ParentObject

        final ParentObject parentObject = new ParentObject();
        // set some properties on parentObject that don't relate to ChildObject
        parentObject.setChildObject(childObject);

        final ParentObject result = given().mockMvc(mockMvc).contentType(ContentType.JSON)
            .and().body(item).log().all()
            .when().post("/parent-objects")
            .then().log().all().statusCode(201).contentType(ContentType.JSON)
            .and().body(matchesJsonSchemaInClasspath("json-schemas/parent-object.json"))
            .and().body("username", equalTo(TEST_USER))
            .extract().as(ParentObject.class);

        assertThat(result.getId(), is(notNullValue()));
        assertThat(result.getChildObject().getParentObjectId(), is(result.getId()));
    }

    // This test FAILS with the 'attempted to assign id from null one-to-one property' error
    @Test
    @WithMockUser(roles = {"MY_APP_ADMIN"}, username = TEST_USER)
    // Inserts a ParentObject record with ID -1
    @Sql(scripts = "/db-scripts/bootstrap.sql")
    public void testPutParentObjectWithChildObject() {
        final ChildObject childObject = new ChildObject();
        // set some properties on childObject that don't relate to ParentObject

        final Long EXISTING_ID = -1L;
        final ParentObject parentObject = new ParentObject();
        parentObject.setId(EXISTING_ID);
        parentObject.setChildObject(childObject);

        final ParentObject result = given().mockMvc(mockMvc).contentType(ContentType.JSON)
            .and().body(item).log().all()
            .when().put("/parent-objects/{parentObjectId}", parentObject.getId()) //line 260
            .then().log().all().statusCode(200)
            .extract().as(ParentObject.class);

        assertThat(result.getId(), is(EXISTING_ID));
        assertThat(result.getChildObject().getParentObjectId(), is(EXISTING_ID));
}

Here's the full stack trace:

Responding with HTTPStatus=INTERNAL_SERVER_ERROR due to the following error: org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]; nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:352) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:254) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:144) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$ExposeRepositoryInvocationInterceptor.invoke(CrudMethodMetadataPostProcessor.java:364) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) at com.sun.proxy.$Proxy249.save(Unknown Source) at org.mycompany.myproject.mymodule.ParentObjectServiceImpl.saveParentObject(ParentObjectServiceImpl.java:93) at org.mycompany.myproject.mymodule.ParentObjectServiceImpl$$FastClassBySpringCGLIB$$86531367.invoke() at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) at org.mycompany.myproject.mymodule.ParentObjectServiceImpl$$EnhancerBySpringCGLIB$$d3a0c3ee.saveChecklistItem() at org.mycompany.myproject.mymodule.ParentObjectController.putParentObject(ParentObjectController.java:78) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:798) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:920) at javax.servlet.http.HttpServlet.service(HttpServlet.java:668) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72) at javax.servlet.http.HttpServlet.service(HttpServlet.java:750) at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167) at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:97) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183) at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.performRequest(MockMvcRequestSenderImpl.java:218) at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.sendRequest(MockMvcRequestSenderImpl.java:447) at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.put(MockMvcRequestSenderImpl.java:504) at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.put(MockMvcRequestSenderImpl.java:100) at org.mycompany.myproject.mymodule.ParentObjectControllerTest.testPutParentObjectWithChildObject(ParentObjectControllerTest.java:260) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38) at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) at com.sun.proxy.$Proxy5.processTestClass(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:118) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:412) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) at java.base/java.lang.Thread.run(Thread.java:834) Caused by: org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject] at org.hibernate.id.ForeignGenerator.generate(ForeignGenerator.java:90) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:119) at org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:287) at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:259) at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:191) at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:927) at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:897) at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149) at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:532) at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:361) at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:188) at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:72) at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:905) at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:310) at com.sun.proxy.$Proxy214.merge(Unknown Source) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:538) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ... 140 more

Thanks in advance for suggestions and help!

Upvotes: 3

Views: 2380

Answers (1)

AForsberg
AForsberg

Reputation: 1194

I'm thinking this is a bug, but I'm still interested in workarounds if anyone has them. I had originally found a bug listed on hibernate that seemed to match the problem, but it was registered as only affecting the 5.2 series and marked as 'fixed', and since our project was using the 5.3 series, I moved on. Digging into submitted bugs from the spring side of things though, a breadcrumb trail led me to this 5.3 series bug: https://hibernate.atlassian.net/browse/HHH-13413 which linked back to the 5.2 series bug I originally found, which, contrary to the 5.3 write-up, seemed to suggest that this has always been a problem since the 5.2 series and should have never worked in the 5.3 series, because, not only was the 5.3 series bug marked as a duplicate of the 5.2 series bug, when I looked closer at the 5.2 bug, it stated that it's fix version wasn't until 5.4: https://hibernate.atlassian.net/browse/HHH-12436

So I'll be trying downgrading to the last 5.2 series that worked, and upgrading to the 5.4 series... I'm just not sure how either of those will play with the rest of the spring data jpa functionality and may be a big issue in and of itself.

Update: aside from running into an issue because we're using java 11 (https://hibernate.atlassian.net/browse/HHH-12924 -> I had to use javassist:3.23.0-GA to get hibernate-core:5.2.13.Final to work), the downgrade was successful in getting the one-to-one mapping working again. All other tests also passed. However I haven't done extensive testing with this solution as I'd prefer an upgrade-based solution.

Update 2: Was able to upgrade to upgrade to Spring Boot 2.2.0 (which uses Hibernate 5.4, which is where this particular bug for one-to-one mappings was fixed)! It required some changes to my sequences, but wasn't too painful of an upgrade; didn't even need to use the downgrade flag! If by chance your mapping issue isn't fixed, there is a 'use the 5.2 behavior' flag since they're still working out the kinks of various mapping scenarios. More details about that on their 5.4 migration notes here: https://github.com/hibernate/hibernate-orm/blob/61cddad76d5bba951805fa7ed90cc149d404841c/migration-guide.adoc

Upvotes: 2

Related Questions