How the Cascade functionality works? How should a developer use the OrphanRemoval? Handling  the org.hibernate.TransientObjectException
It is very common two or more entities to receive updates in the same transaction. When editing a person's data for
example, we could change their name, address, age, car color etc. These changes should trigger updates to three
different entities: Person, Car and Address.
The updates above could be done as shown in the code snippet below:
car.setColor(Color.RED);
car.setOwner(newPerson);
car.setSoundSystem(newSound);
If the code below were to be executed the exception org.hibernate.TransientObjectException would be thrown:
EntityManager entityManager = // get a valid entity manager
Car car = new Car();
car.setName("Black Thunder");
Address address = new Address();
address.setName("Street A");
entityManager.getTransaction().begin();
Person person = entityManager.find(Person.class, 33);
person.setCar(car);
person.setAddress(address);
entityManager.getTransaction().commit();
entityManager.close();
With the EclipseLink JPA implementation the following message would be fired: “Caused by:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked
cascade PERSIST”.
What means that an entity is transient? Or what a relationship not marked with cascade persist is all about?
JPA works as a tracker for every entity that participates in a transaction. An entity that participates in a transaction is
an entity that will be created, updated, deleted. JPA needs to know where that entity came from and where it is
going to. When a transaction is opened every entity brought from the database is “attached”. With “attached” we
mean that the entity is inside a transaction, being monitored by JPA. An entity remains attached until the transaction
is closed (rules for JSE applications or transactions without EJB Stateful Session Beans with Persistence Scope
Extended are different); to be attached an entity needs to come from a database (by query, entity manager find
method…), or receive some contact inside the transaction (merge, refresh).
Notice the code below:
entityManager.getTransaction().begin();
Car myCar = entityManager.find(Car.class, 33);
myCar.setColor(Color.RED);
entityManager. getTransaction().commit();
The transaction is opened, an update is made in the entity and the transaction is committed. It was not required to
do an explicit update to the entity, with the transaction committing all updates made to the entity will be persisted to
the database. The updates made to the Car entity were persisted to the database because the entity is attached,
any update to an attached entity will be persisted in the database after the transaction commits() or a flush call is
made.
As shown In the code snippet above "myCar" entity was brought from the database inside a transaction, thus JPA
will have the "myCar" entity attached into that Persistence Context. It is possible to define a Persistence Context as
a place where JPA will put all attached entities to that transaction, or as a big bag.
the Car entity is added to the Persistence Context only after a database query is
performed to retrieve it from the database. Every update in the Car entity will be monitored by JPA. Once the
transaction is finished (or the flush command invoked), JPA will persist this changes to the database.
The aforementioned problem occures when we update a relationship between two entities. Check the code and the
image below:
entityManager.getTransaction().begin();
Person newPerson = new Person();
newPerson.setName("Mary");
Car myCar = entityManager.find(Car.class, 33);
myCar.setOwner(newPerson);
entityManager. getTransaction().commit();
The Car entity establishes a relationship with the Person entity. The problem is that the Person entity is outside of
The Persistence Context, notice that the person entity was not brought from the database nor was attached to the
transaction. Upon commit JPA cannot recognize that Person is a new entity, which does not exist in the database.
Even if the Person entity existed in the database, since it came from outside a transaction (e.g. a JSF
ManagedBean, Struts Action) it will be considerate unmanagable in this Persistence Context. An object outside the
Persistence Context is known as “detached”.
This detached entity situation may happen in any JPA operations: INSERT, UPDATE, DELETE…
To help in these situations JPA created the option Cascade. This option can be defined in the annotations:
@OneToOne, @OneToMany and @ManyToMany. The enum javax.persistence.CascadeType has all Cascade options available.
Check the cascade options below:
•CascadeType.DETACH
•CascadeType.MERGE
•CascadeType.PERSIST
•CascadeType.REFRESH
•CascadeType.REMOVE
•CascadeType.ALL
The Cascade applies the behavior to repeat the action defined in the relationship. See the code below:
import javax.persistence.*;
@Entity
public class Car {
@Id
@GeneratedValue
private int id;
private String name;
@OneToOne(cascade = CascadeType.PERSIST)
private Person person;
// get and set
}
In the persistence code above it is defined that the @OneToOne Person relationship will have the
Cascade.PERSIST action executed every time the command entityManager.persist(car) is executed; thus for every
persist action invoked in a Car entity JPA will invoke persist in the Person relationship also.
The Cascade advantage is that the propagation of an action is automatic, just need to configure it in a relationship.
Once the cascade is defined, the code below should run without error:
entityManager.getTransaction().begin();
Person newPerson = new Person();
newPerson.setName("Mary");
Car myCar = entityManager.find(Car.class, 33);
myCar.setOwner(newPerson);
entityManager. getTransaction().commit();
Observations about Cascade:
•A developer must be cautious when using CascadeType.ALL in a relationship. When the entity is
deleted its relationship would be deleted as well. In the sample code above, if the cascade type
in the Car entity was set to the ALL option, when a Car entity was deleted from the database the
Person entity would be deleted also.
•CascadeType.ALL (or individual cascades) can cause low performance in every action triggered
in the entity. If for example an entity has a lot of lists of referenced entities a merge() action
invoked on the specific entity could cause all lists to be merged too.
•car.setOwner(personFromDB) => if the "personFromDB" entity exists in the DB but is detached
for the persistence context, Cascade will not help. When the command
entityManager.persist(car) is executed JPA will run the persist command for every relationship
defined with CascadeType.PERSIST (e.g. entityManager.persist(person)). If the Person entity
already exists in the database JPA will try to insert the same record again and an error message
will be thrown. In this case only “attached” entities can be used, the best way to do it is by using
the getReference() method that we present here in more detail.
In order for JPA to trigger the cascade it is necessary always to execute the action in the entity that has the cascade
option defined. Check the code below:
import javax.persistence.*;
@Entity
public class Car {
@Id
@GeneratedValue
private int id;
private String name;
@OneToOne(cascade = CascadeType.PERSIST)
private Person person;
// get and set
}
import javax.persistence.*;
@Entity
public class Person {
@Id
private int id;
private String name;
@OneToOne(mappedBy="person")
private Car car;
// get and set
}
The correct way to trigger the cascade in the entities above is:
entityManager.persist(car);
JPA will search inside the Car entity if there is a Cascade option that should be triggered. If the persist was
executed like below the transient error message would be thrown:
entityManager.persist(person);
Remember: the cascade will only be triggered by JPA when the action is executed in the entity that has the Cascade configured,
in the sample above the Car class defined the Cascade to Person; only the Car class will trigger the Cascade.
OrphanRemoval
The OrphanRemoval option works almost like the CascadeType.REMOVE. The OrphanRemoval is usually applied
in cases where an entity just exists inside another entity.
Imagine a situation where an Address entity will only exist inside a Person entity:
If a new Person entity is persisted to the database an Address entity will be created too. Conceptually the Address
entity is created only when a new Person entity is created and when the Person entity is deleted the Address entity
is deleted also, just like by using the CascadeType.REMOVE option.
The OrphanRemoval has almost the same functionality as the CascadeType.REMOVE, but conceptually it must be
applied at class composition level.
Look at the code below:
import javax.persistence.*;
@Entity
public class Address {
@Id
@GeneratedValue
private int id;
private String name;
// get and set
}
import javax.persistence.*;
@Entity
public class Person {
@Id
private int id;
private String name;
@OneToMany(orphanRemoval=true)
private List<Address> address;
// get and set
}
Imagine that a specific Address entity is assigned to a specific Person entity and only that Person entity has access
to it.
As stated above OrphanRemoval is almost like CascadeType.REMOVE. The difference is that the dependent
entity will be deleted from the database if the attribute is set to null as showin at the code snippet below:
person.setAddress(null);
When the Person entity gets updated in the database, the Address entity will be deleted from the database. The
person.setAddress(null) would cause the Address entity to be orphan.
The OrphanRemoval option is only available in the annotations: @OneToOne and @OneToMany.
It is very common two or more entities to receive updates in the same transaction. When editing a person's data for
example, we could change their name, address, age, car color etc. These changes should trigger updates to three
different entities: Person, Car and Address.
The updates above could be done as shown in the code snippet below:
car.setColor(Color.RED);
car.setOwner(newPerson);
car.setSoundSystem(newSound);
If the code below were to be executed the exception org.hibernate.TransientObjectException would be thrown:
EntityManager entityManager = // get a valid entity manager
Car car = new Car();
car.setName("Black Thunder");
Address address = new Address();
address.setName("Street A");
entityManager.getTransaction().begin();
Person person = entityManager.find(Person.class, 33);
person.setCar(car);
person.setAddress(address);
entityManager.getTransaction().commit();
entityManager.close();
With the EclipseLink JPA implementation the following message would be fired: “Caused by:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked
cascade PERSIST”.
What means that an entity is transient? Or what a relationship not marked with cascade persist is all about?
JPA works as a tracker for every entity that participates in a transaction. An entity that participates in a transaction is
an entity that will be created, updated, deleted. JPA needs to know where that entity came from and where it is
going to. When a transaction is opened every entity brought from the database is “attached”. With “attached” we
mean that the entity is inside a transaction, being monitored by JPA. An entity remains attached until the transaction
is closed (rules for JSE applications or transactions without EJB Stateful Session Beans with Persistence Scope
Extended are different); to be attached an entity needs to come from a database (by query, entity manager find
method…), or receive some contact inside the transaction (merge, refresh).
Notice the code below:
entityManager.getTransaction().begin();
Car myCar = entityManager.find(Car.class, 33);
myCar.setColor(Color.RED);
entityManager. getTransaction().commit();
The transaction is opened, an update is made in the entity and the transaction is committed. It was not required to
do an explicit update to the entity, with the transaction committing all updates made to the entity will be persisted to
the database. The updates made to the Car entity were persisted to the database because the entity is attached,
any update to an attached entity will be persisted in the database after the transaction commits() or a flush call is
made.
As shown In the code snippet above "myCar" entity was brought from the database inside a transaction, thus JPA
will have the "myCar" entity attached into that Persistence Context. It is possible to define a Persistence Context as
a place where JPA will put all attached entities to that transaction, or as a big bag.
the Car entity is added to the Persistence Context only after a database query is
performed to retrieve it from the database. Every update in the Car entity will be monitored by JPA. Once the
transaction is finished (or the flush command invoked), JPA will persist this changes to the database.
The aforementioned problem occures when we update a relationship between two entities. Check the code and the
image below:
entityManager.getTransaction().begin();
Person newPerson = new Person();
newPerson.setName("Mary");
Car myCar = entityManager.find(Car.class, 33);
myCar.setOwner(newPerson);
entityManager. getTransaction().commit();
The Car entity establishes a relationship with the Person entity. The problem is that the Person entity is outside of
The Persistence Context, notice that the person entity was not brought from the database nor was attached to the
transaction. Upon commit JPA cannot recognize that Person is a new entity, which does not exist in the database.
Even if the Person entity existed in the database, since it came from outside a transaction (e.g. a JSF
ManagedBean, Struts Action) it will be considerate unmanagable in this Persistence Context. An object outside the
Persistence Context is known as “detached”.
This detached entity situation may happen in any JPA operations: INSERT, UPDATE, DELETE…
To help in these situations JPA created the option Cascade. This option can be defined in the annotations:
@OneToOne, @OneToMany and @ManyToMany. The enum javax.persistence.CascadeType has all Cascade options available.
Check the cascade options below:
•CascadeType.DETACH
•CascadeType.MERGE
•CascadeType.PERSIST
•CascadeType.REFRESH
•CascadeType.REMOVE
•CascadeType.ALL
The Cascade applies the behavior to repeat the action defined in the relationship. See the code below:
import javax.persistence.*;
@Entity
public class Car {
@Id
@GeneratedValue
private int id;
private String name;
@OneToOne(cascade = CascadeType.PERSIST)
private Person person;
// get and set
}
In the persistence code above it is defined that the @OneToOne Person relationship will have the
Cascade.PERSIST action executed every time the command entityManager.persist(car) is executed; thus for every
persist action invoked in a Car entity JPA will invoke persist in the Person relationship also.
The Cascade advantage is that the propagation of an action is automatic, just need to configure it in a relationship.
Once the cascade is defined, the code below should run without error:
entityManager.getTransaction().begin();
Person newPerson = new Person();
newPerson.setName("Mary");
Car myCar = entityManager.find(Car.class, 33);
myCar.setOwner(newPerson);
entityManager. getTransaction().commit();
Observations about Cascade:
•A developer must be cautious when using CascadeType.ALL in a relationship. When the entity is
deleted its relationship would be deleted as well. In the sample code above, if the cascade type
in the Car entity was set to the ALL option, when a Car entity was deleted from the database the
Person entity would be deleted also.
•CascadeType.ALL (or individual cascades) can cause low performance in every action triggered
in the entity. If for example an entity has a lot of lists of referenced entities a merge() action
invoked on the specific entity could cause all lists to be merged too.
•car.setOwner(personFromDB) => if the "personFromDB" entity exists in the DB but is detached
for the persistence context, Cascade will not help. When the command
entityManager.persist(car) is executed JPA will run the persist command for every relationship
defined with CascadeType.PERSIST (e.g. entityManager.persist(person)). If the Person entity
already exists in the database JPA will try to insert the same record again and an error message
will be thrown. In this case only “attached” entities can be used, the best way to do it is by using
the getReference() method that we present here in more detail.
In order for JPA to trigger the cascade it is necessary always to execute the action in the entity that has the cascade
option defined. Check the code below:
import javax.persistence.*;
@Entity
public class Car {
@Id
@GeneratedValue
private int id;
private String name;
@OneToOne(cascade = CascadeType.PERSIST)
private Person person;
// get and set
}
import javax.persistence.*;
@Entity
public class Person {
@Id
private int id;
private String name;
@OneToOne(mappedBy="person")
private Car car;
// get and set
}
The correct way to trigger the cascade in the entities above is:
entityManager.persist(car);
JPA will search inside the Car entity if there is a Cascade option that should be triggered. If the persist was
executed like below the transient error message would be thrown:
entityManager.persist(person);
Remember: the cascade will only be triggered by JPA when the action is executed in the entity that has the Cascade configured,
in the sample above the Car class defined the Cascade to Person; only the Car class will trigger the Cascade.
OrphanRemoval
The OrphanRemoval option works almost like the CascadeType.REMOVE. The OrphanRemoval is usually applied
in cases where an entity just exists inside another entity.
Imagine a situation where an Address entity will only exist inside a Person entity:
If a new Person entity is persisted to the database an Address entity will be created too. Conceptually the Address
entity is created only when a new Person entity is created and when the Person entity is deleted the Address entity
is deleted also, just like by using the CascadeType.REMOVE option.
The OrphanRemoval has almost the same functionality as the CascadeType.REMOVE, but conceptually it must be
applied at class composition level.
Look at the code below:
import javax.persistence.*;
@Entity
public class Address {
@Id
@GeneratedValue
private int id;
private String name;
// get and set
}
import javax.persistence.*;
@Entity
public class Person {
@Id
private int id;
private String name;
@OneToMany(orphanRemoval=true)
private List<Address> address;
// get and set
}
Imagine that a specific Address entity is assigned to a specific Person entity and only that Person entity has access
to it.
As stated above OrphanRemoval is almost like CascadeType.REMOVE. The difference is that the dependent
entity will be deleted from the database if the attribute is set to null as showin at the code snippet below:
person.setAddress(null);
When the Person entity gets updated in the database, the Address entity will be deleted from the database. The
person.setAddress(null) would cause the Address entity to be orphan.
The OrphanRemoval option is only available in the annotations: @OneToOne and @OneToMany.
No comments:
Post a Comment