Home NullPointerException when deleting subentity with @ManyToOne to other Entities
Reply: 2

NullPointerException when deleting subentity with @ManyToOne to other Entities

gerardribas
1#
gerardribas Published in 2018-02-13 10:45:35Z

I need to delete a SubEntity from a bag in a MainEntity.

This SubEntity has another relationships to other Entities.

The model looks like:

MainEntity:

@Data
@EqualsAndHashCode(of = "idNum")
@Entity
@Table(name = "MAIN_TABLE")
public class MainEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idNum")
    @SequenceGenerator(name = "idNum", sequenceName = "id_num", allocationSize = 1)
    @Column(name="ID_NUM")
    private Long idNum;

    @OneToMany(mappedBy = "mainEntity", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<SubEntity> subEntities = new ArrayList<>();

    public void addSubEntity(SubEntity subEntity) {
        subEntity.setMainEntity(this);
        subEntities.add(subEntity);
    }

    public void removeSubEntity(SubEntity subEntity) {
        subEntity.setMainEntity(null);
        subEntities.remove(subEntity);
    }


}

SubEntity:

@Data
@EqualsAndHashCode(of = "subIdNum")
@ToString(exclude = "mainEntity")
@Entity
@Table(name = "SUB_TABLE")
public class SubEntity implements Serializable {

    @Id
    @Column(name = "SUB_ID")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subIdNumSequence")
    @SequenceGenerator(name = "subIdNumSequence", sequenceName = "SEQ_SUB_ID", allocationSize = 1)
    private Long subIdNum;

    @Column(name = "IND_NUM")
    private String indNum;

    @Column(name = "FAMILY_IDENTIFIER")
    private String familyIdentifier;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ID_NUM")
    private MainEntity mainEntity;

    @OneToMany(mappedBy = "subEntity", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<SubSubEntity> subSubEntities = new ArrayList<>();

    @OneToMany(mappedBy = "subEntity", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<AnotherSubSubEntity> anotherSubSubEntities = new ArrayList<>();

}

SubSubEntity:

@Entity
@Data
@ToString(exclude = "subEntity")
@EqualsAndHashCode(of = {"idNum", "code", "indNum" })
@Table(name = "SUB_SUB_TABLE")
@IdClass(SubSubEntity.SubSubEntityId.class)
public class SubSubEntity implements Serializable {

    @Getter
    @Id
    @Column(name = "ID_NUM", insertable = false, updatable = false)
    private Long idNum;

    @Id
    @Column(name = "CODE")
    private String code;

    @Getter
    @Id
    @Column(name = "IND_NUM", insertable = false, updatable = false)
    private String indNum;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
            @JoinColumn(name = "ID_NUM", referencedColumnName = "ID_NUM"),
            @JoinColumn(name = "IND_NUM", referencedColumnName = "IND_NUM")
    })
    @Id
    private SubEntity subEntity;

    public void setSubEntity(SubEntity subEntity) {
        idNum = Optional.ofNullable(subEntity).map(SubEntity::getMainEntity).map(MainEntity::getIdNum).orElse(null);
        code = Optional.ofNullable(subEntity).map(SubEntity::getIndNum).orElse(null);
        this.subEntity = subEntity;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class SubSubEntityId implements Serializable {

        private Long idNum;

        private String code;

        private String indNum;

    }


}

AnotherSubSubEntity:

@Entity
@Data
@ToString(exclude = "subEntity")
@EqualsAndHashCode(of = {"idNum", "person", "sourceCode"})
@Table(name = "ANOTHER_SUB_SUB_TABLE")
@IdClass(AnotherSubSubEntity.AnotherSubSubEntityId.class)
public class AnotherSubSubEntity implements Serializable {

    @Getter
    @Id
    @Column(name = "ID_NUM", insertable = false, updatable = false)
    private Long idNum;

    @Getter
    @Id
    @Column(name = "PERSON", insertable = false, updatable = false)
    private String person;

    @Id
    @Column(name = "SOURCE_CODE")
    private String sourceCode;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
            @JoinColumn(name = "ID_NUM", referencedColumnName = "ID_NUM", insertable = false, updatable = false),
            @JoinColumn(name = "PERSON", referencedColumnName = "FAMILY_IDENTIFIER", insertable = false, updatable = false)
    })
    @Id
    private SubEntity subEntity;

    public void setSubEntity(SubEntity subEntity) {
        idNum = Optional.ofNullable(subEntity).map(SubEntity::getMainEntity).map(MainEntity::getIdNum).orElse(null);
        person = Optional.ofNullable(subEntity).map(SubEntity::getFamilyIdentifier).orElse(null);
        this.subEntity = subEntity;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class AnotherSubSubEntityId implements Serializable {

        private Long idNum;

        private String person;

        private String sourceCode;

    }

}

And the schema, and data for this test is (I'm running an embedded hsql database):

SET DATABASE SQL SYNTAX ORA TRUE;

CREATE TABLE MAIN_TABLE(
  "ID_NUM" NUMBER,
  CONSTRAINT "PK_MAIN_TABLE" PRIMARY KEY ("ID_NUM")
);

CREATE SEQUENCE SEQ_SUB_ID AS INTEGER START WITH 12345;
CREATE TABLE SUB_TABLE(
  "ID_NUM" NUMBER NOT NULL,
  "SUB_ID" NUMBER(15,0) NOT NULL ,
    "FAMILY_IDENTIFIER" VARCHAR2(15 BYTE),
    "IND_NUM" VARCHAR2(10 BYTE),
    CONSTRAINT "PK_SUB_TABLE" PRIMARY KEY ("SUB_ID"),
    CONSTRAINT "FK_SUB_TABLE_1" FOREIGN KEY ("ID_NUM") REFERENCES "MAIN_TABLE" ("ID_NUM")
);

CREATE TABLE SUB_SUB_TABLE(
  "ID_NUM" NUMBER,
  "CODE" VARCHAR2(5 BYTE),
  "IND_NUM" VARCHAR2(10 BYTE),
  CONSTRAINT "PK_SUB_SUB_TABLE" PRIMARY KEY ("ID_NUM", "CODE", "IND_NUM")
);

CREATE TABLE "ANOTHER_SUB_SUB_TABLE"(
  "ID_NUM" NUMBER,
    "PERSON" VARCHAR2(1 BYTE),
    "SOURCE_CODE" VARCHAR2(5 BYTE),
    CONSTRAINT "PK_ANOTHER_SUB_SUB_TABLE" PRIMARY KEY ("ID_NUM", "PERSON", "SOURCE_CODE"),
    CONSTRAINT "FK_ANOTHER_SUB_SUB_TABLE_1" FOREIGN KEY ("ID_NUM") REFERENCES "MAIN_TABLE" ("ID_NUM")
);


INSERT INTO MAIN_TABLE(ID_NUM) VALUES (99427);
INSERT INTO SUB_TABLE(ID_NUM, SUB_ID, FAMILY_IDENTIFIER, IND_NUM) VALUES (99427, 1, 'A', '123A');
INSERT INTO SUB_TABLE(ID_NUM, SUB_ID, FAMILY_IDENTIFIER, IND_NUM) VALUES (99427, 2, 'S', '321A');
INSERT INTO SUB_SUB_TABLE(ID_NUM, CODE, IND_NUM) VALUES (99427, 'CODE1', '123A');

If I run a test case:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MainRepoTest {

    @Autowired
    private MainRepo mainRepo;


    @Test
    public void testFind() {
        mainRepo.findAll();
    }

    @Test
    public void deleteSubEntity() {
        MainEntity mainEntity = mainRepo.findOne(99427L);
        Optional<SubEntity> subEntityToRemove =  mainEntity.getSubEntities().stream().filter(subEntity -> "123A".equals(subEntity.getIndNum())).findFirst();
        if(subEntityToRemove.isPresent()) {
//            System.out.println(subEntityToRemove.get());
            mainEntity.removeSubEntity(subEntityToRemove.get());
            mainRepo.saveAndFlush(mainEntity);
        }
    }

}

This test throws the following exception:

java.lang.NullPointerException
    at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:278)
    at org.hibernate.type.CollectionType.getElementsIterator(CollectionType.java:229)
    at org.hibernate.type.CollectionType.getElementsIterator(CollectionType.java:219)
    at org.hibernate.engine.spi.CascadingActions.getAllElementsIterator(CascadingActions.java:477)
    at org.hibernate.engine.spi.CascadingActions.access$100(CascadingActions.java:33)
    at org.hibernate.engine.spi.CascadingActions$1.getCascadableChildrenIterator(CascadingActions.java:66)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:429)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:363)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111)
    at org.hibernate.event.internal.DefaultDeleteEventListener.cascadeBeforeDelete(DefaultDeleteEventListener.java:336)
    at org.hibernate.event.internal.DefaultDeleteEventListener.deleteEntity(DefaultDeleteEventListener.java:258)
    at org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:143)
    at org.hibernate.internal.SessionImpl.fireDelete(SessionImpl.java:930)
    at org.hibernate.internal.SessionImpl.delete(SessionImpl.java:874)
    at org.hibernate.engine.internal.Cascade.deleteOrphans(Cascade.java:493)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:466)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:363)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111)
    at org.hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:150)
    at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:141)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:74)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1300)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
    at com.sun.proxy.$Proxy79.flush(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:555)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:523)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:513)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:498)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:475)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy81.saveAndFlush(Unknown Source)
    at com.example.repo.MainRepoTest.deleteSubEntity(MainRepoTest.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    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.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:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    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:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

But if I uncomment the line that's outputting the toString from the SubEntity works well. I presume is because the lazy collections are not initialised when I'm deleting the SubEntity. Is there an elegant way to skip this error in Spring Data Jpa / Hibernate?

Pavlo
2#
Pavlo Reply to 2018-02-13 11:09:28Z

Right, this about lazy initialization. Collection of subentities are populated with objects, that are actually contain only information to be able to load them later (like id). Once hibernate session closed - object is detached, and your call toString fails with exception. This exactly what happen in your code, so to resolve you have two options:

  • Call toString before Hibernate session closed, then Hibernate will populate object (call database). (Preffered)
  • Use eager initializatin instead of lazy. This quite simple to implement, but could lead to high load.

I think @Transactional on your test method should prevent Hibernate session close, but seems it not happen. It could be your repo mainRepo.findOne method wrapped with @Transactional.

Vlad Mihalcea
3#
Vlad Mihalcea Reply to 2018-02-13 12:36:56Z

If you can replicate it with this test case template against the latest version of Hibernate (e.g. 5.2.13). then you should open a Jira issue.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.337482 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO