JPA Unidirectional mapping to insert list of Child entity along with Parent entity + Insert child separately - spring

I have parent-children unidirectional relation as follows:
#Entity
#Table(name = "PARENT")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int parentId;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name = "parent_id", nullable = false)
private List<Child> children;
}
#Entity
#Table(name = "CHILD")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "PARENT_ID", insertable = false, updatable= false)
private int parentId;
//some other field
}
I create an instance of the parent, assign a list of children to it and try to persist it and it works well.
Parent p = new Parent();
List<Child> children = new ArrayList<Child>();
Child c = new Child();
children.add(c);
p.addChildren(children);
em.persit(p);
em.flush();
When I try to save via Child entity separately, I see that the insert query is trying to insert null to column PARENT_ID in Child Table and results in
Child c = new Child();
c.setId(78987);
c.setParentId(12345);
em.persist(c);
em.flush();
Exception while saving Child Entity independent of Parent table. The Child entity that Im trying to insert is related to the Parent entity that exists already.
java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL into ("MY_SCHEMA"."PARENT"."PARENT_ID")_
Is it not possible to save Child entity directly after defining the relation as Unidirectional?

It will work when you make the change that #Shekhar Khairnar also mentioned.
It works well when you add a child to the parent but does not work when defining a child.
You would do that when the responsibility of creating/updating the referenced column isn't in the current entity, but in another
entity.
If you are going to use unidirectional and add a child object, you should remove the insertable = false definition.
However, such usage may result in data inconsistency. You should pay attention.
In addition, you do not need to give an id when defining a child, because this will be created automatically according to your model and will not take the value you give.

Related

relationship row to be deleted on cascade delete

I have the following entities in a one-to-many relationship:
#Entry
class Parent {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<Child> children;
...
}
and
#Entry
class Child {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
...
#Transient
private Parent parent;
...
}
So – out of these, there are the following 2 entity tables in the database
parent(id, ...)
child(id, ..)
and the relationship table between these two entities-- due to that #OneToMany relationship
parent_child(parent_id, child_id)
suppose parent_id=4 has the child_id=7 and thus parent_child table has the row (parent_id=4, child_id=7).
When i delete child id =7, isn’t the (parent_id=4, child_id=7) in parent_child table supposed to be deleted as part of it? I’m seeing that row in parent_child table even after the corresponding child is deleted from the child table.
I’m using the repository (implementing CrudRepository) for deleting that child.
//////////////
UPDATE:
by parent_child(parent_id, child_id), i'm referring to the relationship table that hibernate is generating behind the scenes to maintain the relationship between parent and child tables.
went into this table out of curiosity directly on SQL. and these are what i'm seeing. i expected (still do) the (parent_id=4, child_id=7) row would disappear now that child_id=7 fell off the face of the Earth. but didn't.
You have to mapped your entity class like this.
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false,cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Parent parent;
#OneToMany(mappedBy = "parent",cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<Child> children;
Remove
#Transient
private Parent parent;
And do #ManyToOne mapping in Child Entity.

Spring Boot JPA - find by 2 columns

I don't know how to create a JPA query to get all records from my table:
#Entity
#Table(name = "A")
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "message_id")
private Message;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
#Entity
#Table(name="message")
#Getter
#Setter
public class Message{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", foreignKey = #ForeignKey(name = "FK_account_to_message"))
private Account account; //and then search by Account.id
}
SO I have 2 types of objects in A table (sometimes object is created from email, sometimes from a file):
one type without user_id (null) -> then I have to find all A object by searchning by Message -> Account -> Id
second type with user_id -> we can directly get A objects by values in user_id column
I want to get all records for specific user_id -> how to do that in most efficient way? I don't want to invoke 2 methods in repository:
User user = userService.getEmail();
List<A> aObjects= Stream.concat(ARepository.findByMessage_Account_Id(user.getId()).orElse(new ArrayList<>()).stream(),
aRepository.findByUser_Id(user.getId()).orElse(new ArrayList<>()).stream()).collect(Collectors.toList());
Is it possible to create ONE repository method that finds all records for 2 different objects (one with user_id and second without user_id)?
I guess that this query is to complex for using derived from the method name query. As it stated in the documentation:
Although getting a query derived from the method name is quite convenient, one might face the situation in which either the method name parser does not support the keyword one wants to use or the method name would get unnecessarily ugly. So you can either use JPA named queries through a naming convention or rather annotate your query method with #Query.
So, I would suggest just write the following query:
#Query("select aa from A aa left join aa.user u left join aa.message msg left join msg.account acc where (u is null and acc is not null and acc.id = :userId) or (u is not null and u.id = :userId)")
List<A> findByUserOrAccountId(#Param("userId") Long userId);

How to save child field using #RequestBody in spring-boot?

I have parent and child classes.
Parent have a field:
#OneToMany(mappedBy="parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Set<Child> childs;
Child
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "parent_id", referencedColumnName="id")
private Parent parent;
My controller
#PostMapping("/parent)
public Parent createParent(#RequestBody Parent parent) {
return parentRepo.save(parent);
}
it saves parent and childs info EXCEPT parent_id in the child table. parent_id is always null. Is it possible to workaround this problem without deleting mappedBy="parent" option and using additional tables?

Exception JPA/Hibernate: detached entity passed to persist while saving child #ManyToOne relation

Unable to save child object reference.
Employee parent object contains the child employee_detail which also has a #ManyToOne defined to save Address object.
Table structure
EMPLOYEE
ID BIGINT(20) NOT NULL AUTO_INCREMENT
NAME VARCHAR(100) NOT NULL
EMPLOYEE_DETAIL
ID BIGINT(20) NOT NULL AUTO_INCREMENT
EMPLOYEE_ID BIGINT(20) NOT NULL
ADDRESS_ID BIGINT(20) NOT NULL
Entities
#Entity
#Table(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List < EmployeeDetail > employeeDetails = new ArrayList < > ();
public void addEmployeeDetail(EmployeeDetail ed) {
employeeDetails.add(ed);
ed.setEmployee(this);
}
}
#Entity
#Table(name = "employee_detail")
public class EmployeeDetail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "employee_id")
private Employee employee;
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "address_id")
private Address address;
}
In the REST Controller method:
public void saveEmployee(#RequestBody Employee employee)
{
EmployeeDetail employeeDetail = new EmployeeDetail();
employeeDetail.setEmployee(employee);
for(EmployeeDetail ed : employee.getEmployeeDetails())
{
Address address = addressService.findOne(ed.getAddress().getId());
employeeDetail.setAddress(address);
}
employee.addEmployeeDetail(employeeDetail);
//SAVE
employeeService.create(employee);
}
Exception:
nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.abc.Address
I am unable to save the Address details with the child table EmployeeDetail. What is wrong here?
Apply CascadeType.MERGE too for Address entity as below
#ManyToOne(optional = false, cascade = {CascadeType.PERSIST,CascadeType.MERGE})
#JoinColumn(name = "address_id")
private Address address;
and use merge() instead of persist to save your changes
EDIT
Yes you will have to apply the CascadeType.MERGE for EmployeeDetail too .. otherwise you will get the same exception if you have a detached EmployeeDetail instance in you network of objects starting from Employee.
There are couple of scenarios which you will need to consider.As explained below.
When you call persist() on your root entity all the entity association mapped with CascadeType.PERSIST are also passed to the persist() (transitive peristence) . Now if any of your entity in the object graph is detached persist() will be called for that entity too and you will get an exception for that entity.
In order to save your complete graph of objects which includes both detached and transient instances , mark your association with CascadeType.MERGE and call merge() on your root entity .. Now merge() operation will be cascaded to all the entity association mapped with CascadeType.MERGE. The behaviour of merge is that it will merge any detached entity being passed to it to the existing entity in the persistence context or if the entity is transient it will make it persistent.
So while trying to save you entity graph you need to choose whether to use persist() or merge() based on the info whether the entity graph you are going to save has only transient instances or it has both transient and detached instances.You will also have to consider the cascade type set on each association.
For your code .. i see the Address instance you are setting on the EmployeeDetail instance is detached as you are getting it from another already fetched EmplyeeDetail instance.
You can get more detail on below link
JPA EntityManager: Why use persist() over merge()?
You can also extend your persistence context by creating conversations if you dont want to work with detached instances.More on this below
http://www.thoughts-on-java.org/unsychronized-persistencecontext-implement-conversations-jpa/

how to add #onetoone mapping for self entity in hibernate

how to add one-to-one mapping for the self entity. Like in this example. I want to have parent-child relationship for the Person itself.
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="personId")
private int id;
#OneToOne
#JoinColumn()
private Person parentPerson;
}
Here is example of bidirectional self mapping #OneToOne (I change column names to SQL notation):
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="person_id")
private int id;
#OneToOne
#JoinColumn(name = "parent_person_id")
private Person parentPerson;
#OneToOne(mappedBy = "parentPerson")
private Person childPerson;
}
But, I don't understand why you want to use #OneToOne in this case.
I am using it like this:
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "PARENT_ID", nullable = true)
private Person parent;
In order to add parent from your service layer, you need to already have at least one Person in the database.
Lets presume you do. Then create a new person. For e.g.:
#Transactional
public void createPerson() {
Person parent = //get your parent
Person child = new Person();
if (parent != null) {
child.setParent(parent);
}
}
If this is what you mean..

Resources