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.
Related
I have an "interesting" structure for entities and I'm stuck trying to clone them. I basically have one super-parent that I'm cloning. Let's call this class Document. A document has at least one Function and one Component. Now this part isn't an issue, these are #OneToMany and #ManyToOne and I can clone them just fine. But now there is a third class that belongs to both Function and Component - let's call it an Interaction. By design this is part of both the Function and the Component as it described something that both do when interacting.
Now in a normal flow there would be an order for creation, where a Document is created first, then at least one Component and Function is created automatically. After that Interactions can be created. You can imagine this like a matrix, where an Interaction described how one Component interacts with one Function. Generally speaking Component and Function are not related and can exist independently, though at least one of each will always exist so that the matrix exists.
Now the issue is cloning an entire document. When cloning all the nested entities must be cloned and keep their relations. However IDs are not known at clone time (as they must be unset or new entities created) so I'm not sure how to prevent duplicates here.
How would one clone this entire Document and it's sub-relations with new IDs and keep all the relations intact? I know this is easy if all the relations are in a "linear" structure, but multiple parents appear to be difficult.
Here is a diagram for clarity:
Document.java
#Table(name = "document")
#Entity
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToMany(mappedBy = "document", cascade = CascadeType.ALL)
private List<Component> components = new ArrayList<>();
#OneToMany(mappedBy = "document", cascade = CascadeType.ALL)
private List<Function> functions = new ArrayList<>();
}
Component.java
#Table(name = "component")
#Entity
public class Component {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
private Document document;
#OneToMany(mappedBy = "component", cascade = CascadeType.ALL)
private List<Interaction> interactions;
}
Function.java
#Table(name = "function")
#Entity
public class Function {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
private Document document;
#OneToMany(mappedBy = "function", cascade = CascadeType.ALL)
private List<Interaction> interactions;
}
Interaction.java
#Table(name = "interaction")
#Entity
public class Interaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
private Function function;
#ManyToOne
private Component component;
}
For copying a graph one usually has to build an identity map from source to target and query that before doing a copy. Something like this:
interface CustomClonable {
Object clone(Map<Object, Object> mapping);
}
public static <T extends CustomClonable> T clone(T original, Map<Object, Object> mapping) {
Object copy = mapping.get(original);
if (copy == null) {
copy = original.clone(mapping);
mapping.put(original, copy);
}
return (T) copy;
}
All your type implement that interface and clone themselves, invoking the static clone method for nested objects.
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'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
Lets say I have bidirectional one-to-many association between Parent-Child, mapped as follows:
Parent.java:
#Entity
public class Parent {
#Id
private Integer id;
#OneToMany(mappedBy = "parent")
private List<Child> childs = new ArrayList<>();
...
and Child.java:
#Entity
public class Child {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
...
When I run this code
Parent parent = new Parent(1);
Child child = new Child(1);
Child child2 = new Child(2);
child.setParent(parent);
child2.setParent(parent);
parent.getChilds().add(child);
parent.getChilds().add(child2);
parentRepository.save(parent);
I get exception
Unable to find Child with id 1
Saving a child first doesn't help either, only exception is different
well i am sorry for posting a not sure answer but i cannot post a comment cause of reputation.
i think you have a cross reference problem, because simply by referencing the parent from the child you can get the childs that parent has with a simple query. instead you cross reference the child association resulting to many object problems. if you want i can post you a class diagram for better explanation. hope it helps
Try
#OneToMany(mappedBy = "parent", cascade={CascadeType.PERSIST})
private List<Child> childs = new ArrayList<>();
(see also JPA #ManyToOne with CascadeType.ALL for example)
I am trying to configure this #OneToMany and #ManyToOne relationship but it's simply not working, not sure why. I have done this before on other projects but somehow it's not working with my current configuration, here's the code:
public class Parent {
#OneToMany(mappedBy = "ex", fetch= FetchType.LAZY, cascade=CascadeType.ALL)
private List<Child> myChilds;
public List<Child> getMyChilds() {
return myChilds;
}
}
public class Child {
#Id
#ManyToOne(fetch=FetchType.LAZY)
private Parent ex;
#Id
private String a;
#Id
private String b;
public Parent getParent(){
return ex;
}
}
At first, I thought it could be the triple #Id annotation that was causing the malfunction, but after removing the annotations it still doesn't work. So, if anyone have any idea, I am using EclipseLink 2.0.
I just try to execute the code with some records and it returns s==0 always:
Parent p = new Parent();
Integer s = p.getMyChilds().size();
Why?
The problem most probably is in your saving because you must not be setting the parent object reference in the child you want to save, and not with your retrieval or entity mappings per se.
That could be confirmed from the database row which must be having null in the foreign key column of your child's table. e.g. to save it properly
Parent p = new Parent();
Child child = new Child();
p.setChild(child);
child.setParent(p);
save(p);
PS. It is good practice to use #JoinColumn(name = "fk_parent_id", nullable = false) with #ManyToOne annotation. This would have stopped the error while setting the value which resulted in their miss while you are trying to retrieve.
All entities need to have an #Id field and a empty constructor.
If you use custom sql scripts for initialize your database you need to add the annotation #JoinColumn on each fields who match a foreign key :
example :
class Parent {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
public Parent() {}
/* Getters & Setters */
}
class Child {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
/* name="<tablename>_<column>" */
#JoinColumn(name="Parent_id", referencedColumnName="id")
private int foreignParentKey;
public Child () {}
}
fetch= FetchType.LAZY
Your collection is not loaded and the transaction has ended.