Hibernate JPA value removing OneToMany relation - java

I have two tables where there is a OneToMany, MnatToOne relation.
When I have added instance of AlarmnotifyEmailEntity into alarmnotifyEmailEntityList object and update instance of AlarmnotifyEmailConfEntity, value is save properly into Database.
Bu I could not do the same thing when deleting one of the item of alarmnotifyEmailEntityList.
I am sure that value is removed from alarmnotifyEmailEntityList but it does not reflect this changes into Database
#Entity(name ="alarmnotify_email_conf")
#Table(name = "alarmnotify_email_conf")
public class AlarmnotifyEmailConfEntity implements Serializable {
#OneToMany(mappedBy = "alarmnotifyEmailConfRef",cascade=CascadeType.ALL)
private List<AlarmnotifyEmailEntity> alarmnotifyEmailEntityList;
}//end of Class
#Entity (name ="alarmnotify_email")
#Table(name = "alarmnotify_email")
public class AlarmnotifyEmailEntity implements Serializable {
#JoinColumn(name = "alarmnotify_email_conf_ref", referencedColumnName = "id")
#ManyToOne
private AlarmnotifyEmailConfEntity alarmnotifyEmailConfRef;
}end of Class
I am only invoking following statement to update.
JPAManager.getJPAManagerInstance().update(alarmnotifyemailconf);
public Object update(Object o) {
try {
tx.begin();
EntityManager em = getEntityManager();
System.out.println("updating object:" + o);
o = em.merge(o);
em.close();
tx.commit();
System.out.println("closed and commited merge operation");
return o;
}
catch (Exception e) {
e.printStackTrace();
}
return o;
}

From my experience cascade only applied for same operation. If we save parent, then children also will saved same case with update. But I think when you want to remove one of children, we have to remove explicitly using entity manager, and cannot just merging parent and expect will cascade remove to children.

I have found out the answer in jpa removing child from collection.
as a result adding orphanRemoval=true solved the problem.
#Entity(name ="alarmnotify_email_conf")
#Table(name = "alarmnotify_email_conf")
public class AlarmnotifyEmailConfEntity implements Serializable {
#OneToMany(mappedBy =
"alarmnotifyEmailConfRef",cascade=CascadeType.ALL ,orphanRemoval=true)
private List alarmnotifyEmailEntityList;
}//end of Class

Related

Spring data JPA #PreRemove ConcurrentModificationException when removing from parent enity

I have a case where a participant can register courses.
Basically I have the following entity configuration (getters and setters omitted as well as other useless properties) :
#Entity
#Table(name = "course")
public class Course {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "course")
private Set<Registration> registrations;
}
#Entity
#Table(name = "participant")
public class Participant {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "participant")
private Set<Registration> registrations;
}
#Entity
#Table(name = "registration")
public class Registration {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "course_id")
private Course course;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "participant_id")
private Participant participant;
#PreRemove
private void removeRegistrationFromHolderEntities() {
course.getRegistrations().remove(this);
participant.getRegistrations().remove(this);
}
}
Then I can from my viewmodel delete a registration or a course (I have also removed unnecessary stuff) :
#Command
public void deleteRegistration(Registration reg) {
registrationMgr.delete(reg);
}
#Command
public void deleteCourse(Course crs) {
courseMgr.delete(crs);
}
Problem :
If I delete a registration, I need the #PreRemove function so I can remove the references. Without this the remove is ignored (no error, simply ignored)
If I delete a course, I have to remove the #PreRemove function else I get a ConcurrentModificationException (evidently...)
I also cannot remove references from the deleteRegistration method (instead of #PreRemove) because participant registrations are lazily loaded (would raise failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session exception).
What is the best approach here ?
I use Java 11 with Spring Boot 1.0.4 (and spring-boot-starter-data-jpa).
EDIT :
The managers/repositories or defined this way (same for registration and participant) so it should be transactional (I don't have #EnableTransactionManagement on my main class but it should not be required as I don't use transactions outside of repositories) :
#Transactional
#Component("courseMgr")
public class CourseManager {
#Autowired
CourseRepository courseRepository;
public void saveOrUpdate(Course course) {
courseRepository.save(course);
}
public void delete(Course course) {
courseRepository.delete(course);
}
}
public interface CourseRepository extends CrudRepository<Course, Long> {
...
}
EDIT2 :
I think I have found a pretty simple solution :
I have removed the #PreRemove method from the entity, then instead of removing the references like this in the deleteRegistration method (which I had tried but was causing failed to lazily initialize a collection of role exception) :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
I simply set parents to null, I don't care as it will be deleted...
#Command
public void deleteRegistration(Registration reg) {
reg.setCourse(null);
reg.setParticipant(null);
registrationMgr.delete(reg);
}
So now I can also delete a course without triggering the ConcurrentModificationException in the #PreRemove.
EDIT3 : My bad, registration was not removed with the solution above (still no error but nothing happens). I ended with this instead, which finally works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
You have setup your repositories incorrectly. You need a composite PK for Registration and you need to understand that bidirectional mappings are really for query only. Further, bidirectional mappings in Course and Participate present challenges because the ManyToOne relationship through the Registration entity is FetchType.EAGER by default. With all the cascade and fetch annotations you have you are asking for a complicated combination of things from JPA and it seems like you really haven't sorted it all out yet. Start with the basics, be sure to print your SQL statements, and proceed from there if you want to try to finesse more from JPA.
#Entity
#Data
public class Course {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Participant {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Registration {
#EmbeddedId
private RegistrationPK id;
#ManyToOne
#MapsId("participant_id")
private Participant participant;
#ManyToOne
#MapsId("course_id")
private Course course;
}
#Embeddable
#Data
public class RegistrationPK implements Serializable {
private static final long serialVersionUID = 1L;
private Integer course_id;
private Integer participant_id;
}
Is your basic Entities. The RegistrationRepository needs an additional query.
public interface RegistrationRepository extends JpaRepository<Registration, RegistrationPK> {
Set<Registration> findByCourse(Course c);
}
And to use all this in an example:
#Override
public void run(String... args) throws Exception {
create();
Course c = courseRepo.getOne(1);
Set<Registration> rs = read(c);
System.out.println(rs);
deleteCourse(c);
}
private void create() {
Course c1 = new Course();
c1.setId(1);
c1.setName("c1");
courseRepo.save(c1);
Participant p1 = new Participant();
p1.setId(1);
p1.setName("p1");
participantRepo.save(p1);
Registration r1 = new Registration();
r1.setId(new RegistrationPK());
r1.setCourse(c1);
r1.setParticipant(p1);
registrationRepo.save(r1);
}
private Set<Registration> read(Course c) {
return registrationRepo.findByCourse(c);
}
private void deleteCourse(Course c) {
registrationRepo.deleteAll( registrationRepo.findByCourse(c) );
courseRepo.delete(c);
}
OK solution was pretty simple.
I indeed need to remove the references from the deleteRegistration method. This is what I had tried but was causing failed to lazily initialize a collection of role exception :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
The trick is that I also have to save the course entity before trying to delete the registration.
This works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
#PreRemove was doing the job, but that way I can now also delete a course without triggering the ConcurrentModificationException.

JPA (orphanRemoval = true) Implementation

I've been reading posts about orphanRemoval= true in JPA .
According to documentation :
orphanRemoval is a flag -
Whether to apply the remove operation to entities that have been
removed from the relationship and to cascade the remove operation to
those entities.
Also I refered to this article for more info , where they have tried to set child entity (address - in their example ) as null.
I currently understand that making orphanRemoval= true will perform similar operation as cascade=CascadeType.REMOVE and if I remove my parent entity , it will delete the child entity as well .
What i want to test is the additional functionality that it brings which is removal of entities that are not referenced by their parent entity.
I am trying to create a similar scenario where I am setting the new collection of phones as new ArrayList<>() where the parent entity is Person .
Following are my entity classes .
Person.java
#Entity
#Table(name = "person")
#Data
public class Person {
#Id
int pd ;
String fname;
String lname;
#OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
List<Phone> phones = new ArrayList<>() ;
public boolean addPhone(Phone phone) {
boolean added = this.phones.add(phone);
phone.setPerson(this);
return added;
}
}
Phone.java
#Entity
#Table(name = "phone")
#Data
public class Phone {
private int countryCode;
#Id
private String number ;
#ManyToOne
#JoinColumn(name="fk_person")
Person person ;
}
main class
public void testFlow() {
Person p = fetchById(765);
p.setPhones(new ArrayList<>());
personRepo.save(p); **// exception on this line**
getPersons();
}
public Person fetchById(int id) {
Optional<Person> pe = personRepo.findById(id);
Person person = pe.get();
System.out.println("person is :"+ person.getFname());
System.out.println("cc is :"+ person.getPhones().get(0).getNumber());
return person;
}
public List<Person> getPersons() {
List<Person> persons = personRepo.findAll();
persons.forEach(p -> {
System.out.println("person :"+p.getPd());
System.out.println("person phones :"+p.getPhones().get(0).getNumber());
System.out.println("=================================");
});
return persons;
}
The entry method is testFlow() .
When I execute this code , I get error :
org.hibernate.HibernateException: A collection with
cascade="all-delete-orphan" was no longer referenced by the owning
entity instance: com.example.entity.Person.phones
Any clue how i can test the working example of orphanRemoval ?
The problem is caused by the following line:
p.setPhones(new ArrayList<>());
In Hibernate, you cannot overwrite a collection retrieved from the persistence context if the association has orphanRemoval = true specified. If your goal is to end up with an empty collection, use p.getPhones().clear() instead.
This is the line the exception should be thrown:
personRepo.save(p);
It happens, because you are trying to save Person that doesn't reference any Phones. I means, that you're dereferencing only Person but not the Phone entities. Since it's a bidirectional relationship, you would have to dereference both:
public void testFlow() {
Person p = fetchById(765);
p.getPhones().foreach(ph -> ph.setPerson(null));
p.setPhones(new ArrayList<>());
personRepo.save(p); **// exception on this line**
getPersons();
}

How to add existing value in many to many relationship spring boot [duplicate]

I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.
Here's a snippet of the code:
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
How can I correctly save a Transaction, associated with an already persisted Account object?
The solution is simple, just use the CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL.
I have had the same problem and CascadeType.MERGE has worked for me.
I hope you are sorted.
This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.
As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.
An example setter for the Many side is in this link.
After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.
Remove cascading from the child entity Transaction, it should be just:
#Entity class Transaction {
#ManyToOne // no cascading here!
private Account account;
}
(FetchType.EAGER can be removed as well as it's the default for #ManyToOne)
That's all!
Why? By saying "cascade ALL" on the child entity Transaction you require that every DB operation gets propagated to the parent entity Account. If you then do persist(transaction), persist(account) will be invoked as well.
But only transient (new) entities may be passed to persist (Transaction in this case). The detached (or other non-transient state) ones may not (Account in this case, as it's already in DB).
Therefore you get the exception "detached entity passed to persist". The Account entity is meant! Not the Transaction you call persist on.
You generally don't want to propagate from child to parent. Unfortunately there are many code examples in books (even in good ones) and through the net, which do exactly that. I don't know, why... Perhaps sometimes simply copied over and over without much thinking...
Guess what happens if you call remove(transaction) still having "cascade ALL" in that #ManyToOne? The account (btw, with all other transactions!) will be deleted from the DB as well. But that wasn't your intention, was it?
Don't pass id(pk) to persist method or try save() method instead of persist().
Removing child association cascading
So, you need to remove the #CascadeType.ALL from the #ManyToOne association. Child entities should not cascade to parent associations. Only parent entities should cascade to child entities.
#ManyToOne(fetch= FetchType.LAZY)
Notice that I set the fetch attribute to FetchType.LAZY because eager fetching is very bad for performance.
Setting both sides of the association
Whenever you have a bidirectional association, you need to synchronize both sides using addChild and removeChild methods in the parent entity:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}
Using merge is risky and tricky, so it's a dirty workaround in your case. You need to remember at least that when you pass an entity object to merge, it stops being attached to the transaction and instead a new, now-attached entity is returned. This means that if anyone has the old entity object still in their possession, changes to it are silently ignored and thrown away on commit.
You are not showing the complete code here, so I cannot double-check your transaction pattern. One way to get to a situation like this is if you don't have a transaction active when executing the merge and persist. In that case persistence provider is expected to open a new transaction for every JPA operation you perform and immediately commit and close it before the call returns. If this is the case, the merge would be run in a first transaction and then after the merge method returns, the transaction is completed and closed and the returned entity is now detached. The persist below it would then open a second transaction, and trying to refer to an entity that is detached, giving an exception. Always wrap your code inside a transaction unless you know very well what you are doing.
Using container-managed transaction it would look something like this. Do note: this assumes the method is inside a session bean and called via Local or Remote interface.
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
...
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
entityManager.persist(account);
}
Probably in this case you obtained your account object using the merge logic, and persist is used to persist new objects and it will complain if the hierarchy is having an already persisted object. You should use saveOrUpdate in such cases, instead of persist.
My Spring Data JPA-based answer: I simply added a #Transactional annotation to my outer method.
Why it works
The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.
Reference:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
An old question, but came across the same issue recently . Sharing my experience here.
Entity
#Data
#Entity
#Table(name = "COURSE")
public class Course {
#Id
#GeneratedValue
private Long id;
}
Saving the entity (JUnit)
Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Fix
Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Conclusion : If the entity class has #GeneratedValue for primary key (id), then ensure that you are not passing a value for the primary key (id)
If nothing helps and you are still getting this exception, review your equals() methods - and don't include child collection in it. Especially if you have deep structure of embedded collections (e.g. A contains Bs, B contains Cs, etc.).
In example of Account -> Transactions:
public class Account {
private Long id;
private String accountName;
private Set<Transaction> transactions;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Account))
return false;
Account other = (Account) obj;
return Objects.equals(this.id, other.id)
&& Objects.equals(this.accountName, other.accountName)
&& Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
}
}
In above example remove transactions from equals() checks. This is because hibernate will imply that you are not trying to update old object, but you pass a new object to persist, whenever you change element on the child collection.
Of course this solutions will not fit all applications and you should carefully design what you want to include in the equals and hashCode methods.
In your entity definition, you're not specifying the #JoinColumn for the Account joined to a Transaction. You'll want something like this:
#Entity
public class Transaction {
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
#JoinColumn(name = "accountId", referencedColumnName = "id")
private Account fromAccount;
}
EDIT: Well, I guess that would be useful if you were using the #Table annotation on your class. Heh. :)
Even if your annotations are declared correctly to properly manage the one-to-many relationship you may still encounter this precise exception. When adding a new child object, Transaction, to an attached data model you'll need to manage the primary key value - unless you're not supposed to. If you supply a primary key value for a child entity declared as follows before calling persist(T), you'll encounter this exception.
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
In this case, the annotations are declaring that the database will manage the generation of the entity's primary key values upon insertion. Providing one yourself (such as through the Id's setter) causes this exception.
Alternatively, but effectively the same, this annotation declaration results in the same exception:
#Entity
public class Transaction {
#Id
#org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
#GeneratedValue(generator="system-uuid")
private Long id;
....
So, don't set the id value in your application code when it's already being managed.
Here is my fix.
Below is my Entity. Mark that the id is annotated with #GeneratedValue(strategy = GenerationType.AUTO), which means that the id would be generated by the Hibernate. Don't set it when entity object is created. As that will be auto generated by the Hibernate.
Mind you if the entity id field is not marked with #GeneratedValue then not assigning the id a value manually is also a crime, which will be greeted with IdentifierGenerationException: ids for this class must be manually assigned before calling save()
#Entity
#Data
#NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String key;
#Column
private String value;
}
And here is my main class.
public class SimpleObjectMain {
public static void main(String[] args) {
System.out.println("Hello Hello From SimpleObjectMain");
SimpleObject simpleObject = new SimpleObject();
simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
simpleObject.setKey("Friend");
simpleObject.setValue("Bani");
EntityManager entityManager = EntityManagerUtil.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(simpleObject);
entityManager.getTransaction().commit();
List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
for(SimpleObject simple : simpleObjectList){
System.out.println(simple);
}
entityManager.close();
}
}
When I tried saving that, it was throwing that
PersistentObjectException: detached entity passed to persist.
All I needed to fix was remove that id setting line for the simpleObject in the main method.
Maybe It is OpenJPA's bug, When rollback it reset the #Version field, but the pcVersionInit keep true. I have a AbstraceEntity which declared the #Version field. I can workaround it by reset the pcVersionInit field. But It is not a good idea. I think it not work when have cascade persist entity.
private static Field PC_VERSION_INIT = null;
static {
try {
PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
PC_VERSION_INIT.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
}
}
public T call(final EntityManager em) {
if (PC_VERSION_INIT != null && isDetached(entity)) {
try {
PC_VERSION_INIT.set(entity, false);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
em.persist(entity);
return entity;
}
/**
* #param entity
* #param detached
* #return
*/
private boolean isDetached(final Object entity) {
if (entity instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable) entity;
if (pc.pcIsDetached() == Boolean.TRUE) {
return true;
}
}
return false;
}
You need to set Transaction for every Account.
foreach(Account account : accounts){
account.setTransaction(transactionObj);
}
Or it colud be enough (if appropriate) to set ids to null on many side.
// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
foreach(Account account : accounts){
account.setId(null);
}
transactionObj.setAccounts(accounts);
// just persist transactionObj using EntityManager merge() method.
cascadeType.MERGE,fetch= FetchType.LAZY
Resolved by saving dependent object before the next.
This was happened to me because I was not setting Id (which was not auto generated). and trying to save with relation #ManytoOne
#OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
worked for me.
In my case I was committing transaction when persist method was used.
On changing persist to save method , it got resolved.
If above solutions not work just one time comment the getter and setter methods of entity class and do not set the value of id.(Primary key)
Then this will work.
Another reason I have encountered this issue is having Entities that aren't versioned by Hibernate in a transaction.
Add a #Version annotation to all mapped entities
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "orders")
private CustomerOrders orders;
}
#Entity
public class CustomerOrders {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
private BigDecimal value;
}
This error comes from the JPA Lifecycle.
To solve, no need to use specific decorator. Just join the entity using merge like that :
entityManager.merge(transaction);
And don't forget to correctly set up your getter and setter so your both side are sync.
So I stumbled across this Question and Answers because I got the same Error but a very basic object with just Strings and Integers.
But in my case I was trying to set a Value to a Field which was annotated with #Id.
So if you are using #Id it seems that you can't create a new Object on a Class and set an Id by yourself and persist it to Database. You should then leave the Id blank. I wasn't aware and maybe this helps anyone else.
The problem here is lack of control.
When we use the CrudRepository/JPARepository save method we loose the transactional control.
To overcome this issue we have Transaction Management
I prefer the #Transactional mechanism
imports
import javax.transaction.Transactional;
Entire Source Code:
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
#Data
#ToString(exclude = {"employee"})
#EqualsAndHashCode(exclude = {"employee"})
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
#SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
#Setter(AccessLevel.NONE)
#Column(name = "project_id",updatable = false,nullable = false)
private Integer pId;
#Column(name="project_name",nullable = false,updatable = true)
private String projectName;
#Column(name="team_size",nullable = true,updatable = true)
private Integer teamSize;
#Column(name="start_date")
private Date startDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projectemp_join_table",
joinColumns = {#JoinColumn(name = "project_id")},
inverseJoinColumns = {#JoinColumn(name="emp_id")}
)
private List<Employee> employees;
}
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Entity
#Data
#EqualsAndHashCode(exclude = {"projects"})
#ToString(exclude = {"projects"})
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
#SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
#Setter(AccessLevel.NONE)
#Column(name = "emp_id",nullable = false,updatable = false)
private Integer eId;
#Column(name="fist_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#ManyToMany(mappedBy = "employees")
private List<Project> projects;
}
package com.oracle.repo;
import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
package com.oracle.repo;
import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRepo extends JpaRepository<Project,Integer> {
}
package com.oracle.services;
import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
#Component
public class DBServices {
#Autowired
private ProjectRepo repo;
#Transactional
public void performActivity(){
Project p1 = new Project();
p1.setProjectName("Bank 2");
p1.setTeamSize(20);
p1.setStartDate(new Date(2020, 12, 22));
Project p2 = new Project();
p2.setProjectName("Bank 1");
p2.setTeamSize(21);
p2.setStartDate(new Date(2020, 12, 22));
Project p3 = new Project();
p3.setProjectName("Customs");
p3.setTeamSize(11);
p3.setStartDate(new Date(2010, 11, 20));
Employee e1 = new Employee();
e1.setFirstName("Pratik");
e1.setLastName("Gaurav");
Employee e2 = new Employee();
e2.setFirstName("Ankita");
e2.setLastName("Noopur");
Employee e3 = new Employee();
e3.setFirstName("Rudra");
e3.setLastName("Narayan");
List<Employee> empList1 = new LinkedList<Employee>();
empList1.add(e2);
empList1.add(e3);
List<Employee> empList2 = new LinkedList<Employee>();
empList2.add(e1);
empList2.add(e2);
List<Project> pl1=new LinkedList<Project>();
pl1.add(p1);
pl1.add(p2);
List<Project> pl2=new LinkedList<Project>();
pl2.add(p2);pl2.add(p3);
p1.setEmployees(empList1);
p2.setEmployees(empList2);
e1.setProjects(pl1);
e2.setProjects(pl2);
repo.save(p1);
repo.save(p2);
repo.save(p3);
}
}

JPA Inheritance issue

Working with JPA 1 (hibernate-core version 3.3.0.SP1 and hibernate-entitymanager version 3.4.0.GA) :
I've some entities similar to those defined below, where ChildOne and ChildTwo extends from the Father entity.
#Entity
#Table(name = "TABLE_FATHER")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.INTEGER, name = Father.C_ID_CTG)
public class Father {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sq")
#Column(name = "ID_PK", nullable = false)
#BusinessId
private Long id;
...
}
#Entity
#Table(name = "TABLE_CHILD_ONE")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorValue(Categories.ID_CTG_ONE)
public class ChildOne extends Father {
...
}
#Entity
#Table(name = "TABLE_CHILD_TWO")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorValue(Categories.ID_CTG_TWO)
public class ChildTwo extends Element {
...
}
Let's say I've one entity having a Father element, and another having a collection of father elements. In both cases, should go the children entities.
#Entity
#Table(name = "TABLE_ONE")
public class OneTable {
#JoinColumn(name = "ID_PK", referencedColumnName = "ID_PK", nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Father element;
...
}
#Entity
#Table(name = "TABLE_ANOTHER")
public class Another {
#Fetch(FetchMode.JOIN)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "id", fetch = FetchType.LAZY)
private Collection<Father> elementCollection;
...
}
I'm expecting to obtain always the children elements but when I get the element getElement() returns the father element
and, on the other hand, when I get the collection getElementCollection() the children elements are coming.
Apparently, the #JoinColumn is the cause of this behaviour, doing the join with the father table and forgetting the children elements.
The collection is working as expected.
How could I get the children element with a getElement() call? Any ideas or workarround?
Thanks in advance.
The problem is not caused by #JoinColumn.
The reason is Lazy Loading.
I manage to pinpoint your problem in simpler example.
Forgive me for changing convention from Father to Parent.
In the example below, uninitialized Element is type of jpa.inheritance.issue.Parent_$$_javassist_1. It is a Hibernate Proxy - dynamically created subclass of Parent.
You can "unproxy" it by invoking Hibernate proprietary API getHibernateLazyInitializer().getImplementation().
Collection of elementCollection is also Lazy Initialized. The type of the collection is org.hibernate.collection.PersistentBag which is being initilized with correct data at the time of first access.
Collection is initialized all at once.
Please see the test which successfully passed green with your exact version of Hibernate (3.3.0.SP1/3.4.0.GA).
#Test
public void test() {
Child c = new Child();
em.persist(c);
Another a = new Another();
a.setElement(c);
Collection<Parent> col = new ArrayList<Parent>();
col.add(c);
a.setElementCollection(col);
em.persist(a);
c.setAnother(a);
long idx = a.getId();
tx.commit();
// I'm cleaning the cache to be sure that call to a.getElement() will return proxy.
em.clear();
tx = em.getTransaction();
tx.begin();
a = em.find(Another.class, idx);
Assert.assertNotNull(a);
Parent p = a.getElement();
// At this point p is a type of jpa.inheritance.issue.Parent_$$_javassist_1
Assert.assertTrue(p instanceof Parent);
Assert.assertFalse(p instanceof Child);
// At this point a.elements is a not initialized (empty) collection of type org.hibernate.collection.PersistentBag
// When we access this collection for the first time, records are read from the database
Assert.assertEquals(1, a.getElementCollection().size());
if (p instanceof HibernateProxy) {
p =
(Parent) ((HibernateProxy) p).getHibernateLazyInitializer()
.getImplementation();
}
// At this point p is a type of jpa.inheritance.issue.Child
Assert.assertTrue(p instanceof Child);
}
#Entity
public class Another {
#JoinColumn(name = "element_id", referencedColumnName = "id", nullable = false)
#ManyToOne(fetch=FetchType.LAZY)
private Parent element;
public Parent getElement() {
return element;
}
public void setElement(Parent element) {
this.element = element;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "another", fetch = FetchType.LAZY)
public Collection<Parent> elements;
public Collection<Parent> getElementCollection() {
return elements;
}
public void setElementCollection(Collection<Parent> elementCollection) {
this.elements = elementCollection;
}
// #Id ...
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Parent {
#ManyToOne
private Another another;
public Another getAnother() {
return another;
}
public void setAnother(Another another) {
this.another = another;
}
// #Id ...
}
#Entity
public class Child extends Parent {
}
You don't need #DiscriminatorColumn nor #DiscriminatorValue because those annotations are needed with InheritanceType.SINGLE_TABLE as an only resort to determine the type.
With InheritanceType.JOINED Hibernate is able to determine polymorphic type by checking if there is a record in both (Parent and Child) tables with the same Id.
You can turn on hibernate logging to see how the query to determine the type looks like. It works like this:
select
another0_.id as id0_1_,
another0_.element_id as element2_0_1_,
parent1_.id as id1_0_,
parent1_1_.name as name2_0_,
case
when parent1_1_.id is not null then 1
when parent1_.id is not null then 0
else -1
end as clazz_0_
from
Another another0_
inner join
Parent parent1_
on another0_.element_id=parent1_.id
left outer join
Child parent1_1_
on parent1_.id=parent1_1_.id
where
another0_.id=?

JPA: How to have one-to-many relation of the same Entity type

There's an Entity Class "A". Class A might have children of the same type "A". Also "A" should hold it's parent if it is a child.
Is this possible? If so how should I map the relations in the Entity class?
["A" has an id column.]
Yes, this is possible. This is a special case of the standard bidirectional #ManyToOne/#OneToMany relationship. It is special because the entity on each end of the relationship is the same. The general case is detailed in Section 2.10.2 of the JPA 2.0 spec.
Here's a worked example. First, the entity class A:
#Entity
public class A implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne
private A parent;
#OneToMany(mappedBy="parent")
private Collection<A> children;
// Getters, Setters, serialVersionUID, etc...
}
Here's a rough main() method that persists three such entities:
public static void main(String[] args) {
EntityManager em = ... // from EntityManagerFactory, injection, etc.
em.getTransaction().begin();
A parent = new A();
A son = new A();
A daughter = new A();
son.setParent(parent);
daughter.setParent(parent);
parent.setChildren(Arrays.asList(son, daughter));
em.persist(parent);
em.persist(son);
em.persist(daughter);
em.getTransaction().commit();
}
In this case, all three entity instances must be persisted before transaction commit. If I fail to persist one of the entities in the graph of parent-child relationships, then an exception is thrown on commit(). On Eclipselink, this is a RollbackException detailing the inconsistency.
This behavior is configurable through the cascade attribute on A's #OneToMany and #ManyToOne annotations. For instance, if I set cascade=CascadeType.ALL on both of those annotations, I could safely persist one of the entities and ignore the others. Say I persisted parent in my transaction. The JPA implementation traverses parent's children property because it is marked with CascadeType.ALL. The JPA implementation finds son and daughter there. It then persists both children on my behalf, even though I didn't explicitly request it.
One more note. It is always the programmer's responsibility to update both sides of a bidirectional relationship. In other words, whenever I add a child to some parent, I must update the child's parent property accordingly. Updating only one side of a bidirectional relationship is an error under JPA. Always update both sides of the relationship. This is written unambiguously on page 42 of the JPA 2.0 spec:
Note that it is the application that bears responsibility for maintaining the consistency of runtime relationships—for example, for insuring that the “one” and the “many” sides of a bidirectional relationship are consistent with one another when the application updates the
relationship at runtime.
For me the trick was to use many-to-many relationship. Suppose that your entity A is a division that can have sub-divisions. Then (skipping irrelevant details):
#Entity
#Table(name = "DIVISION")
#EntityListeners( { HierarchyListener.class })
public class Division implements IHierarchyElement {
private Long id;
#Id
#Column(name = "DIV_ID")
public Long getId() {
return id;
}
...
private Division parent;
private List<Division> subDivisions = new ArrayList<Division>();
...
#ManyToOne
#JoinColumn(name = "DIV_PARENT_ID")
public Division getParent() {
return parent;
}
#ManyToMany
#JoinTable(name = "DIVISION", joinColumns = { #JoinColumn(name = "DIV_PARENT_ID") }, inverseJoinColumns = { #JoinColumn(name = "DIV_ID") })
public List<Division> getSubDivisions() {
return subDivisions;
}
...
}
Since I had some extensive business logic around hierarchical structure and JPA (based on relational model) is very weak to support it I introduced interface IHierarchyElement and entity listener HierarchyListener:
public interface IHierarchyElement {
public String getNodeId();
public IHierarchyElement getParent();
public Short getLevel();
public void setLevel(Short level);
public IHierarchyElement getTop();
public void setTop(IHierarchyElement top);
public String getTreePath();
public void setTreePath(String theTreePath);
}
public class HierarchyListener {
#PrePersist
#PreUpdate
public void setHierarchyAttributes(IHierarchyElement entity) {
final IHierarchyElement parent = entity.getParent();
// set level
if (parent == null) {
entity.setLevel((short) 0);
} else {
if (parent.getLevel() == null) {
throw new PersistenceException("Parent entity must have level defined");
}
if (parent.getLevel() == Short.MAX_VALUE) {
throw new PersistenceException("Maximum number of hierarchy levels reached - please restrict use of parent/level relationship for "
+ entity.getClass());
}
entity.setLevel(Short.valueOf((short) (parent.getLevel().intValue() + 1)));
}
// set top
if (parent == null) {
entity.setTop(entity);
} else {
if (parent.getTop() == null) {
throw new PersistenceException("Parent entity must have top defined");
}
entity.setTop(parent.getTop());
}
// set tree path
try {
if (parent != null) {
String parentTreePath = StringUtils.isNotBlank(parent.getTreePath()) ? parent.getTreePath() : "";
entity.setTreePath(parentTreePath + parent.getNodeId() + ".");
} else {
entity.setTreePath(null);
}
} catch (UnsupportedOperationException uoe) {
LOGGER.warn(uoe);
}
}
}

Categories