I want to persist parent entity with 20 child entities,
my code is below
Parent Class
#OneToMany(mappedBy = "parentId")
private Collection<Child> childCollection;
Child Class
#JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
#ManyToOne(optional=false)
private Parent parent;
String jsonString = "json string containing parent properties and child collection"
ObjectMapper mapper = new ObjectMapper();
Parent parent = mapper.readValue(jsonString, Parent.class);
public void save(Parent parent) {
Collection<Child> childCollection = new ArrayList<>() ;
for(Child tha : parent.getChildCollection()) {
tha.setParent(parent);
childCollection.add(tha);
}
parent.setChildCollection(childCollection);
getEntityManager().persist(parent);
}
So if there are 20 child tables then I have to set parent reference in each of them for that I have to write 20 for loops?
Is it feasible? is there any other way or configuration where I can automatically persist parent and child?
Fix your Parent class:
#OneToMany(mappedBy = "parent")
mappedBy property should point to field on other side of relationship. As JavaDoc says:
The field that owns the relationship. Required unless the relationship is unidirectional.
Also you should explicitely persist Child entity in cycle:
for(Child tha : parent.getChildCollection()) {
...
getEntityManager().persist(tha);
...
}
As Alan Hay noticed in comment, you can use cascade facilities and let EntityManager automatically persist all your Child entities:
#OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
More details about cascades (and JPA itself) you can find in Vlad Mihalcea's blog.
Generally, #JoinColumn indicates that the entity is the owner of the relationship & mappedBy indicates that the entity is the inverse of the relationship.
So, if you are trying like following
#OneToMany(mappedBy = "parent")
private Collection<Child> childCollection;
That means it is inverse of the relationship and it will not set parent reference to its child.
To set parent reference to its child, you have to make the above entity owner of the relationship in the following way.
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn
private Collection<Child> childCollection;
You need not set any child reference because above code will create a column in the child table.
As pointed out in the comments you must take care of the object graph consistency with child/parent relationship. This consistency won't come free when JSON is coming directly from i.e. a POST request.
You have to annotate the parent and child field with #JsonBackReference and #JsonManagedReference.
Parent class:
#OneToMany(mappedBy = "parentId")
#JsonBackReference
private Collection<Child> childCollection;
Child class:
#JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
#ManyToOne(optional=false)
#JsonManagedReference
private Parent parent;
Similar question with answer is here
Furthermore, if you use #JsonBackReference/#JsonManagedReference on javax.persistence annotated classes in combination with Lombok's #ToString annotation you will incur in stackoverflow error.
Just exclude childCollection and parent field from the #ToString annotation with #ToString( exclude = ...)
The same will happen with Lombok's generated equals() method (#Data, #EqualsAndHashCode). Just implements those methods by hand or to use #Getter and #Setter annotations only.
I would let the parent persist it's own children
package com.greg;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
#Entity(name = "PARENT")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "DESCRIPTION")
private String description;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name = "parent", referencedColumnName = "id", nullable = false)
private List<Child> children = new ArrayList<Child>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<Child> getChildren() {
return children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
}
I am using lombok to generate getter and setter properties on my entity classes.
I was also facing issue of NULL referenceID on child entity when I was trying to save parent Entity having child.
On my parent entity when I add children then I set "this" reference of parent on child.
In my example, I have User table and Address table where a User can have many addresses.
I have created domain classes as below.
e.g address.setUser(this);
package com.payment.dfr.entities;
import lombok.Data;
import javax.persistence.*;
import java.math.BigInteger;
#Entity
#Data
#Table(name="User")
public class User {
#Id
#GeneratedValue
private BigInteger RecordId;
private String Name;
private String Email;
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Address> addresses = new ArrayList<>();
public void addAddress(Address address){
address.setUser(this);
addresses.add(address);
}
}
#Entity
#Data
#Table(name="UserAddress")
public class Address {
#Id
#GeneratedValue
private BigInteger RecordId;
private String AddressLine;
private String City;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="UserId")
private User user;
}
This is how I save user with address
User newUser = new User();
newUser.setName("Papa");
newUser.setEmail("manish#gmail.com");
Address address1 = new Address();
address1.setAddressLine("4401 Central Ave");
address1.setCity("Fremont");
newUser.addAddress(address1);
Address address2 = new Address();
address2.setAddressLine("4402 Central Ave");
address2.setCity("Fremont");
newUser.addAddress(address2);
User user1 = userRepository.save(newUser);
log.info(user1.getRecordId().toString());
Related
I have three tables.
1.Street (street_id, settlement_entity_id, street_name),
2.Settlement_entity (settlement_entity_id, settlement_id, settlement_name), for example could be community, regional, municipality.. and
3.Settlement (settlement_id, settlement_name), for example could be city, town, township, village....
Relation - Street - Settlement_entity = > onetomany
Relation - Settlement_entity - Settlement = > onetomany
I want when I enter the name of the street, I get the settlement entity as above
as in parentheses and after that the settlement name with characteristics that are not to be the subject of this application, such as for example the house/apartment number, floor, longitude, latitude, altitude of the settlement.
entity
Street.java
package net.javaguides.springboot.entity;
#Entity
#Table(name = "street")
public class Street {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long street_id;
#Column(name = "street_name")
private String street_name;
public Street(String street_name) {
super();
this.street_name = street_name;
}
// getter and setters
entity
Settlement_entity.java
package net.javaguides.springboot.entity;
import javax.persistence.Column;
#Entity
#Table(name = "settlement_entity")
public class SettlementEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long settlement_entity_id;
#Column(name = "settlement_entity_name")
private String settlement_entity_name;
public SettlementEntity(String settlement_entity_name) {
super();
this.settlement_entity_name = settlement_entity_name;
}
// getters and setters
public void setSettlement_entity_name(String settlement_entity_name) {
this.settlement_entity_name = settlement_entity_name;
}
}
entity
Settlement.java
package net.javaguides.springboot.entity;
import javax.persistence.Column;
#Entity
#Table(name = "settlement")
public class Settlement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long settlement_id;
#Column(name = "settlement_name")
private String settlement_name;
public Settlement(String settlement_name) {
super();
this.settlement_name = settlement_name;
}
// getters and setters
}
I creating according suggestion from -thelearner new linking entity which would be used for mapping that particular table.
entity linking table
StreetSettlementEntitySettlement.java
package net.javaguides.springboot.entity;
import javax.persistence.CascadeType;
public class StreetSettlementEntitySettlement {
#Id
#GeneratedValue
private long id;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "street_id")
private Street street;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "settlement_entity_id")
private SettlementEntity settlement_entity;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "settlement")
private Settlement settlement;
public StreetSettlementEntitySettlement(long id, Street street, SettlementEntity
settlement_entity, Settlement settlement) {
super();
this.id = id;
this.street = street;
this.settlement_entity = settlement_entity;
this.settlement = settlement;
}
}
Directory structure
I saw some proposal from thelearner like this:
It's not clear for me and don't understanding. Where should I put those three classes or should do something else. Repo's is clear.
Any help and explanation would be appreciated. Thank you
Simple example (hopefully). I have a primary key (using a sequence) in one table and that value is a partial FK into a child table. I see the Parent is trying to be saved with a generated sequence, but then I see an exception that the parentId in the embeddable is null while saving the child. The sequence value used for the parent is not being carried over to the child. I have tried many annotations and mappedBy/join column names but no luck.
Any pointers would be very much appreciated.
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "audit_seq")
#SequenceGenerator(name = "audit_seq", allocationSize = 5)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
//Used to add child record o the parent
public void addChild(Child child) {
this.childList .add(child);
child.setParent(this);
}
}
#Embeddable
public class ChildId {
private Long parentId;
private String name;
}
public class Child {
#EmbeddedId
private ChildId id;
private String myCol;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "parentId", insertable = false, updatable = false)
private Parent parent;
}
I was able to get this resolved with the use of a couple of annotations:
Parent class:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true)
#PrimaryKeyJoinColumn
private List<Child> childList = new ArrayList<>();
Child class:
#ManyToOne
#MapsId("id")
#JoinColumn(name = "id")
private Parent parent;
Now all objects are being persisted when saving the parent with the appropiate sequence id.
org.postgresql.util.PSQLException: ERROR: insert or update on table "party_custom_fields" violates foreign key constraint "fk21oqkpi7046skme7jce06fxdu"
Below error could help, what need to be done on the code, I have tried few reference, but not helpful.
Detail: Key (custom_field_value)=(11) is not present in table "custom_field_value"
Above is my error while saving.
Party is the class which will have custom fields and it's data
import lombok.Data;
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
#Entity
#Data
#Table(name = "party")
public class Party {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Column(name = "last_name")
private String lastName;
private String email;
private String gender;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "party_custom_fields",
joinColumns = {#JoinColumn(name = "custom_field")},
inverseJoinColumns = {#JoinColumn(name = "custom_field_value")})
#MapKeyColumn(name = "custom_field_key")
private Map<Long, CustomFieldValue> customField = new HashMap<>();
public Party() {
}
public Party(String name) {
this.name = name;
}
}
Custom fields value model
package org.aeq.multitenant.model;
import lombok.Data;
import javax.persistence.*;
#Data
#Entity
#Table(name = "custom_field_value")
public class CustomFieldValue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String value;
}
Custom fields model which hold what are the custom fields for the tables
package org.aeq.multitenant.model;
import lombok.Data;
import org.aeq.multitenant.enums.Tables;
import javax.persistence.*;
#Data
#Entity
#Table(name = "custom_field")
public class CustomField {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String type;
private boolean optional;
#Enumerated(EnumType.STRING)
#Column(name = "table_name")
private Tables tableName;
}
Controller function to save
Map<Long, CustomFieldValue> cfMap = new HashMap<>();
for (CustomField cf : customFields) {
if (!partyData.containsKey(cf.getName())) {
return new ApiResult<>(false, "Please provide " + cf.getName() + " custom field of party");
} else {
CustomFieldValue cfv = new CustomFieldValue();
cfv.setValue(partyData.get(cf.getName()).trim());
cfv = customFieldValueRepository.save(cfv);
cfMap.put(cf.getId(), cfv);
}
}
Party party = new Party();
party.setName(partyData.get("name"));
party.setEmail(partyData.get("email").trim());
party.setGender(partyData.get("gender").trim());
party.setLastName(partyData.get("last_name").trim());
party.setCustomField(cfMap);
party = partyRepository.save(party);
please review my code and let me where I am going wrong
If a column has a foreign key constraint, then any entry to that column should be present in the reference table given. If not, then this exception will be thrown.
I've seen other posts about this problem, but have found no answer to my own troubles. I have
#Entity
#Table(name= ServerSpringConstants.COMPANY)
public class Company implements Serializable {
private static final long serialVersionUID = -9104996853272739161L;
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column (name = "companyID")
private long companyID;
#OneToMany (targetEntity = Division.class, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch = FetchType.EAGER)
#JoinTable (name = "companyDivisionJoinTable",
joinColumns = #JoinColumn(name="companyID"),
inverseJoinColumns = #JoinColumn(name="divisionID")
)
private Set<Division> divisions = new HashSet<>();
public long getCompanyID() {
return companyID;
}
public Set<Division> getDivisions() {
return divisions;
}
public void setDivisions(Set<Division> divisions) {
this.divisions = divisions;
}
}
On the other side:
#Entity
#Table(name= ServerSpringConstants.DIVISION)
public class Division implements Serializable {
private static final long serialVersionUID = -3685914604737207530L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "divisionID")
private long divisionID;
#ManyToOne
(fetch = FetchType.LAZY, optional = false, targetEntity = Company.class,
cascade = {CascadeType.PERSIST, CascadeType.MERGE
}
)
#JoinColumn(name="companyID", referencedColumnName = "companyID")
private Company company;
public long getDivisionID() {
return divisionID;
}
public void setDivisionID(long divisionID) {
this.divisionID = divisionID;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Yet for some reason, LAZY loading not working. I'm using JPA. I'm calling back the companies, and their enclosing divisions from within a 'User' class -- the pertinent part
#ManyToMany (targetEntity = Company.class,
cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch=FetchType.EAGER )
#JoinTable (
name="companyUserJoinTable",
joinColumns=#JoinColumn(name="userID"),
inverseJoinColumns=#JoinColumn(name="companyID")
)
private Set<Company> company = new HashSet<>();
I've searched out existing threads, and have tried adding various suggestions, but nothing has helped!
Any help appreciated.
Thanks!
Since you are loading the divisions set eagerly (with fetch = FetchType.EAGER) and you have a bidirectional association, divisions will be initialized with the parent reference to company. I can't see any problem with it. Jpa loaded the full object tree because you just told it so. A company contains divisions which contain a back reference to the company that loaded them.
To understand it better, since the reason for lazy loading is to reduce the data loaded from database, the owning company is already loaded in session for the divisions, so why not setting the association too?
The #ManyToOne association on the other side takes effect if you load divisions first.
To be more correct with your mapping add also a #MappedBy attribute to the one part of the association. This does not affect fetching behavior but will prevent double updates to the database issued by both ends of the association.
I wrote an example for the code i am trying to implement, i get an error with Constraint "Student_Teacher_FK" already exists.
the #embiddable class has a foreign key that is created twice with current code.
#Entity
public class Teacher {
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Entity
public class Student{
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Embeddable
public class StudentList implements Serializable {
#ManyToMany
#JoinTable(name = "Student_Teacher",
joinColumns =
#JoinColumn(name = "Student_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "Teacher_ID", referencedColumnName = "ID")
)
#ForeignKey(name = "Student_Teacher_FK", inverseName = "Teacher_Student_FK")
public List<Student> studentList = new ArrayList<Student>();
}
#Entity
public class HistoryTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class LangTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class RetiredTeacher extends Teacher {
// has no students
}
#embeddable : Defines a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity (http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html)
As you are declaring it in 2 different entity, jpa will create associated association table (student-teacher) 2 times with associated fk, which is explicitely named, and so created 2 times too with the same name. Here is your error.
I don't think using #embeddable is appropriated for what you're intending to do. A student has is own existence and is not part of teacher itself (not an uml composition / black diamond) so it's not an embeddable entity. Student list should be held by teacher entity using a simple manyToMany association.