Save either a new or detached child object using Spring JPA and Hibernate - spring

I have a class like this:
#Entity
#Table(name = "parent")
class Parent{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "child")
private Child child;
}
The child class and therefore the child table has no references to Parent. Now the problem is that I have code like:
//Brand new transient parent created
Parent parent = new Parent();
//Either find child in the DB using the attributes or create a brand new one
Child child = createOrFindChild(childAttributes)
//Set child in parent
parent.setChild();
parentRepository.save(parent)
The problem here is that if I don't find the child and try to save I get the error:
"object references an unsaved transient instance - save the transient instance before flushing"
If I set CascadeType.PERSIST then it works when a brand new child but when I find the child in the DB and it is detached then I get the error:
"detached entity passed to persist".
Anyway to make it work without explicitly saving child first?

You can use CascadeType.MERGE and use EntityManager#merge in your repository.

Related

Spring/Hibernate/JPA: Prevent reassigning child to another parent

I am working in a SpringBoot application with Hibernate that exposes a REST interface.
The Problem
Users are able to create objects, let's name them parent. Users can associate the parent object with several child items. These items are also composed of other objects, but lets keep it simple for now. I want to prevent the case that a client can do the following:
create parent1 and create child1, child2
create parent2 and create child3
update parent1 with child3 in payload
This behaviour should not be possible as the child items should not be updatable when they belong to another parent.
My goal is to make it impossible to update parent and update any child that belongs to a different parent.
The model
#Entity(name = "Parent")
#Table(name = "t_parent")
data class Parent(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int = 0,
#JoinColumn(name = "fk_parent")
#OneToMany(orphanRemoval = true, cascade = [CascadeType.ALL])
val children: MutableList<Child> = mutableListOf()
)
#Table(name = "t_child")
#Entity(name = "Child")
data class Child(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int = 0,
)
I am using Spring's JpaRepository for persistence.
Solutions?
The 'dumbest' way of doing this would be to fetch parent1 from the database and remove all child items from the client's request that are not contained in dbParent1. This is quite tedious and not really smart I guess.
Thanks for your advice.
What about transactions? In a transactional method you can check if a child belongs to another parent. If you find any obstacle for update/insert or whatever, then rollback the transaction (for example, just throw an exception). This methodology will require additional service layer in your archtecture since the code of repos is autogenerated from interface declarations.
This approach is described here: How to override save method of CrudRepository REST wise
I would do this in the following way for the update operation:
1) Fetch the parent with childId
2a) If there are no parents, then just add the child to the parent (new child)
2b) Otherwise, check if the child belongs to the parent (you can use parentId probably set in the #PathVariable
2b1) If not, throw a new IllegalArgumentException("wrong parent")
2b2) Otherwise, update parent

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)?

Not able to save parent id when handling OneToMany relationship

I am facing some difficulty while maintaning one-to-many relationship through Spring JPA. We have two entities parent and child. I have defined many to one relationship like this
parent entity
#OneToMany(cascade = CascadeType.ALL, mappedBy = parent)
Set<Child> childs;
child entity
#ManyToOne
#JoinColumn(name=""parent_id)
private Parent parent;
Below is the code in my service to save the parent.
Parent parent = new Parent();
parent.setName("name");
List<Child> children= new ArrayList<>();
Child child1 = new Child();
child1.setAge(10);
children.add(child1);
Child child2 = new Child();
child2.setAge(11);
children.add(child1);
parent.setChilds(children)
parentReposiroty.save(parent);
It saving data in both table but in child table parent_id is null. Kindly suggest what I am missing here.
When managing bidirectional relationship you should also set parent for each child when saving from parent side. so see below to update your code to set parent for child by adding child1.setParent(parent); and child2.setParent(parent);
Parent parent = new Parent();
parent.setName("name");
List<Child> children= new ArrayList<>();
Child child1 = new Child();
child1.setAge(10);
child1.setParent(parent);
children.add(child1);
Child child2 = new Child();
child2.setAge(11);
child2.setParent(parent);
children.add(child1);
parent.setChilds(children)
parentReposiroty.save(parent);

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

(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.

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