Why Entitys uninitialized collection is initialized automatically only for Entities persisted before current transaction? - java-8

(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;
}

If you create the Parent the collection is not initialized because you don't do it. And also when persisting the Parent JPA will leave the collection as it is.
But when you read the Parent with Hibernate the collection will contain a proxy because toMany relationships are fetched LAZY and this proxy is used to fetch the children on demand.
My recommendation is to always initialize collection to avoid NullPointerExceptions. That's good programming style.

The answer below is correct, I'd just like to add some more information as I was asked to in a comment elsewhere.
JPA uses caching to avoid database hits where possible, and where a database hit is still required, caching avoids the cost of rebuilding objects and allows maintaining Identity - ensuring you get back the same A instance when traversing A->B->A circular references.
When you persist an entity, you are placing it in the EntityManager cache as a managed entity - calling find on that EntityManager will return you the same exact instance you just passed in.
A initialA = new A();
A managedA = em.persist(initialA);
managedA==initialA
The persist call itself will not change anything within your entity (except possibly the ID if a sequence that allows preallocation to be used), so any null references will still be null.
Eventually the transaction commits and depending on your provider, entities can be cached in a second level cache. I'll assume you aren't using it for the sake of brevity; unless you force the EM to refresh this instance (flush first if its a new one!) or read it in a separate EntityManager, you will always get that same instance back with any null references.
If you refresh it or otherwise cause it to be reloaded, your JPA provider is required to set everything in the object as it is in the database, according to your mappings. Since null isn't a persistable state for a collection mapping, that means it will either eagerly fetch your references, or place proxies in there for lazy relationships, causing you to find an empty collection.

Related

How to get actual child collection when updating parent

How can I get actual child collection, when adding new one in separated transactional method, while updating parent.
I have spring boot app with hibernate/jpa and one-to-many unidirectional model:
parent:
#Entity
public class Deal {
private UUID id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Rate> rates;
....
}
child:
#Entity
public class Rate {
private UUID id;
....
}
And I have non transactional method for do some business logic by rest call:
public Deal applyDeal(UUID dealId) {
dealService.apply(dealId);
return dealService.getById(dealId);
}
Method apply in DealService has several methods in separate transactions (all methods doLogic() annotated with #Transactional(Propagation.REQUIRES_NEW):
public void apply(UUI dealId) {
someService1.do1Logic(...);
someService2.do2Logic(...);
someService3.do3Logic(...);
}
In do2Logic() I have some logic that adding new Rate entity to my parent entity with dealId and direct call of save method for Deal object.
#Transactional(Propagation.REQUIRES_NEW)
publid void do2Logic(...) {
...
var deal = dealService.getById(...);
deal.getRates().add(new Rate());
dealService.save(deal);
}
But when I get response from root method applyDeal the new child entity is absent.
If after that I will try to get this parent in separate rest call (getDeal) I get actual parent entity with new child in collection.
How to get actual child collection in parent response of applyDeal method?
I tried to make all logic in one #Transactional but it doesn't works.
I also don't understand why when I am try to get deal instance to return in applyDeal I get old data.
Thank you.
I guess you are running MySQL or MariaDB? These two database by default use the repeatable read transaction isolation level, which can cause this behavior. Try configuring the read committed isolation level instead, and/or remove the REQUIRES_NEW propagation if possible, since that will suspend an already running transaction to start a second one.

Transaction getting rolled back on persisting the entity from Many to one side

I have this association in the DB -
I want the data to be persisted in the tables like this -
The corresponding JPA entities have been modeled this way (omitted getters/setters for simplicity) -
STUDENT Entity -
#Entity
#Table(name = "student")
public class Student {
#Id
#SequenceGenerator(name = "student_pk_generator", sequenceName =
"student_pk_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"student_pk_generator")
#Column(name = "student_id", nullable = false)
private Long studentId;
#Column(name = "name", nullable = false)
private String studentName;
#OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
private Set<StudentSubscription> studentSubscription;
}
STUDENT_SUBSCRIPTION Entity -
#Entity
#Table(name = "student_subscription")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class StudentSubscription {
#Id
private Long studentId;
#ManyToOne(optional = false)
#JoinColumn(name = "student_id", referencedColumnName = "student_id")
#MapsId
private Student student;
#Column(name = "valid_from")
private Date validFrom;
#Column(name = "valid_to")
private Date validTo;
}
LIBRARY_SUBSCRIPTION Entity -
#Entity
#Table(name = "library_subscription",
uniqueConstraints = {#UniqueConstraint(columnNames = {"library_code"})})
#PrimaryKeyJoinColumn(name = "student_id")
public class LibrarySubscription extends StudentSubscription {
#Column(name = "library_code", nullable = false)
private String libraryCode;
#PrePersist
private void generateLibraryCode() {
this.libraryCode = // some logic to generate unique libraryCode
}
}
COURSE_SUBSCRIPTION Entity -
#Entity
#Table(name = "course_subscription",
uniqueConstraints = {#UniqueConstraint(columnNames = {"course_code"})})
#PrimaryKeyJoinColumn(name = "student_id")
public class CourseSubscription extends StudentSubscription {
#Column(name = "course_code", nullable = false)
private String courseCode;
#PrePersist
private void generateCourseCode() {
this.courseCode = // some logic to generate unique courseCode
}
}
Now, there is a Student entity already persisted with the id let's say - 100.
Now I want to persist this student's library subscription. For this I have created a simple test using Spring DATA JPA repositories -
#Test
public void testLibrarySubscriptionPersist() {
Student student = studentRepository.findById(100L).get();
StudentSubscription librarySubscription = new LibrarySubscription();
librarySubscription.setValidFrom(//some date);
librarySubscription.setValidTo(//some date);
librarySubscription.setStudent(student);
studentSubscriptionRepository.save(librarySubscription);
}
On running this test I am getting the exception -
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.springboot.data.jpa.entity.Student; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.springboot.data.jpa.entity.Student
To fix this I attach a #Transactional to the test. This fixed the above exception for detached entity, but the entity StudentSubscription and LibrarySubscription are not getting persisted to the DB. In fact the transaction is getting rolled back.
Getting this exception in the logs -
INFO 3515 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext#35390ee3 testClass = SpringDataJpaApplicationTests, testInstance = com.springboot.data.jpa.SpringDataJpaApplicationTests#48a12036, testMethod = testLibrarySubscriptionPersist#SpringDataJpaApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration#5e01a982 testClass = SpringDataJpaApplicationTests, locations = '{}', classes = '{class com.springboot.data.jpa.SpringDataJpaApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#18ece7f4, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#264f218, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer#2462cb01, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer#928763c, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#7c3fdb62, org.springframework.boot.test.context.SpringBootTestArgs#1, org.springframework.boot.test.context.SpringBootTestWebEnvironment#1ad282e0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
Now I have couple of questions -
Why am I getting detached entity exception. When we fetch an entity from the DB, Spring Data JPA must be using entityManager to fetch the entity. The fetched entity gets automatically attached to the persistence context right ?
On attaching #Transactional on the test, why the transaction is getting rolledback, and no entity is getting persisted. I was expecting the two entities - StudentSubscription and LibrarySubscription should've been persisted using the joined table inheritance approach.
I tried many things but no luck. Seeking help from, JPA and Spring DATA experts :-)
Thanks in advance.
Let me add a few details that outline a couple of design problems with your code that significantly complicate the picture. In general, when working with Spring Data, you cannot simply look at your tables, create cookie-cutter entities and repositories for those and expect things to simply work. You need to at least spend a bit of time to understand the Domain-Driven Design building blocks entity, aggregate and repository.
Repositories manage aggregates
In your case, Student treats StudentSubscriptions like an entity (full object reference, cascading persistence operations) but at the same time a repository to persist the …Subscriptions exists. This fundamentally breaks the responsibility of keeping consistency of the Student aggregate, as you can simply remove a …Subscription from the store via the repository without the aggregate having a chance to intervene. Assuming the …Subscriptions are aggregates themselves, and you'd like to keep the dependency in that direction, those must only be referred to via identifiers, not via full object representations.
The arrangement also adds cognitive load, as there are now two ways to add a subscription:
Create a …Subscription instance, assign the Student, persist the subscription via the repository.
Load a Student, create a …Subscription, add that to the student, persist the Student via it's repository.
While that's already a smell, the bidirectional relationship between the …Subscription and Student imposes the need to manually manage those in code. Also, the relationships establish a dependency cycle between the concepts, which makes the entire arrangement hard to change. You already see that you have accumulated a lot of (mapping) complexity for a rather simple example.
What would better alternatives look like?
Option 1 (less likely): Students and …Subscriptions are "one"
If you'd like to keep the concepts close together and there's no need to query the subscriptions on their own, you could just avoid those being aggregates and remove the repository for them. That would allow you to remove the back-reference from …Subscription to Student and leave you with only one way of adding subscriptions: load the Student, add a …Subscription instance, save the Student, done. This also gives the Student aggregate its core responsibility back: enforcing invariants on its state (the set of …Subscription having to follow some rules, e.g. at least one selected etc.)
Option 2 (more likely): Students and …Subscriptions are separate aggregates (potentially from separate logical modules)
In this case, I'd remove the …Subscriptions from the Student entirely. If you need to find a Students …Subscriptions, you can add a query to the …SubscriptionRepository (e.g. List<…Subscription> findByStudentId(…)). As a side effect of this you remove the cycle and Student does not (have to) know anything about …Subscriptions anymore, which simplifies the mapping. No wrestling with eager/lazy loading etc. In case any cross-aggregate rules apply, those would be applied in an application service fronting the SubscriptionRepository.
Heuristics summarized
Clear distinction between what's an aggregate and what not (the former get a corresponding repository, the later don't)
Only refer to aggregates via their identifiers.
Avoid bidirectional relationships. Usually, one side of the relationship can be replaced with a query method on a repository.
Try to model dependencies from higher-level concepts to lower level ones (Students with Subscriptionss probably make sense, a …Subscription without a Student most likely doesn't. Thus, the latter is the better relationship to model and solely work with.)
The transaction is getting rolled back because the test is doing DB updates in the test method.
#Transactional does auto rollback if the transaction includes any update DB. Also here is the compulsion to use transaction because EntityManager gets closed as soon as the Student entity gets retrieved, so to keep that open the test has to be within the transactional context.
Probably if I had used a testDB for my testcases then probably spring wouldn't haveve been rolling back this update.
Will setup an H2 testDb and perform the same operation there and will post the outcome.
Thanks for the quick help guys. :-)
Why am I getting detached entity exception. When we fetch an entity from the DB, Spring Data JPA must be using entityManager to fetch the entity. The fetched entity gets automatically attached to the persistent context right ?
Right, but only for as long as the entityManager stays open. Without the transactional, as soon as you return from studentRepository.findById(100L).get();, the entityManager gets closed and the object becomes detached.
When you call the save, a new entityManager gets created that doesn't contain a reference to the previous object. And so you have the error.
The #Trannsaction makes the entity manager stay open for the duration of the method.
At least, that's what I think it's happening.
On attaching #Transactional on the test, why the transaction is getting rolledback,
With bi-directional associations, you need to make sure that the association is updated on both sides. The code should look like:
#Test
#Transactional
public void testLibrarySubscriptionPersist() {
Student student = studentRepository.findById(100L).get();
StudentSubscription librarySubscription = new LibrarySubscription();
librarySubscription.setValidFrom(//some date);
librarySubscription.setValidTo(//some date);
// Update both sides:
librarySubscription.setStudent(student);
student.getStudentSubscription().add(librarySubscription);
// Because of the cascade, saving student should also save librarySubscription.
// Maybe it's not necessary because student is managed
// and the db will be updated anyway at the end
// of the transaction.
studentSubscriptionRepository.save(student);
}
In this case, you could also use EntityManager#getReference:
#Test
#Transactional
public void testLibrarySubscriptionPersist() {
EntityManager em = ...
StudentSubscription librarySubscription = new LibrarySubscription();
librarySubscription.setValidFrom(//some date);
librarySubscription.setValidTo(//some date);
// Doesn't actually load the student
Student student = em.getReference(Student.class, 100L);
librarySubscription.setStudent(student);
studentSubscriptionRepository.save(librarySubscription);
}
I think any of these solutions should fix the issue. Hard to say without the whole stacktrace.

Unable to initialize lazy-loaded relationship inside of `#Transactional` method

I have a set of simple models like this (getters and setters omitted for brevity):
#Entity
public class Customer {
#Id
private Integer id;
}
#Entity
public class Order {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
}
I am trying to load an Order using a Spring JPA repository with a findById method, including the customer.
First I tried this:
#Transactional
Optional<Order> findById(Integer id) {
return repository.findById(id);
}
But when I tried to access Customer I got a LazyInitializationException: could not initialize proxy - no Session. So after referring to some other questions, I updated my method to be a bit uglier, but to explicitly call Hibernate.initialize:
#Transactional
Optional<Order> findById(Integer id) {
return repository.findById(id)
.map( order -> {
Hibernate.initialize(order.getCustomer());
return order;
);
}
But I still get org.hibernate.LazyInitializationException: could not initialize proxy - no Session. repository is a regular CrudRepository which provides the findById method out-of-the-box.
How can I initialize this lazily loaded child entity? My understanding is that the #Transactional indicates that I should still be within the transaction for the entirety of this method call. The only thing further downstream is the repository itself, which is just an interface, so I'm not sure how else to go about forcing the load of this child entity.
The Order entity and everything else in it is retrieved properly from the database; it's only when I try to get the lazy-loaded child entities that we start having issues.
The only way I managed to get this working was to write a custom hql method in the Repository using a left join fetch. While that works, it clutters up my repository with a method that is pretty much a duplicate of another and which I'm pretty sure I'm not actually supposed to need (so I would rather not do it this way.)
Spring-Boot 2.1.4.RELEASE, Spring 5.1.6.RELEASE, Hibernate 5.3.7.Final.
You have to define the method as public. See "Method visibility and #Transactional" in the spring docs.
This should work:
#Transactional
public Optional<Order> findById(Integer id) {
Optional<Order> order = repository.findById(id);
order.ifPresent(o -> Hibernate.initialize(o.getCustomer()));
return order;
}

Collection not updating upon saving opposite entity using Spring Data Jpa

I have two entities that are in a one-to-many relationship:
Parent entity:
#OneToMany(mappedBy = "parent")
public List<Child> getChildren()
Child entity:
#ManyToOne
#JoinColumn(name = "PARENT_ID")
public Parent getParent()
Consider the following code (inside transaction):
Child child = childRepository.findById(id).get();
Parent parent = child.getParent();
child.setParent(null);
childRepository.saveAndFlush(child);
List<Child> children = parent.getChildren();
In this case the 'children' list will still contain the child entity although it is already removed. I tried flushing the repositories, saving the parent entity or even getting a new one from the parentRepository, none of these worked.
Why is the children list not updated upon save and how can I make sure the collection is up-to-date without explicitly removing the entity (I want to make further operations on the entities in the collection)?

Spring-JPA: updating parent Entity fails to persist new child Entities, interpreting them as Transient instead

I'm new to Spring/JPA/Hibernate, and while it sounds easy reality just hasn't been. I could use some help.
I have a parent Entity that holds a list of child Entities. I'll use these to keep the discussion simple:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="parent")
private List<Child> children= new ArrayList<Child>();
etc...
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Parent parent;
etc...
}
#Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {};
Round 1, I create a new parent and a new child, add the child to the parent's list and set the parent on the child. When I save the parent the child is saved as well.
#Transactional(propagation=Propagation.REQUIRES_NEW)
void create() {
Parent parent = new Parent();
Child child = new Child();
parent.add(child);
child.setParent(parent);
parent = repository.save(parent);
}
Now, Round 2, I add a new child:
#Transactional(propagation=Propagation.REQUIRES_NEW)
void update() {
Parent parent = repository.findOne(parentID);
Child newChild = new Child();
newChild.setParent(parent);
parent.add(newChild);
parent = repository.save(parent);
}
However, this time the new child is never persisted!
I've tried most every variation of CascadeType, #GeneratedValue GenerationType, #Transactional Propagation type...
Tracing this through hibernate (painful!), here's what I've found:
When saving the second time, the problem is with the second (new) child.
The issue seems to be that when it comes time to persist the parent's Child list
the new child is not in the EntityManager (yet) and thus in considered to be Transient.
As a result, it is effectively being passed down the chain as null, resulting in the following:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is
javax.persistence.RollbackException: Error while committing thetransaction
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
...
Caused by: javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:92)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:512)
...
Caused by: org.hibernate.AssertionFailure: collection [null] was not processed by flush()
at org.hibernate.engine.spi.CollectionEntry.postFlush(CollectionEntry.java:225)
...
It might be relevant that in my actual code "Child" also has a map of child Entities. This "value" is what gets passed down as null due to the "Transient" misappropriation.
I've been using repository.saveAndFlush() to keep things synchronous for debugging. When I use just .save() my #PreUpdate EntityListener is called but the #PostUpdate listener never is.
It seems that there wouldn't be a problem if Child were just persisted or given an Id at least before persisting Parent. But it also seems counter-productive to do that manually. Still, that's the only alternative I can think of.
Thanks for reading. Any help would be much appreciated!
I found the problem, though I don't really have a complete solution yet.
First, some additional background. In addition to Parent and Child, I had a related class I'll call "House" here. House has an EntityListener defined so that when it is saved/updated, the associated Parent & Child objects get created/updated. So it is during House's PostPersist/PostUpdate that Parent and Child objects are created, linked, pointed back to House, and then persisted.
So the problem seems to be this is done before the House transaction completes. By merely pulling out the Parent/Child activity until after the House activity completes, all the problems went away.
The only thing I can figure (I'll dig a little deeper) is that since House hasn't been completely persisted at that moment, it results in the Transient condition described above.
Update:
Chalk one up to ignorance. Apparently EntityCallback methods "should not call EntityMan­ager or Query methods and should not access any other entity objects." Did not know that. This raises the question now of how should I trigger an Entity's creation on another's activity. But I'll start another thread for that if necessary. Thanks all!
Everything in what you've shown seems pretty normal, so the problem might lie in that map you mentioned. I have a working example of a bidirectional one-to-many using Spring Data JPA on Github. You can look through the code or clone and run it with:
git clone git://github.com/zzantozz/testbed tmp
cd tmp/spring-data
mvn -q compile exec:java -D exec.mainClass=rds.springdata.JpaBiDiOneToManyExample

Resources