(Please feel free to edit the title after reading this question)
I have quite simple @ManyToOne
bidirectional mapping between entities Parent
and Child
.
The list of children Collection<Child> children
in Parent
is never initialized so it should be null
.
When using EntityManager.find(...)
for previously persisted Parent
and then getting the list from that Parent
gives ArrayList even there are no children yet with this Parent
and it is fine.
However if persisting or merging a new Parent
in the same transaction collection of children will be null
even if the persisted/merged Parent
is fetched again with EntityManager.find(...)
.
So i wonder this different behavior and if it is happening only in my environment.
I assume it has something to do with the caching of entities: entity is found from cache and it is returned instead of fetching it from db AND the initialization of empty collections will happen only when fetched from db, maybe depending on the JPA implementation.
Is my assumption even near the truth and if not what is the reason ?
Entities and test cases below. My test environment listed in tags.
// using lombok
@Slf4j
@RunWith(Arquillian.class)
public class NoPersistTest {
@PersistenceContext
private EntityManager em;
@Deployment
public static final WebArchive deploy() {
WebArchive wa = ShrinkWrap.create(WebArchive.class, "test.war")
.addAsWebInfResource("test-persistence.xml", "persistence.xml").addClasses(Parent.class, Child.class);
return wa;
}
@Test
@Transactional
public void testWithPreviouslyPersistedParent() {
Parent parent = em.find(Parent.class, 1); // has no children in db
// before
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
log.info("type of Collection<Child> is {}", parent.getChildren().getClass().getName());
// above logs "type of Collection<Child> is
// org.apache.openjpa.util.java$util$ArrayList$proxy"
}
@Test(expected = NullPointerException.class)
@Transactional
public void testPersistingParentInSameTransaction() {
Parent parent = new Parent();
em.persist(parent);
Parent parent2 = em.find(Parent.class, parent.getId());
Child child = new Child();
child.setParent(parent2);
log.info("Collection<Child> is {}", parent2.getChildren());
// above logs Collection<Child> is null
parent2.getChildren().add(child);
}
@Test(expected = NullPointerException.class)
@Transactional
public void testMergingParentInSameTransaction() {
Parent parent = new Parent();
parent = em.merge(parent);
Parent parent2 = em.find(Parent.class, parent.getId());
Child child = new Child();
child.setParent(parent2);
log.info("Collection<Child> is {}", parent2.getChildren());
// logs Collection<Child> is null
parent2.getChildren().add(child);
}
}
@Entity @Getter @Setter
public class Parent {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL, orphanRemoval=true)
private Collection<Child> children;
private Date created = new Date(); // just to have something to persist
}
@Entity @Getter @Setter
public class Child {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private Date created = new Date(); // just to have something to persist
@ManyToOne(optional=false)
private Parent parent;
}