Get reference to parent document from embedded document in Spring Data MongoDB - java

I'd like to have access to the parent object from an embedded object, but can't find a way to do it. I'll show here an example of what this means:
I have 2 Java classses like this:
#Document
public class Parent {
private String name;
private List<Child> children;
#PersistenceConstructor
public Parent (String name, List<Child> children) {
this.name = name;
this.children = children;
}
...
#Document
public class Child {
private String name;
private Parent parent;
#PersistenceConstructor
public Child(String name, Parent parent) {
this.name = name;
this.parent = parent;
}
...
My document in MongoDB is like this:
{
"name": "some name",
"children" : {
"name": "name1"
},
{
"name" : "name2"
}
}
Is it somehow possible to get reference to the parent, for example in the constructor in Child class? It seems that all the parameters in #PersistenceConstructor annotated constructor in Child class have to come from the children array, which means that the parameter named parent comes as null :(
I know that if I create my own Converter implementation, I could just de-serialize Parent and Child and set the parent reference to child myself, but my actual document is quite big and having to write the de-serialization code myself would be a lot of boiler-plate code.

What you can do is to set the parent reference manually:
in Child:
#Transient
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
The #Transient annotation tells spring data to ignore the field when saving the object.
Then in Parent:
#PersistenceConstructor
public Parent (String name, List<Child> children) {
this.name = name;
this.children = children;
for (Child child : children) {
child.setParent(this);
}
}
and with some clever interfaces and helper class or inheritance the amount of duplicated code will be minimal.

As a variant of #František Hartman's answer, instead of using #PersistenceConstructor you can use a AfterConvertCallback to populate the parent field, so that the inner working remains mostly invisible to the entity class.
This is the callback:
#Configuration
public class EntityCallbacks {
#Bean
AfterConvertCallback<MyDocument> myDocumentAfterConvertCallback() {
return (entity, document, collection) -> {
entity.getChild().setParent(entity);
return entity;
};
}
}
And these are your entity classes (I also used Lombok):
#Data
#Document(collection = "myCollection")
public class MyDocument {
#Id
private String id;
#Field("field")
private String field;
#Field("child")
private MyChildDocument child;
}
#Data
//the following annotations are necessary to avoid a stack overflow when calling the corresponding methods
#ToString(exclude = "parent")
#EqualsAndHashCode(exclude = "parent")
public static class MyChildDocument {
#Transient
private MyDocument parent;
#Field("childField")
private String childField;
}

Related

Hibernate: Child id is null after merging parent

I've poured over similar questions on stackoverflow but I'm not able to find the right answer, so I'll post the question, if I may:
I have a typical one-to-many Parent-Child entities in a Spring Boot application. So, I create a new child, add it to the parent, and merge/flush/clear entity manager. Child entity gets saved in the database correctly, with a new ID, but the Child object's ID under the Parent object is zero. What am I missing that would force the refresh of the ID?
The code that creates a new child is in the same controller that fetches the Parent initially, so it's using the same DAO and the same entity manager.
Here's my code (entities are mostly generated by JPA):
Parent:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.MERGE})
private List<Child> children;
public List<Child> getChildren() {
return this.Children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
public Child addChild(Child child) {
getChildren().add(child);
child.setParent(this);
return child;
}
}
Child:
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade={CascadeType.MERGE})
#JoinColumn(name="parent_id")
private Parent parent;
public Parent getParent() {
return this.parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
DAO:
#Repository
public class ParentDao {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void update(Parent parent) {
entityManager.merge(parent);
entityManager.flush();
entityManager.clear();
}
}
Controller:
#Autowired
private ParentDao parentDao;
. . . createChild() {
Child child = new Child();
// Set the link on both sides of the relationship
parent.addChild(child);
parentDao.update(parent);
// --> child.getId() == 0
}
The way merge() works when it is invoked on a new entity (Child in your case) it acts a persist() call with one small difference.
It only creates a copy of the passed entity and calls persist() on the copy.
So your Parent entity cannot see the change of id because it is holding the 'old' instance of the Child which was not persisted and eventually updated with a fresh id.
Hope that clears things up.
Update
To get the new state you need to:
1) change the transactional repository method to return the merge() value:
#Transactional
public Parent update(Parent parent) {
Parent newParent = entityManager.merge(parent);
entityManager.flush();
entityManager.clear();
return newParent
}
2) change your service:
#Autowired
private ParentDao parentDao;
. . . createChild() {
Child child = new Child();
// Set the link on both sides of the relationship
parent.addChild(child);
Parent newParent = parentDao.update(parent);
// --> newParent.getChildren(0).getId() == 0
}

How to code the hierarchical relationship to the node of the same type properly in spring data neo4j?

I have a tree data structure I'd like to store using Neo4j.
There is a parent node :CodeSet, which is always the root of the tree and a child nodes :Node, which themselves can have child nodes of the same type. They are connected with relationship of type :SUBTREE_OF as follows:
The parent node is displayed in red and it itself has a parent displayed in green.
As soon as parent node and child nodes have some common data, I created an abstract class:
public abstract class AbstractNode {
private Long id;
#NotEmpty
private String code;
#Relationship(type = "SUBTREE_OF", direction = Relationship.INCOMING)
private Set<Node> children;
<getters & setters omitted>
}
Class for the parent node:
public class CodeSet extends AbstractNode {
#Relationship(type = "SUBTREE_OF", direction = Relationship.OUTGOING)
private Application parent;
<getters and setters omitted>
}
Class for the child node:
public class Node extends AbstractNode {
#NotEmpty
private String description;
#NotEmpty
private String type;
#NotEmpty
private String name;
#NotNull
#Relationship(type = "SUBTREE_OF", direction = Relationship.OUTGOING)
private AbstractNode parent;
<getters and setters omitted>
}
What I need is just making a child node update. I use the following method at my service layer:
public Node update(Node node, Long nodeId) throws EntityNotFoundException {
Node updated = findById(nodeId, 0);
updated.setDescription(node.getDescription());
updated.setType(node.getType());
updated.setName(node.getName());
updated.setCode(node.getCode());
nodeRepository.save(updated);
return updated;
}
With this I got the following result:
The relationship is broken. I also tried out to specify depth=1 at findById method parameter, but that resulted in wrong relationships once again:
After that I tried out modifying bi-directional relationship in my classes to uni-directional so as only one class has an annotated with #Relatinship field pointing to another, but that did not help either.
How to make this work?
Was resolved by updating the save operation in the service method:
public Node update(Node node, Long nodeId) throws EntityNotFoundException {
Node updated = findById(nodeId, 0);
updated.setDescription(node.getDescription());
updated.setType(node.getType());
updated.setName(node.getName());
updated.setCode(node.getCode());
//added param depth=0 here
nodeRepository.save(updated, 0);
return updated;
}
Maybe there is a problem with your definition of relationship in abstract class. Children's nodes inherit INCOMING relationships too, so when you update using depth 1 relationships are bilateral.

Hibernate - Transferring a Collection of objects from one parent object to another

I have 3 types of objects - Parent, Child, and ChildAttr
My goal is to transfer a subset of Children from one Parent to another using Hibernate (3.2.5).
Objects are structured as such:
public class Parent {
Set<Child> children;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "parent")
#Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
public Set<Child> getChildren() {
return this.children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
}
...
public class Child {
Set<ChildAttr> attributes;
Parent parent;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "child")
#Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
public Set<ChildAttr> getAttributes() {
return this.attributes;
}
public void setAttributes(Set<ChildAttr> attributes) {
this.attributes = attributes;
}
}
...
public class ChildAttr {
Child child;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "child_id", nullable = false)
public Child getChild() {
return this.child;
}
public void setChild(Child child) {
this.child = child;
}
}
Now say I run some client code that takes a subset of Parent A's Child objects and moves them to Parent B:
Set<Child> children = getChildrenToTransfer(transferCriteria, parentA);
parentA.getChildren().removeAll(children);
manager.saveOrUpdate(parentA); // method also calls flush();
parentB.getChildren().addAll(children);
manager.saveOrUpdate(parentB); // error thrown here.
I get an error thrown when trying to save parentB.
Found two representations of same collection: com.mycode.Child.attributes;
The application currently seems to do this work fine in multiple sessions - e.g. - some user comes along and removes the set of children, then some time later adds them to some other Parent. Moreover, I don't really understand why it's instantiating multiple versions of that attribute list when it should really just be one, even if the Parent changes.
What is causing the aforementioned error and how do I get around it?
One rule of thumb: ensure the objects are consistent as an object graph before trying to persist them... iow you're removing all the children from parent A and are adding them to parentB but you have not updated the parent link for those children.
So I'd suggest the following:
add methods to Parent:
add(Child child) {
child.setParent0(this);
children.add(child);
}
remove(Child child) {
child.setParent0(null);
children.remove(child);
}
And then on Child:
setParent0(Parent parent) {
this.parent = parent;
}
setParent(Parent parent) {
parent.add(this);
}
This way when you add from either direction you've got a consistent object model without the outside code knowing the details.
It makes more sense to remove from the parent...
So try with these methods.
Seens thats because your bidirectional relation (parent-children). When you remove/add the child to one parent to another, you should update the parent reference.

Managing a simple OneToMany entity relationship in java without database

I'm trying to find a way to manage in a lightweight fashion two models that have a OneToMany relationship but will never be persisted.
My search has lead me to the javax.persistence that seems to be able to do what i want but i do not seem to be able to make it work.
As tests always say much more than a speech, here's what i'd like to achieve:
public void test_relationship() {
Parent p = new Parent("Mary");
Child c1 = new Child("Hugo");
Child c2 = new Child("Charly");
Child c3 = new Child("Françine");
p.addChild(c1)
Assert.assertEquals(p, c1.getParent());
p.removeChild(c1)
Assert.assertNull(c1.getParent());
p.addChildren(c1, c2)
Assert.assertEquals(p, c1.getParent());
Assert.assertEquals(p, c2.getParent());
c1.removeParent();
Assert.assertFalse(p.hasChild(c1));
c1.setParent(p);
Assert.assertTrue(p.hasChild(c1));
}
All this without a database. It's only purpose is to facilitate the access from one object to another. These objects will be built depending on data received as an HTTP request payload (JSON format) and will need to be serialized back to JSON as part of the response.
For now here's what i've done
#Entity
public class Parent implements Serializable {
private Collection<Child> children = new HashSet<Child>();
#Id
private String id;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
public Collection<Child> getChildren() {
return children;
}
public void addChild(child) {
this.children.add(child);
}
public void removeChild(child) {
this.children.remove(child);
}
public boolean hasChild(child) {
this.children.contains(child);
}
}
#Entity
public class Child implements Serializable {
private Parent parent;
#Id
private String id;
#ManyToOne
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public void removeParent() {
this.parent = null;
}
}
Edit:
I'm expecting the relationship behavior to come from javax.persistence. Maybe through an EntityManager ? I'm really confused on how those work. I know I could code this behavior myself but I would like to avoid to do this on each relation of each of my entities and have the driest code possible.
You are part of the way there. The bit that is missing is that when you create/update/remove one side of a Parent <-> Child relationship you need to do the same on the other side.
So for example
public void setParent(Parent parent) {
if (this.parent != null) {
this.parent.removeChild(this);
}
this.parent = parent;
parent.addChild(this);
}
(And change childs to children or the grammar monster will eat you!)

How to avoid cyclic reference annotating with JPA?

I'm annotating my domain model for a shop (with JPA 2, using a Hibernate Provider).
In the shop every product can have a Category. Each category can be assigned to several super- and subcategories, meaning a category "candles" can have "restaurant" and "decoration" as parents and "plain candles" and "multi-wick candles" as children, etc.
Now I want to avoid cyclic references, i. e. a category "a" that has "b" as its parent which in turn has "a" as its parent.
Is there a way to check for cyclic references with a constraint in JPA? Or do I have to write some checks myself, maybe in a #PostPersist-annotated method?
Here's my Category class:
#Entity
public class Category {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToMany
private Set<Category> superCategories;
#ManyToMany(mappedBy="superCategories")
private Set<Category> subCategories;
public Category() {
}
// And so on ..
}
I believe you would have to check this through a business rule in your code. Why don't you separate these ManyToMany mappings in a separate Entity ? Like for example:
#Entity
#Table(name = "TB_PRODUCT_CATEGORY_ROLLUP")
public class ProductCategoryRollup {
private ProductCategory parent;
private ProductCategory child;
#Id
#GeneratedValue
public Integer getId() {
return super.getId();
}
#Override
public void setId(Integer id) {
super.setId(id);
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_PRODUCT_CATEGORY_PARENT", nullable=false)
public ProductCategory getParent() {
return parent;
}
public void setParent(ProductCategory parent) {
this.parent = parent;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_PRODUCT_CATEGORY_CHILD", nullable=false)
public ProductCategory getChild() {
return child;
}
public void setChild(ProductCategory child) {
this.child = child;
}
}
In this way, you could before Saving a new entity, query for any existing Parent-Child combination.
I know I come back to the problem after several years but, I faced this problem, followed all of your resolutions and it didn't work for me. But I found the best solution using #JsonIgnoreProperties which solved the problem perfectly. In fact, I injected #JsonIgnoreProperties into the entity classes linked by a mapping like here:https://hellokoding.com/handling-circular-reference-of-jpa-hibernate-bidirectional-entity-relationships-with-jackson-jsonignoreproperties/

Categories

Resources