I am working on a java application using Neo4j as DB. MY data model uses a class connected to same class in a type of child-parent relationship.
I have a query to get all children for a certain node, however, since parent and child are same class, my query is returning all nodes including parent node. See code below:
#NodeEntity
class A {
#Id
private String id;
#Relationship(type = "PARENT")
private Parent parent;
// Constructor, getters, setters, etc.
}
#RelationshipEntity(type = "PARENT")
class Parent {
#Id
#GeneratedValue
private Long relationshipId;
#StartNode
private A child;
#EndNode
private A parent;
// Constructor, getters, setters, etc.
}
class Repository { // simplified repository class
#Query("MATCH(c:A)-[r:PARENT]->(p:A {id: $id}) return c,r,p")
List<A> getParents(#Param("id") String id);
}
If I change query to:
class Repository { // simplified repository class
#Query("MATCH(c:A)-[r:PARENT]->(p:A {id: $id}) return c")
List<A> getParents(#Param("id") String id);
}
it indeed returns only child nodes, but I get null if I do something like a.getParent().
Is there a query I can use to get all children with nested parent?
Related
There are a few different questions around this topic that have had answers but from what I can see many answers are old or don't make clear sense to me.
Let's say I have an Entity/Table:
#Entity
#Table(name = "ParentTable")
public class Parent {
#Id
#GeneratedValue
private Integer id;
#OneToMany(cascade = CascadeType.ALL)
#NotNull
private List<Child> children;
public Parent(String childLabel){
this.children = new ArrayList<>();
this.children.add(new Child(childLabel));
}
// Get/Set/Constructors
}
Then Child as:
#Entity
public class Child {
#Id
#GeneratedValue
private Integer id;
#NotNull
private String label;
public Child(String label){
this.label = label;
}
// Get/Set/Constructors
}
And I then construct some parents by:
String childLabel = "child-label";
Parent a = new Parent(childLabel);
Parent b = new Parent(childLabel);
// Save both parents to a db
It creates two instances of the child in the table with different IDs. I understand that it is because different instances of the Child are being created and then saved separately.
But how should I go about changing my design to ensure only one instance of two identical children is saved and referenced? I have tried constructing the child then giving to the parents but then I get a primary key error.
Alter your constructor to take a Child instead:
public Parent(Child childLabel){
this.children = new ArrayList<>();
this.children.add(childLabel);
}
If you want to enforce uniqueness for the label on Child then change the column definition in Child
#Column(unique=true, nullable=false)
private String label;
If more than one Parent needs to reference the same child then you may need to use a ManyToMany type reference instead of One to Many.
Let's say I have 2 entities like that
class Parent {
String name;
Child children;
}
class Child {
String name;
Parent parent;
}
The thing is that I don't want to allow deleting child from database if it's associated with any Parent -> child in parent can't be null. Is there any way to do it? I could just check it with some forloop everytime i try to delete child from db (like query all parents and check their childId), but it doesn't seem to be very efficient.
Probably your sample should be:
public class Parent {
#Id
#GeneratedValue
private long id;
#OneToMany(optional=false)
private Set<Child> children;
// getter/setter
...
}
public class Child {
#Id
#GeneratedValue
private long id;
private String name;
// getter/setter
...
}
So take a look in this annotation: #OneToMany(optional=false), it is for enforcing NOT NULL constraint.
Here you can find more information: http://docs.jboss.org/hibernate/core/4.2/manual/en-US/html_single/#d5e5674
Your question is not well defined, but i will asume you are using tags like #Entity since you put the Hibernate and jpa tags.
In this case you should use a #OneToMany relationship (Parent -> child) and #ManyToOne in (Child -> Parent). If you do so one of them will store the id(or whatever you use as id column). When you do the addParent method and removeParent (from Child) just don't delete the other and that's it.
I have been struggling with query dsl joins. My problem is with unidirectional Many to One mapping. I am unable to fetch data from both tables as query dsl is unable to find many to one relationship entity as null. As I am using latest query dsl library and spring data.
#Entity
Public class Parent {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
private Child c;
// Getters and setters.
}
#Entity
public class Child {
#Id
#GenereatedValue
private Integer id;
private String name;
// Getters and setters
}
// Query dsl method.
public List<Parent> getParentsByChildName(String name){
QParent qParent = QParent.parent ;
QChild qChild = QChild.child;
return queryFactory.select(Qparent).from(qChild).innerJoin(qParent). where (qChild.name.eq(name));
}
I've been looking around the net for a decent answer to this but all I've gotten is confused. I'm struggling with how #ManyToOne annotations in hibernate are supposed to work - because #OneToMany with #JoinColumn seems far superior because hibernate inserts the foreign keys properly instead of me having to assign the child object in java before saving.
Basically as the most simple example I have something like this:
Parent Table: id int,
Child Table : id int, parentFk int
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany
#JoinColumn(name = "parentFk")
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#Column
private Integer parentFk;
//setters and getters
}
Now if I send some json that maps to a Parent class and ask hibernate to save it, it will save everything in the table including the new id of the parent in the parentFk field. However I've been lead to believe that this is actually the wrong way of doing things and that I should be doing #OneToMany(mappedBy = "id") in the Parent class instead - and then also having #ManyToOne with #JoinColumn and a Parent object in the Child class.
The problem is doing it this way I have to manually set the parent object in the child via java code before hibernate will save the id of the parent in the parentFk field correctly...it just seems like a very long winded of doing something that is already working perfectly for me (albeit I cannot access the parent object from the child).
Moreover I've tried to remove the parentFk field in the child object and use #ManyToOne with a parent object reference, but hibernate doesn't seem to like it. Am I doing this all wrong?
Your mapping should be something like this.
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="parent", cascade={CascadeType.ALL})
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name="parentFk")
private Parent parent;
//setters and getters
}
You should also have an add(Child child) method on your parent (and remove) to manage the relation ship. And assuming Parent and Child are in the same package I would make the setParent on the Child package protected, i.e no access modifier (setParent(Parent parent) { this.parent=parent}).
public void add(Child child) {
child.setParent(this);
this.children.add(child);
}
When doing it like this hibernate will be able to execute the right queries.
Fixed by using #JsonManagedReference on the #OneToMany mappings and #JsonBackReference on the #ManyToOne
I have the following use case:
Parents contain Children and ParentTags.
Children contain ChildrenTags and their Parent node.
Both ParentTags and ChildrenTags are Tags with T being Parent or Child.
Now I want a derived field in Parent that map all tags relative to this parent or to one of its children.
The SQL query is straightforward but I can't make it work by annotating a given property.
Note that the #ManyToOne mapping works like a charm in order to find the Parent owner of a tag, cf my code below (this is a #OneToMany relation, Tag is maybe not the best name I could find for this example).
Parent.class
#Entity
#Audited
public class Parent {
#Id
private Long id;
#OneToMany(mappedBy = "parent")
private List<Child> children;
#OneToMany(mappedBy = "parent")
private List<ParentTag> tags;
// Does not work!
#OneToMany(mappedBy = "owner")
private List<Tag<?>> allTags;
}
Child.class
#Entity
#Audited
public class Child {
#Id
private Long id;
#ManyToOne
private Parent parent;
#OneToMany(mappedBy = "child")
private List<ChildTag> tags;
}
Tag.class
#Entity
#Audited
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Tag<T> {
#Id
private Long id;
protected String value;
#NotAudited // otherwise ClassCastException
#ManyToOne
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(
value = "CASE WHEN parent_id IS NOT NULL THEN parent_id WHEN child_id IS NOT NULL THEN (SELECT child.parent_id FROM Child child WHERE child.id = child_id) end",
referencedColumnName="id"))
})
private Parent owner;
public abstract T getNode();
}
ChildTag.class
#Audited
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ChildTag extends Tag<Child> {
#ManyToOne
private Child child;
#Override
public Child getNode() {
return child;
}
}
ParentTag.class
#Audited
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ParentTag extends Tag<Parent> {
#ManyToOne
private Parent parent;
#Override
public Parent getNode() {
return parent;
}
}
I'd like to find a way to load allTags by a given query or formula.
The inverse mapping #ManyToOne works (as long as owner is not audited, it was hard to find that bug).
I already tried the following solutions without success:
#JoinFormula
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(
value = "CASE WHEN parent_id IS NOT NULL THEN parent_id WHEN child_id IS NOT NULL THEN (SELECT child.parent_id FROM Child child WHERE child.id = child_id) end",
referencedColumnName="id"))
})
private List<Tag<?>> allTags;
I get a ClassCastException, Formula cannot be cast into a Column
#Loader
#Entity
#Audited
#NamedNativeQuery(name = "loadAllTags",
query = "SELECT * FROM Tag tag WHERE "
+ "(tag.parent_id IS NOT NULL AND tag.parent_id = :id) OR "
+ "(tag.child_id IS NOT NULL AND EXISTS (SELECT 1 FROM Child child WHERE child.id = tag.child_id AND child.parent_id = :id))")
public class Parent {
...
#Loader(namedQuery = "loadAllTags")
private List<Tag<?>> allTags;
}
No exception, actually when debugging I can see that the loader find all appropriate tags, but does not initialize the collection with them.
I also tried to put the query in a hbm configuration file, as I understood that #Loader and #NamedNativeQuery do not get along well, but without success (maybe I did something wrong here). However I'd rather implement a solution without configuration file.
I could add another column (owner_id) but if I can find a way to solve that problem without changing the model, it would be better.
I don't know if generics have something to do with it. In addition my entities are audited and indexed.
Any help will be greatly appreciated!