JPA Mapping OneToMany with partial embedded id child - spring-data-jpa

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.

Related

Pagerequest object with sort property of the child

How to specify the children's property name while creating the PageRequest object. In the below example, I want to sort based on the name property of the Customer entity which is part of the Order class. I should be able to sort on any of the parent or child field.
Sort.Direction sortDirection = direction
.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC;
//how to make the `name` property map to `Customer` name
PageRequest pageRequest = PageRequest.of(page, size, sortDirection, "name");
Page<Order> pageResponse = this.orderRepository.findAll(pageRequest);
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name="customer_id", nullable = false)
#JsonBackReference
private Customer customer;
...
----------
#Entity
#Table(name = "customers")
#NoArgsConstructor
public class Customer implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotEmpty(message = "customer name cannot be empty")
private String name;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonManagedReference
private Set<Order> orders;
...

Foreign key is inserted null with CascadeType.PERSIST,- Spring Data Jpa

I have two tables Parent and Child. They have one to one relation between them. When I save the Parent table it should insert into the Child table as well. A child table is inserted but the foreign key is null in the child table. I want a foreign key to be the autogenerated value from the Parent table.
I am using the Postgres database.
Here are my entity relations:
#Table(name = "`PARENT`")
#Setter
#Getter
#Entity
public class Parent{
#Id
#Column(name = "`RPT_PARM_ID`")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//Other column mappings.
#OneToOne(mappedBy = "parent", cascade = { CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST })
private Child child;
}
#Entity
#Table(name = "`CHILD`")
#Setter
#Getter
public class Child {
#Id
#Column(name = "`ID`")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//Other column mappings
#OneToOne(optional = false,cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST })
#JoinColumn(name = "`PARAM_ID`", referencedColumnName ="`RPT_PARM_ID`" )
private Parent parent;
}
//This is how I save the code
pubilc void save(){
Parent parent = new Parent();
//set other fields for parent.
Child child =new Child();
//set other fields in child.
parent.setChild(child);
repository.save(parent);
}
In child table, PARAM_ID is inserted null, but I want the column value from RPT_PARM_ID (this is auto-generated)

deleting parent exception with jpa

With netbeans 8.1 I have generated entity and jpa controller.
I have a parent entity and a child with a one to one relation.
The child entity has primary key equals to foreign key to the parent:
#Entity
#Table(name = "parent")
#XmlRootElement
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "parent")
private Child child;
public Parent() {
}
etc....
}
#Entity
#Table(name = "child")
#XmlRootElement
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "parent_id")
private Integer parentId;
#JoinColumn(name = "parent_id", referencedColumnName = "id", insertable = false, updatable = false)
#OneToOne(optional = false)
private Parent parent;
public Child() {
}
etc....
}
When I try to delete the parent I get:
IllegalOrphanException: This Parent (model.Parent[ id=1 ]) cannot be destroyed since the Child model.Child[ parentId=1 ] in its child field has a non-nullable parent field.
To avoid this I should delete the child first, but due to cascade.ALL it should be already done automatically.
Coding Child class as follows works
#Entity
#Table(name = "child")
#XmlRootElement
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JoinColumn(name = "parent_id", referencedColumnName = "id")
#OneToOne(optional = false)
private Parent parent;
...
}
Tables look like
create table child (parent_id integer not null,..., primary key (parent_id))
create table parent (id integer not null, ..., primary key (id))
alter table child add constraint ... foreign key (parent_id) references parent
The removal of a parent (em.remove(parent)) leads to
delete from child where parent_id=?
delete from parent where id=?

JPA Persist parent and child with one to many relationship

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());

Exception while persisting JPA object in DB having one to many relation

hi have two tables in picture table a and table b as follows :
#Entity
#Table(name = "A")
public class A implements Serializable {
#Id
#SequenceGenerator(name = "JOURNAL_CATEGORY_ID_GENERATOR", allocationSize = 1, sequenceName = "clm_jounal_category_config_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "JOURNAL_CATEGORY_ID_GENERATOR")
#Column(name = "CLAIM_ID")
private String claimId;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "country")
private List<ClaimDTLS> claimDetails;
}
B Primary Key:
#Embeddable
public class BPK implements Serializable {
#Column(name = "code")
private String code;
#Column(name = "CLAIM_ID")
private String claimId;
}
B Entity:
#Entity
#Table(name = "B")
public class B implements Serializable {
#EmbeddedId
protected BPK bpk;
#Column(name = "name")
private String name;
#MapsId("country_code")
#JoinColumn(name = "claimId", referencedColumnName = "claimId", insertable = false, updatable = false)
#ManyToOne
private A a;
}
when i try to persist object of A type in Db the value of table b claim id is not set and is intialized with zero.
Also primary key of table A is generated with a oracle sequence.
any help will be welcomed.
thanks in advance
Sequence values are numbers and when JPA use them as a generator it call the setter method of the entity PK. Now, you defined your PK as a string while you use a sequence and so no matching setter can be found. Change the type of you PK to be Long and things shall work