En este tutorial se entrara mas en detalle sobre la interface EntityManager, el ciclo de vida y los distintos metodos y operaciones que se pueden hacer con la misma.
Temas a tratar:
- Application-managed entity manager VS Container-managed entity manager
- Conceptos basicos
- Creacion del EntityManager
- Persistence.xml
- Merge (distintos casos y formas de uso)
- Remove
- Contains
- Flush
- Flush Mode
- Refresh
- Clear
- Cascading (operaciones en cascada)
- Extendiendo el contexto de persistencia
- Callback methods
- postLoad
- PrePersist
- PostPersist
- PreRemove
- PostRemove
- PreUpdate
- PostUpdate
Antes que nada tenemos que tener bien en claro dos temas muy importanes que son:
- Application-managed entity manager
- Container-managed entity manager
Para las situaciones en donde no requerimos los servicios ofrecidos por un EJB3 Container (como por ejemplo JBoss ó WebLogic application server) pero querremos utilizar el modelo de persistencia de JPA, el API JPA provee el Application-managed entity manager. En estos casos las aplicaciones son denominadas aplicaciones standalone. Un ejmplo claro son las aplicaciones SWING.
Estas aplicaciones que corren fuera de un EJB3 container (porque no lo necesitan) usaran el resource-local transaction provisto por el entity manager, lo que permite que la aplicacion sea la encargada de manejar las transacciones o dicho de otra manera, la aplicacion sera la encargada de manejar el ciclo de vida del entity manager (abriendo transacciones, comiteando, haciendo rollback, etc)
Para las situaciones en donde si se requiera de los servicios de un EJB3 container (como por ejemplo JBoss ó WebLogic application server), el API JPA provee el Container-managed entity manager. En estas aplicaciones, el manejo de las transacciones es llevada a cabo por el contenedor usando Java Transaction API (JTA).
■ EntityManager- Conceptos basicos
Antes que nada la palabra EJB3 Container lo utilizamos como sinonimo de Application Server o de ejb container.
Una entidad (clase java que tenga la anotacion @Entity) pasara a ser manejada por el contexto de persistencia de JPA cuando ésta sea persistida (mediante el metodo persist() del EntityManager). En este punto, la entidad pasara a estar asociada a lo que comunmente llamamos el contexto de persistencia. En este caso, y mientras la entidad sea manejada/asociada por el contexto de persistencia (tambien se las conoce como entidades atachadas o attached entities), el estado (valores de la propiedades) de la entidad sera automaticamente sincronizado con la BD.
Por defecto, el scope o alcance del contexto de persistencia sera lo que dure la transaccion entre que se abre y hasta que se cierre. Por lo tanto, cuando la transaccion termina, el contexto de persistencia termina tambien y la entidad que estaba siendo manejada por el mismo, deja de estarlo y pasara a ser una entidad no manejada (tambien conocida como entidades desatachadas o detached entities) y no estara asociada a ningun contexto de persistencia. Cualquier cambio sobre el estado de una entidad desatachada no sera sincronizado con la BD.
Siempre que una transaccion sea iniciada, un nuevo contexto de persistencia (persistence context) es creado. Esto es asi tanto para el Application-managed entity manager como tambien para el Container-managed entity manager:
Para el caso del Application-managed entity manager (cuando no usamos un application server), la aplicacion es la encargada de abrir y cerrar la transaccion.
Para el caso del Application-managed entity manager (cuando no usamos un application server), la aplicacion es la encargada de abrir y cerrar la transaccion.
Para el caso del Container managed entity manager (cuando utilizamos un ejb container), por default la transaccion es iniciada cuando se invoque desde el cliente al EJB (a un stateless session bean, ahora bien, para los stateful session bean el comportamiento de las transacciones es distinto). La transaccion termina cuando finaliza la ejecucion del metodo del session bean. Ver http://en.wikipedia.org/wiki/Session_Beans
Para el caso de los stateful session bean y el comportamiento de las transacciones se tiene que tener en cuenta algunos tips pero lo veremos mas adelante en el apartado Extendiendo el persistence contexto
Todo esto es el comportamiento por defecto (en lo referido a transacciones, de cualquier aplicacion tanto standalone (j2se - sin ejb container) como empresarial (j2ee - con ejb container)
La tablas sobre la que vamos a ver los siguientes ejemplos son las siguiente (las mismas que venimos utilzando en los anteriores tutoriales): Ver JPA I - Manejo de relaciones donde encontraremos como estan mapeadas las entidades y sus relaciones
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 First name 1 MALE Last name 1 Damian (null) 1
2 First name 2 FEMALE Last name 2 (null) (null) 2
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 First name 4 MALE Last name 4 (null) (null) (null)
TABLA REFEREE
ID COMMENTS NAME
----- ----------- ---------
1 comments 1 Referee 1
2 comments 2 Referee 2
TABLA ACCOUNTS
ACCOUNT_TYPE ACCOUNT_ID BALANCE DATEOPENED OVERDRAFTLIMIT INTERESTRATE CUSOMTER_ID ----- ----------- ---------
1 comments 1 Referee 1
2 comments 2 Referee 2
TABLA ACCOUNTS
--------------- ------------- ---------- --------------------- ----------------- --------------- --------------
C 1 10 6/21/2011 12:00:00 AM 0 (null) 1
C 2 20 (null) 0 (null) 1
S 3 100 6/21/2011 12:00:00 AM (null) 0 1
TABLA CUSTOMER_ADDRESS
CUST_ID ADD_ID
---------- --------
1 2
1 0
TABLA ADDRESS
ADDRESS_ID ADDRESSLINE COUNTRY POSTCODE
------------- -------------- ---------- -----------
2 addressLine ARGENTINA 1428
0 addressLine2 BRASIL 1629
■ Entity Manager - Creacion del EntityManager
Si una aplicacion usa el Container-managed entity manager (es decir, requiere de los servicios que brinda un application server), el aplication server inyectara una instancia del Entity Manager. Para esto se utilizara la antoacion @PersistenceContext. Todo el overhead de crear el EntityManager, iniciar la transaccion, etc, sera llevado a cabo por el EJB3 Container. Mas adelante se entrara mas en detalle en este tipo de aplicaciones.
@PersistenceContext(unitName="examplePersistenceUnit")
private EntityManager em;
private EntityManager em;
En cambio, si una aplicacion usa el Application-managed entity manager, el EntityManager debera ser creado explicitamente mediante el EntityManagerFactory. Ademas de iniciar la transaccion mediante el EntityTransaction, comitear y cerrar tanto el EntityManager como el EntityManagerFactory. En el caso anterior, de todo esto se encargaba el application server.
"examplePersistenceUnit");
EntityManager em = emf.createEntityManager();
EntityTransaction trans = em.getTransaction();
trans.begin();
...
....
trans.commit();
em.close();
emf.close();
El persistence.xml nos quedaria asi:
<persistence>
<persistence-unit name="examplePersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- Module 1 -->
<class>module1.Referee</class>
<class>module1.Account</class>
<class>module1.Customer</class>
<class>module1.Address</class>
<class>module1.CheckingAccount</class>
<class>module1.SavingAccount</class>
<!-- Oracle DB -->
<properties>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>
<property name="hibernate.connection.url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver"/>
<property name="hibernate.connection.password" value="hibernate_tutorial"/>
<property name="hibernate.connection.username" value="hibernate_tutorial"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
<persistence-unit name="examplePersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- Module 1 -->
<class>module1.Referee</class>
<class>module1.Account</class>
<class>module1.Customer</class>
<class>module1.Address</class>
<class>module1.CheckingAccount</class>
<class>module1.SavingAccount</class>
<!-- Oracle DB -->
<properties>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>
<property name="hibernate.connection.url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver"/>
<property name="hibernate.connection.password" value="hibernate_tutorial"/>
<property name="hibernate.connection.username" value="hibernate_tutorial"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
En el persistence.xml vemos que tenemos un persistence-unit con el nombre "examplePersistenceUnit" el cual utilizamos para crear el EntityMangerFactory el cual utilizaremos para luego crear el EntityManager y un tipo de transaccion del tipo RESOURCE_LOCAL. Como dijimos al principo, el manejo del ciclo de vida del entity manager sera resuelto por la aplicacion a nivel local y no por un application server.
Luego mediante el elemento <class> definiremos todas las clases del tipo entidades (que tendran la anotacion @Entity) para que el API de JPA agregue a su set de clases manejadas por el contexto de persistencia. Y por ultimo, mas abajo, mediante el elemento <properties> definiremos la implementacion de JPA a utilizar. como asi tambien los detalles de conectividad a la bd En este ejemplo vemos que estamos utilizando Hibernate como implementacion de JPA.
■ EntityManager - Merge
Caso 1:
Para poder sincronizar el estado de una entidad desatachada con la BD necesitaremos utilizar el metodo merge() del EntityManager el cual a su vez nos devolvera una entidad atachada al contexto de persistencia.
IMPORTANTE: Siempre y cuando el ID de la entidad desatachada sea igual a alguno que ya este persistido en la BD. En caso contrario, el metodo merge() creara un nuevo registro en la bd teniendo el mismo efecto que el metodo persist() del EntityManager.
IMPORTANTE: Siempre y cuando el ID de la entidad desatachada sea igual a alguno que ya este persistido en la BD. En caso contrario, el metodo merge() creara un nuevo registro en la bd teniendo el mismo efecto que el metodo persist() del EntityManager.
em.getTransaction().begin();
Customer cust = new Customer();
cust.setId(2);
cust.setFirstName("Damian Ciocca");
Customer customerMerged = em.merge(cust);
em.getTransaction().commit();
La traza SQL es la siguiente:
3094 [main] DEBUG module1.EntityManagerTest - ----------mergeTest1------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_0_,
customer0_.FIRST_NAME as FIRST2_2_0_,
customer0_.GENDER as GENDER2_0_,
customer0_.LAST_NAME as LAST4_2_0_,
customer0_.referee_id as referee7_2_0_,
customer0_.name1 as name5_2_0_,
customer0_.name2 as name6_2_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
referee0_.id as id0_0_,
referee0_.comments as comments0_0_,
referee0_.name as name0_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
delete
from
CUSTOMER_ADDRESS
where
CUST_ID=?
Podemos ver que no solo se ejecuto el UPDATE sino que tambien se ejecuto un DELETE, debido a que el customer creado (desatachado) no se le asocio un address
Luego de ejecutar el merge, la tabla nos queda asi (ver 2do registro cuyo customer ID = 2):
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 First name 1 MALE Last name 1 Damian (null) 1
2 Damian Ciocca (null) (null) (null) (null) (null)
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 First name 4 MALE Last name 4 (null) (null) (null)
Caso 2:
Tambien se puede sincronizar una entidad atachada con la BD. En estos casos, al no crear una entidad desde cero como en el ejemplo anterior (entidad desatachada) y en su lugar, recuperala de la BD (entidad atachada), esto nos permite poder mantener el resto de las propiedades (que no se pierdan como ocurrio en el ejemplo anterior) excepto las que se modifican, como en este caso que se modifica la propiedad firstName
En estos casos y si el negocio lo permite, muchas veces se puede obviar la llamada al metodo merge() cuando estamos trabajando directamente con entidades atachadas. Al ejecutarse el comit() se debera llevar a cabo el merge de forma automatica.
em.getTransaction().begin();
Customer customerFind = em.find(Customer.class, 1);
customerFind.setFirstName("Martin Lautaro");
em.merge(customerFind);
em.getTransaction().commit();
La traza SQL es la siguiente:
2203 [main] DEBUG module1.EntityManagerTest - ----------mergeTest2------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 Martin Lautaro MALE Last name 1 Damian (null) 1
2 Damian Ciocca (null) (null) (null) (null) (null)
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 First name 4 MALE Last name 4 (null) (null) (null)
Caso 3:
Ahora vamos a ver el 3er caso en el que creamos una entidad desatachada pero le seteamos un ID que no esta en la BD, por ende, el metodo merge() actuara como el persist()
em.getTransaction().begin();
Customer cust2 = new Customer();
cust2.setId(10); // El ID 10 no existe en la tabla CUSTOMER
cust2.setFirstName("Pepe sandoval");
em.merge(cust2);
em.getTransaction().commit();
La traza SQL es la siguiente:
2734 [main] DEBUG module1.EntityManagerTest - ----------mergeTest3------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_0_,
customer0_.FIRST_NAME as FIRST2_2_0_,
customer0_.GENDER as GENDER2_0_,
customer0_.LAST_NAME as LAST4_2_0_,
customer0_.referee_id as referee7_2_0_,
customer0_.name1 as name5_2_0_,
customer0_.name2 as name6_2_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
Hibernate:
insert
into
Customer
(FIRST_NAME, GENDER, LAST_NAME, referee_id, name1, name2, CUSTOMER_ID)
values
(?, ?, ?, ?, ?, ?, ?)
Por lo que vemos que se ejecuto un INSERT en lugar de un UPDATE como vimos en los dos ejemplos anteriores.
La tabla nos quedara asi (ver ultimo registro cuyo customer ID = 10)::
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 Martin Lautaro MALE Last name 1 Damian (null) 1
2 Damian Ciocca (null) (null) (null) (null) (null)
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 First name 4 MALE Last name 4 (null) (null) (null)
10 Pepe sandoval (null) (null) (null) (null) (null)
Caso 4:
Ahora vamos a ver el 4to caso en el que creamos una entidad desatachada, le seteamos un ID que esta en la BD y no hacemos merge de forma explicita, por ende, al terminar la transaccion no se efectuara el flush de dichos cambios contra la BD ya que dicha entidad no esta atachada al contexto de persistencia.
Customer cust3 = new Customer();
cust3.setId(3);
cust3.setFirstName("Hector Gomez");
em.getTransaction().commit();
No tiene traza SQL, por ende, el resultado de la ejecucion de la consulta es lo siguiente:
2938 [main] DEBUG module1.EntityManagerTest - ----------mergeTest4------------
Caso 5:
Ahora vamos a ver el 5to caso en el que obtenemos una entidad atachada, le seteamos un nombre distinto que el que esta en la BD y no hacemos merge de forma explicita, por ende, al terminar la transaccion se efectuara el flush de dichos cambios contra la BD ya que dicha entidad esta atachada al contexto de persistencia. Con este ejemplo, vemos que el merge, al trabajar con entidades atachadas es manejado de forma automatica por JPA/Hibernate
Customer cust4 = em.find(Customer.class, 4);
cust4.setFirstName("Miguel Angel Sanchez");
em.getTransaction().commit();
La traza SQL es la siguiente:
2938 [main] DEBUG module1.EntityManagerTest - ----------mergeTest5------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 Martin Lautaro MALE Last name 1 Damian (null) 1
2 Damian Ciocca (null) (null) (null) (null) (null)
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 Miguel Angel Sanchez MALE Last name 4 (null) (null) (null)
10 Pepe sandoval (null) (null) (null) (null) (null)
El metodo remove() del EntityManager permite eliminar las entidades atachadas al contexto de persistencia (o tambien llamadas managed entities).
La eliminación se produce cuando el EntityManager decide sincronizar, o hacer flush, el contexto de persistencia con la base de datos. Tipicamente esto ocurre cuando se hace commit de la transaccion.
Caso 1:
Procedemos a eliminar el customer cuyo ID = 10 donde vemos en las tablas que no tiene relacion con otras tablas ni que otras tablas esten apuntando a dicho customer.
Caso 1:
Procedemos a eliminar el customer cuyo ID = 10 donde vemos en las tablas que no tiene relacion con otras tablas ni que otras tablas esten apuntando a dicho customer.
Customer customerFind = em.find(Customer.class, 10);
em.remove(customerFind);
em.getTransaction().commit();
La traza SQL es la siguiente:
3172 [main] DEBUG module1.EntityManagerTest - ----------removeTest1------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
delete
from
CUSTOMER_ADDRESS
where
CUST_ID=?
Hibernate:
delete
from
Customer
where
CUSTOMER_ID=?
La tabla nos quedara asi (ver que no esta mas el registro cuyo customer ID = 10)::
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 Martin Lautaro MALE Last name 1 Damian (null) 1
2 Damian Ciocca (null) (null) (null) (null) (null)
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 Miguel Angel Sanchez MALE Last name 4 (null) (null) (null)
Caso 2:
Ahora procedemos a eliminar el customer cuyo ID = 1 donde vemos en las tablas que este SI tiene relaciones con otras tablas y que otras tablas apuntan a dicho customer. Ej, la tabla Account tiene referencias al customer 1.
em.getTransaction().begin();
Customer customerFind2 = em.find(Customer.class, 1);
em.remove(customerFind2);
em.getTransaction().commit();
2829 [main] DEBUG module1.EntityManagerTest - ----------removeTest2------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
customer_.CUSTOMER_ID,
customer_.FIRST_NAME as FIRST2_2_,
customer_.GENDER as GENDER2_,
customer_.LAST_NAME as LAST4_2_,
customer_.referee_id as referee7_2_,
customer_.name1 as name5_2_,
customer_.name2 as name6_2_
from
Customer customer_
where
customer_.CUSTOMER_ID=?
Hibernate:
delete
from
CUSTOMER_ADDRESS
where
CUST_ID=?
Hibernate:
delete
from
Customer
where
CUSTOMER_ID=?
2937 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 2292, SQLState: 23000
2937 [main] ERROR org.hibernate.util.JDBCExceptionReporter - ORA-02292: integrity constraint (HIBERNATE_TUTORIAL.FK1D0C220DFA661F1F) violated - child record found
2937 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 2292, SQLState: 23000
2937 [main] ERROR org.hibernate.util.JDBCExceptionReporter - ORA-02292: integrity constraint (HIBERNATE_TUTORIAL.FK1D0C220DFA661F1F) violated - child record found
2937 [main] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:172)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
at module1.EntityManagerTest.removeTest(EntityManagerTest.java:47)
at module1.EntityManagerTest.EntityManagerTest(EntityManagerTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.sql.BatchUpdateException: ORA-02292: integrity constraint (HIBERNATE_TUTORIAL.FK1D0C220DFA661F1F) violated - child record found
at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:343)
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10657)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
... 34 more
Como podemos ver, al intentar eliminar el customer cuyo ID = 10, no nos deja porque estamos violando la integridad referencial con las tablas ACCOUNTS y CUSTOMER_ADDRESS
En estos casos tenemos dos alternativas. O bien, eliminar en prmier lugar todos los registros que referencien al customer 1 o bien, mapear en las entidades que la eliminacion de un customer, por cascada elimine los registros que lo referencian. El tema de las operaciones en cascada lo veremos mas adelante en este capitulo.
■ EntityManager - Contains
Este metodo es utilizado para saber si una entidad esta atachada al contexto de persistencia o no.
Customer customerFind = em.find(Customer.class, 1);
if (em.contains(customerFind)){
log.debug("customerFind es una entidad atachada");
}
Customer cust = new Customer();
if (!em.contains(cust)){
log.debug("cust NO es una entidad atachada");
}
em.getTransaction().commit();
La traza SQL y el resultado es el siguiente:
2688 [main] DEBUG module1.EntityManagerTest - ----------containsTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
2688 [main] DEBUG module1.EntityManagerTest - customerFind es una entidad atachada
2688 [main] DEBUG module1.EntityManagerTest - cust NO es una entidad atachada
■ EntityManager - Flush
Este metodo permite sincronizar (fuerza la sincronizacion) el contexto de persistencia con la BD. Tipicamente el flush ocurre cuando se hace commit de la transaccion
em.getTransaction().begin();
Customer customerFind = em.find(Customer.class, 1);
customerFind.setGender(Gender.FEMALE);
em.flush(); // en cada flush ejecuta el UPDATE
customerFind.setFirstName("Michael");
em.flush(); // en cada flush ejecuta el UPDATE
customerFind.setLastName("Jordan");
em.getTransaction().commit(); // en el commit ejecuta el 3er UPDATE
La traza SQL es la siguiente:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
En este caso, al hacer dos flush antes del commit (tenemos dos flush y un commit), vemos que por detras, se lanzaron 3 UPDATEs cada uno para actualizar algo distinto del customer (dos updates por los dos flush que se hicieron y el ultimo update se hizo por el commit). Ahora bien, si en lugar de hacer los dos flush, no hacemos ninguno y solo hacemos el commit, nos quedaria algo asi:
Customer customerFind1 = em.find(Customer.class, 1);
customerFind1.setGender(Gender.MALE);
customerFind1.setFirstName("Michael1");
customerFind1.setLastName("Jordan1");
em.getTransaction().commit();
La traza SQL seria la siguiente
2641 [main] DEBUG module1.EntityManagerTest - ----------flushTest2------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
En este caso, podemos ver que solo se ejecuta un solo UPDATE en lugar de los 3 que se ejecutaban en el ejemplo anterior.
Recordamos que el FLUSH se termina ejecutando cuando se hace COMMIT de la transaccion y que cuando estamos trabajando con entidades manejadas o atachadas, no hace falta hacer el merge. En estos dos ejemplos que vimos recien, se obvio hacer el merge ya que el mismo FLUSH o COMMIT se encarga de sincronizar y llevar a cabo el merge del contexto de persistencia con la BD.
■ EntityManager - FlushMode
Flush Mode AUTO: Por defecto, cada vez que se ejecuta una consulta dentro de una transacción el EntityManager nos asegura que las actualizaciones de la base de datos se incluyen en los resultados de dicha consulta. Un ejemplo tipico es cuando modificamos el estado de alguna entidad atachada al contexto de persistencia y luego ejecutamos una consulta dentro de la misma transaccion., e resultado de esto sera que prmiero se ejecuten los UPDATEs y luego los SELECTs. Este comportamiento es conocido como Flush Mode en AUTO y tipicamente esto se logra haciendo flush antes de ejecutar las queries. para asegurarse que lo que traiga de la BD sea lo ultimo (que este sincronizado con el contexto de persistencia) Esto es totalmente transparente para el desarrollador.
Flush Mode COMMIT: Este modo se utilizara si se sabe que la consulta no se ve afectada por actualizaciones pendientes de base de datos, mejorando la performance de las consultas ya que hay menos llamadas a la base de dato. En estos casos solo se ejecutaran los flush al momento de hacer commit una transaccion.
Caso 1:
em.setFlushMode(FlushModeType.AUTO); // Por defecto ya viene seteado
em.getTransaction().begin();
// Obtenemos un customer (entidad atachada) y le hacemos modificaciones
Customer customerFind = em.find(Customer.class, 1);
customerFind.setGender(Gender.MALE);
customerFind.setFirstName("Isabel");
customerFind.setLastName("La Loca");
// Sin mergear los cambios (ya q no se hizo merge ni flush ni commit),
// ejecutamos la consulta
Query query = em.createQuery("SELECT c from Customer c");
List<Customer> customers = query.getResultList();
for (Customer customer : customers) {
log.debug("CUSTOMER " + customer.getFirstName() + " " + customer.getLastName());
}
em.getTransaction().commit();
3094 [main] DEBUG module1.EntityManagerTest - ----------flushModeTest1------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_,
customer0_.FIRST_NAME as FIRST2_2_,
customer0_.GENDER as GENDER2_,
customer0_.LAST_NAME as LAST4_2_,
customer0_.referee_id as referee7_2_,
customer0_.name1 as name5_2_,
customer0_.name2 as name6_2_
from
Customer customer0_
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
3110 [main] DEBUG module1.EntityManagerTest - CUSTOMER Isabel La Loca
3110 [main] DEBUG module1.EntityManagerTest - CUSTOMER Damian Ciocca null
3110 [main] DEBUG module1.EntityManagerTest - CUSTOMER First name 3 Last name 3
3110 [main] DEBUG module1.EntityManagerTest - CUSTOMER Miguel Angel Sanchez Last name 4
Podemos ver que a la hora de ejecutar la query, se lanzara un UPDATE antes.
Caso 2:
Ahora vamos a cambiar el modo del flush y vemos como el UPDATE pasara a ejecturase a lo ultimo de todo (cuando se ejecuta el COMMIT).
em.setFlushMode(FlushModeType.COMMIT); // Cambiamos el tipo de flushMode
em.getTransaction().begin();
// Obtenemos un customer (entidad atachada) y le hacemos modificaciones
Customer customerFind2 = em.find(Customer.class, 1);
customerFind2.setGender(Gender.FEMALE);
customerFind2.setFirstName("Hector");
customerFind2.setLastName("Reclade");
// Sin mergear los cambios (ya q no se hizo merge ni flush ni commit),
// ejecutamos la consulta
Query query1 = em.createQuery("SELECT c from Customer c");
List<Customer> customers1 = query1.getResultList();
for (Customer customer : customers1) {
log.debug("CUSTOMER " + customer.getFirstName() + " " + customer.getLastName());
}
em.getTransaction().commit();
La traza SQL es la siguiente:
3125 [main] DEBUG module1.EntityManagerTest - ----------flushModeTest2------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_,
customer0_.FIRST_NAME as FIRST2_2_,
customer0_.GENDER as GENDER2_,
customer0_.LAST_NAME as LAST4_2_,
customer0_.referee_id as referee7_2_,
customer0_.name1 as name5_2_,
customer0_.name2 as name6_2_
from
Customer customer0_
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
3125 [main] DEBUG module1.EntityManagerTest - CUSTOMER Hector Reclade
3125 [main] DEBUG module1.EntityManagerTest - CUSTOMER Damian Ciocca null
3125 [main] DEBUG module1.EntityManagerTest - CUSTOMER First name 3 Last name 3
3125 [main] DEBUG module1.EntityManagerTest - CUSTOMER Miguel Angel Sanchez Last name 4Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
En este 2do caso, vemos como el UPDATE se ejecutara ni bien se haga el COMMIT de la transaccion mas alla de que la query nos traera correctamente los resultados (es decir, nos traera los valores actualizados)
La tabla nos quedara asi (luego de ejecutar el caso 1 y caso 2)::
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 Hector FEMALE Recalde Damian (null) 1
2 Damian Ciocca (null) (null) (null) (null) (null)
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 Miguel Angel Sanchez MALE Last name 4 (null) (null) (null)
■ EntityManager - Refresh
Este metodo nos permitira sincronizar cualquier cambio en la base de datos con los objetos atachados al contexto de persistencia
Esto es util para cuando tenemos transacciones largas.
em.getTransaction().begin();
Customer c = em.find(Customer.class, 1);
c.setGender(Gender.MALE);
c.setFirstName("Isabel");
c.setLastName("La Loca");
Customer custMerged = em.merge(c);
log.debug("Antes de ejecutar el REFRESH");
log.debug("CUSTOMER " + custMerged.getFirstName() + " " + custMerged.getLastName());
em.refresh(custMerged);
log.debug("Luego de ejecutar el REFRESH");
log.debug("CUSTOMER " + custMerged.getFirstName() + " " + custMerged.getLastName());
em.getTransaction().commit();
La traza SQL es la siguiente:
4765 [main] DEBUG module1.EntityManagerTest - ----------refreshTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
4765 [main] DEBUG module1.EntityManagerTest - Antes de ejecutar el REFRESH
4765 [main] DEBUG module1.EntityManagerTest - CUSTOMER Isabel La Loca
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_0_,
customer0_.FIRST_NAME as FIRST2_2_0_,
customer0_.GENDER as GENDER2_0_,
customer0_.LAST_NAME as LAST4_2_0_,
customer0_.referee_id as referee7_2_0_,
customer0_.name1 as name5_2_0_,
customer0_.name2 as name6_2_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
4765 [main] DEBUG module1.EntityManagerTest - Luego de ejecutar el REFRESH
4765 [main] DEBUG module1.EntityManagerTest - CUSTOMER Hector Reclade
■ EntityManager - Clear
Este metodo nos permite limpiar el contexto de persistencia. Esto causara que todas las managed entities o entidades atachadas pasen a ser entidades desatachadas. Cualquier update pendiente sobre alguna entidad no sera llevado a cabo (no se ejecutara el flush contra la BD)
em.getTransaction().begin();
Customer customerFind = em.find(Customer.class, 1);
if (em.contains(customerFind)){
log.debug("customerFind es una entidad atachada");
}
em.clear();
if (!em.contains(customerFind)){
log.debug("customerFind YA NO es una entidad atachada");
}
em.getTransaction().commit();
La traza SQL y la ejecución de este código es la siguiente:
3938 [main] DEBUG module1.EntityManagerTest - ----------clearTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
customerFind es una entidad atachada
3938 [main] DEBUG module1.EntityManagerTest - customerFind YA NO es una entidad atachada
■ EntityManager - Cascade Operations (operaciones en cascada)
Las operaciones en cascada ocurren para las relaciones entre las entidades. Es decir, pueden ocurrir para las relaciones one-to-one, one-to-many, many-to-one, y many-to-many. Las operaciones de cascada podran ser: persist, remove, merge, y refresh.
Por defecto, las relaciones entre las entidades no soportan las operaciones de cascada
a. Operacion persist (el ejemplo lo veremos sobre la relacion Customer - Referee)
En la relacion, agregar a la anotacion @OneToOne lo siguiente: @OneToOne(cascade=CascadeType.PERSIST)
@Entity
public class Customer implements Serializable{
@Id
@Column(name="CUSTOMER_ID")
private int id;
@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@OneToOne(cascade=CascadeType.PERSIST)
private Referee referee;
...
...
}
@Entity
public class Customer implements Serializable{
@Id
@Column(name="CUSTOMER_ID")
private int id;
@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@OneToOne(cascade=CascadeType.PERSIST)
private Referee referee;
...
...
}
log.debug("----------cascadePersistTest------------");
// En caso de no agregar la anotacion @OneToOne(cascade=CascadeType.PERSIST)
// Se producira el siguiente error:
// javax.persistence.RollbackException: Error while commiting the transaction
// Caused by: org.hibernate.TransientObjectException: object references an unsaved
// transient instance - save the transient instance before flushing:
// module1.Customer.referee -> module1.Referee
em.clear();
Referee r1 = new Referee();
r1.setComments("prueba de persistencia");
r1.setName("referee de lopez");
r1.setId(10);
Customer c1 = new Customer();
c1.setFirstName("Juan");
c1.setLastName("Lopez");
c1.setReferee(r1); // Aqui relacionamos el referee creado al customer
c1.setGender(Gender.FEMALE);
c1.setId(100);
em.getTransaction().begin();
em.persist(c1);
em.getTransaction().commit();
La traza SQL es la siguiente:
3953 [main] DEBUG module1.EntityManagerTest - ----------cascadePersistTest------------
Hibernate:
insert
into
Referee
(comments, name, id)
values
(?, ?, ?)
Hibernate:
insert
into
Customer
(FIRST_NAME, GENDER, LAST_NAME, referee_id, name1, name2, CUSTOMER_ID)
values
(?, ?, ?, ?, ?, ?, ?)
En este caso, vemos que al agregar la anotacion @OneToOne(cascade=CascadeType.PERSIST) a la entidad Customer, nos permitirá solo persistir el customer sin tener la necesidad de persistir previamente el Referee (en caso que no tenga la anotación, antes tendríamos que hacer el persist del Referee creado y luego ese referee creado asociarlo al customer y recién ahi persistir el customer) y JPA se encargara automáticamente de persistir también el Referee.
b. Operacion remove (el ejemplo lo veremos sobre la relacion Customer - Account)
En la relacion, agregar a la anotacion @OneToMany lo siguiente: @OneToMany(fetch=EAGER, cascade=CascadeType.REMOVE)
@Entity
public class Customer implements Serializable{
@Id
@Column(name="CUSTOMER_ID")
private int id;
@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@OneToOne(cascade=CascadeType.PERSIST)
private Referee referee;
@OneToMany(mappedBy="customer", fetch=FetchType.EAGER, cascade=CascadeType.REMOVE)
private List<Account> accounts;
...
...
}
log.debug("----------cascadeRemoveTest Initial dummy data------------");
em.clear();
em.getTransaction().begin();
Customer c4 = new Customer();
c4.setFirstName("prueba first name");
c4.setLastName("prueba last name");
c4.setGender(Gender.FEMALE);
c4.setId(400);
CheckingAccount a1 = new CheckingAccount();
a1.setBalance(110);
a1.setCustomer(c4); // referencia bidireccional account/customer
a1.setDateOpened(new Date());
a1.setId(200);
Address add2 = new Address();
add2.setAddressLine("prueba address");
add2.setCountry("MEXICO");
add2.setPostCode("9090");
add2.setId(100);
List<Address> addresses = new ArrayList<Address>();
addresses.add(add2);
c4.setAddresses(addresses); // referencia bidireccional customer/address
em.persist(a1);
em.persist(add2);
em.persist(c4);
em.getTransaction().commit();
em.clear();
log.debug("----------cascadeRemoveTest------------");
em.getTransaction().begin();
Customer c = em.find(Customer.class, 400);
em.remove(c);
em.getTransaction().commit();
1437 [main] DEBUG module1.EntityManagerTest - ----------cascadeRemoveTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
delete
from
CUSTOMER_ADDRESS
where
CUST_ID=?
Hibernate:
delete
from
Account
where
ACCOUNT_ID=?
Hibernate:
delete
from
Customer
where
CUSTOMER_ID=?
Como podemos ver, al eliminar el customer, tanto el CUSTOMER_ADDRESS como el ACCOUNT que tenían referencias a CUSTOMER tambien se eliminaron. Esto es gacias que que la relacion con ACCOUNT nos quedo con la anotacion @OneToMany(fetch=EAGER, cascade=CascadeType.REMOVE).
Igualmente nos queda una duda respecto a porque tambien elimino el CUSTOMER_ADDRESS ya que no se le agrego ninguna anotacion de cascading. La unica diferencia que tiene con ACCOUNTS es que la relacion en lugar de ser oneToMany es ManyToMany. Voy a seguir investigando este tema ya que todavía no lo he podido descifrar.
c. Operación merge (el ejemplo lo veremos sobre la relacion Customer - Account)
En la relacion, agregar a la anotacion @OneToMany lo siguiente: @OneToMany(fetch=EAGER, cascade=CascadeType.MERGE)
@Entity
public class Customer implements Serializable{
@Id
@Column(name="CUSTOMER_ID")
private int id;
@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@OneToOne(cascade=CascadeType.PERSIST)
private Referee referee;
@OneToMany(mappedBy="customer", fetch=FetchType.EAGER, cascade={CascadeType.REMOVE,
CascadeType.MERGE})
private List<Account> accounts;
...
...
}
log.debug("----------cascadeMergeTest------------");
em.getTransaction().begin();
Customer c = em.find(Customer.class, 1);
Session session = (Session) em.getDelegate();
session.evict(c); // desatachamos la entidad
c.setFirstName("cascadeMerge");
List<Account> accounts = c.getAccounts();
for (Account account : accounts) {
session.evict(account); // desatachamos la entidad
account.setBalance(678);
}
// Ahora vemos que tanto el customer como los accounts
// estan todos desatachados. Por ende, procedemos a atachar el
// customer nuevamente al contexto de persistencia.
// Gracias a la operacion de cascada sobre los accounts, es que
// vamos a poder persistir los cambios en cada uno de los accounts.
em.merge(c);
em.getTransaction().commit();
La traza SQL es la siguiente:
1390 [main] DEBUG module1.EntityManagerTest - ----------cascadeMergeTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_1_,
customer0_.FIRST_NAME as FIRST2_2_1_,
customer0_.GENDER as GENDER2_1_,
customer0_.LAST_NAME as LAST4_2_1_,
customer0_.referee_id as referee7_2_1_,
customer0_.name1 as name5_2_1_,
customer0_.name2 as name6_2_1_,
accounts1_.CUSOMTER_ID as CUSOMTER7_3_,
accounts1_.ACCOUNT_ID as ACCOUNT2_3_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
where
customer0_.CUSTOMER_ID=?
Hibernate:
update
Account
set
balance=?,
CUSOMTER_ID=?,
dateOpened=?,
overdraftLimit=?
where
ACCOUNT_ID=?
Hibernate:
update
Customer
set
FIRST_NAME=?,
GENDER=?,
LAST_NAME=?,
referee_id=?,
name1=?,
name2=?
where
CUSTOMER_ID=?
Hibernate:
update
Account
set
balance=?,
CUSOMTER_ID=?,
dateOpened=?,
overdraftLimit=?
where
ACCOUNT_ID=?
Hibernate:
update
Account
set
balance=?,
CUSOMTER_ID=?,
dateOpened=?,
interestRate=?
where
ACCOUNT_ID=?
El tema esta en que tanto el customer como los accounts no lo tenemos atachados. Para estos casos es que nos sirve esta anotación ya que mergeara el contexto de persistencia con la BD tanto los cambios del customer como de los accounts
La tabla ACCOUNTS nos quedara asi despues de ejecutar este test.
TABLA ACCOUNTS
ACCOUNT_TYPE ACCOUNT_ID BALANCE DATEOPENED OVERDRAFTLIMIT INTERESTRATE CUSOMTER_ID
--------------- ------------- ---------- --------------------- ----------------- --------------- --------------
C 1 678 6/21/2011 12:00:00 AM 0 (null) 1
C 2 678 (null) 0 (null) 1
S 3 678 6/21/2011 12:00:00 AM (null) 0 1
--------------- ------------- ---------- --------------------- ----------------- --------------- --------------
C 1 678 6/21/2011 12:00:00 AM 0 (null) 1
C 2 678 (null) 0 (null) 1
S 3 678 6/21/2011 12:00:00 AM (null) 0 1
Donde podemos ver que en la columna BALANCE de los 3 registros se modifico por el valor 678
d. Operacion refresh (el ejemplo lo veremos sobre la relacion Customer - Account)
En la relación, agregar a la anotacion @OneToMany lo siguiente: @OneToMany(fetch=EAGER, cascade=CascadeType.REFRESH)
En primer lugar vamos a ver la ejecución del refresh() sin tener el CascadeType.REFRESH en la anotación oneToMany
log.debug("----------cascadeRefreshTest------------");
em.clear();
em.getTransaction().begin();
Customer customerFind = em.find(Customer.class, 1);
em.refresh(customerFind);
em.getTransaction().commit();
La traza SQL es la siguiente:
375 [main] DEBUG module1.EntityManagerTest - ----------cascadeRefreshTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_0_,
customer0_.FIRST_NAME as FIRST2_2_0_,
customer0_.GENDER as GENDER2_0_,
customer0_.LAST_NAME as LAST4_2_0_,
customer0_.referee_id as referee7_2_0_,
customer0_.name1 as name5_2_0_,
customer0_.name2 as name6_2_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Ahora bien, agregaremos el CascadeType.REFRESH a la relacion CUSTOMER-ACCOUNTS,
@Entity
public class Customer implements Serializable{
@Id
@Column(name="CUSTOMER_ID")
private int id;
@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@OneToOne(cascade=CascadeType.PERSIST)
private Referee referee;
@OneToMany(mappedBy="customer", fetch=FetchType.EAGER, cascade={CascadeType.REMOVE,
CascadeType.MERGE,
CascadeType.REFRESH})
private List<Account> accounts;
....
....
}
y vemos que la traza SQL cambia a:
1438 [main] DEBUG module1.EntityManagerTest - ----------cascadeRefreshTest------------
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Hibernate:
select
checkingac0_.ACCOUNT_ID as ACCOUNT2_1_0_,
checkingac0_.balance as balance1_0_,
checkingac0_.CUSOMTER_ID as CUSOMTER7_1_0_,
checkingac0_.dateOpened as dateOpened1_0_,
checkingac0_.overdraftLimit as overdraf5_1_0_
from
Account checkingac0_
where
checkingac0_.ACCOUNT_ID=?
and checkingac0_.ACCOUNT_TYPE='C'
Hibernate:
select
checkingac0_.ACCOUNT_ID as ACCOUNT2_1_0_,
checkingac0_.balance as balance1_0_,
checkingac0_.CUSOMTER_ID as CUSOMTER7_1_0_,
checkingac0_.dateOpened as dateOpened1_0_,
checkingac0_.overdraftLimit as overdraf5_1_0_
from
Account checkingac0_
where
checkingac0_.ACCOUNT_ID=?
and checkingac0_.ACCOUNT_TYPE='C'
Hibernate:
select
savingacco0_.ACCOUNT_ID as ACCOUNT2_1_0_,
savingacco0_.balance as balance1_0_,
savingacco0_.CUSOMTER_ID as CUSOMTER7_1_0_,
savingacco0_.dateOpened as dateOpened1_0_,
savingacco0_.interestRate as interest6_1_0_
from
Account savingacco0_
where
savingacco0_.ACCOUNT_ID=?
and savingacco0_.ACCOUNT_TYPE='S'
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_1_,
customer0_.FIRST_NAME as FIRST2_2_1_,
customer0_.GENDER as GENDER2_1_,
customer0_.LAST_NAME as LAST4_2_1_,
customer0_.referee_id as referee7_2_1_,
customer0_.name1 as name5_2_1_,
customer0_.name2 as name6_2_1_,
accounts1_.CUSOMTER_ID as CUSOMTER7_3_,
accounts1_.ACCOUNT_ID as ACCOUNT2_3_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
where
customer0_.CUSTOMER_ID=?
Donde vemos que se va a ejecutar para el customer 1, una consulta distinta para cada Account de dicho customer. Es decir, como sabemos que el customer cuyo ID=1 tiene 3 Accounts, lo que hace el refresh() es hacer una consulta por cada Account para refrescar cada entidad Account (asociadas al customer) del contexto de persistencia y así estar seguros que el objeto atachado al contexto, esta sincronizado con la BD.
e. Operacion all (el ejemplo lo veremos sobre la relacion Customer - Account)
Para los casos en los cuales necesitamos especificar los 4 tipos de cascadas, utilizamos la anotacion ALL del tipo de cascada.
En la relacion, agregar a la anotacion @OneToMany lo siguiente:
@OneToMany(fetch=EAGER, cascade=CascadeType.ALL) ■ EntityManager - Extendiendo el contexto de persistencia
Suponiendo que tenemos un EJB del tipo stateful session bean donde tenemos una variable de instancia del tipo Customer y tres metodos que utilizaran dicha variable y modificaran algun estado (ej, metodo que modificara el first name del customer)
Recordamos que el EJB del tipo Stateful Session Bean, a diferencia del Stateless Session Bean, mentiene el estado de las variables entre las llamadas a los diferentes métodos (diferentes transacciones), mientras que el Stateless Session Bean, cada transacción es acotada a cada llamada de método y no mantiene el estado entre las diferentes llamadas (transacciones).
Recordamos que el EJB del tipo Stateful Session Bean, a diferencia del Stateless Session Bean, mentiene el estado de las variables entre las llamadas a los diferentes métodos (diferentes transacciones), mientras que el Stateless Session Bean, cada transacción es acotada a cada llamada de método y no mantiene el estado entre las diferentes llamadas (transacciones).
Ejemplo de la variable del tipo customer en un EJB del tipo Stateful Session Bean:
private Customer cust;
Ejemplo de los metodos que encontraremos en el Stateful Session Bean:
public void addCustomer(int custId, String firstName,
String lastName) {
cust = new Customer();
cust.setId(custId);
cust.setFirstName(firstName);
cust.setLastName(lastName);
em.persist(cust);
}
public void updateFirstName(String firstName) {
cust.setFirstName(firstName);
Customer mergedCust = em.merge(cust);
}
public void updateLastName(String lastName) {
cust.setLastName(lastName);
Customer mergedCust = em.merge(cust);
}
Recordamos nuevamente, que el stateful session bean mantendra el valor del customer en la variable de instancia entre las llamadas a los metodos mientras que los statless session bean el valor del customer se perderia entre las distitnas llamadas.
Ahora bien, como se explico en el apartado de conceptos basicos, sabemos que una transaccion se iniciara al momento de que se efectue la invocacion al metodo del EJB y la misma terminara cuando finalice el metodo (container managed entity manager), lo cual esto produce que cada vez que se inicie la transaccion, se cree un nuevo contexto de persistencia y cuando la misma termina, el contexto de persistencia tambien finalice (se cierre).
Esto provocara que la variable de instancia Customer cust creada por el metodo addCustomer pase de ser una entidad atachada al momento de ejecucion del metodo, a ser una entidad desatachada ni bien termina la su ejecucion.
Implicando que los metodos updateFirstName, updateLastName deberan ejecutar un merge para llevar a cabo cualquier operacion con la variable de instancia customer.
Se recuerda que el metodo merge no hace falta invocarlo ya que todos los cambios de estados que se efectuen sobre una entidad atachada, ni bien termine la transaccion, dichos cambios se reflejaran/sincronizaran en la BD (se hara commit de la transaccion automaticamente)
Se recuerda que el metodo merge no hace falta invocarlo ya que todos los cambios de estados que se efectuen sobre una entidad atachada, ni bien termine la transaccion, dichos cambios se reflejaran/sincronizaran en la BD (se hara commit de la transaccion automaticamente)
Customer mergedCust = em.merge(cust);
Para evitar el tener que hacer un merge, se podra habilitar que el contexto de persistencia pueda abarcar mas de una transaccion. Para ello es que se podra extender el contexto de persistencia el cual puede ser solo aplicado a los EJB del tipo stateful session beans
Para esto se utilizara la anotacion:
@PersistenceContext(unitName="examplePersistenceUnit",
type=PersistenceContextType.EXTENDED)
type=PersistenceContextType.EXTENDED)
De esta forma, los metodos podran no tener mas la llamada al metodo merge() como se tenia antes, ya que ahora el valor de la propiedad Customer del EJB, sera un customer atachado al contexto de persistencia y esto perdurara entre las llamadas a los metodos.
public void updateFirstName(String firstName) {
cust.setFirstName(firstName);
}
public void updateLastName(String lastName) {
cust.setLastName(lastName);
}
■ EntityManager - Callback Methods
A continuacion daremos un ejemplo de un callback method para ilustrar como es que se utilizan y para que sirven
PostLoad:
Se ejecuta después de ejecutar una consulta o un refresh() sobre la entidad
Agregamos a la entidad Referee el siguiente método:
@PostLoad
public void postLoad(){
System.out.println("referee.postLoad method invoked");
}
Luego ejecutamos el siguiente código:
log.debug("----------cascadeRefreshTest------------");
em.clear();
em.getTransaction().begin();
log.debug("ejecutando el find");
Referee refereeFound = em.find(Referee.class, 1);
log.debug("ejecutando el refresh");
em.refresh(refereeFound);
em.getTransaction().commit();
La traza SQL y ejecución del log es lo siguiente:
1438 [main] DEBUG module1.EntityManagerTest - ----------cascadeRefreshTest------------
1438 [main] DEBUG module1.EntityManagerTest - ejecutando el find
Hibernate:
select
referee0_.id as id0_0_,
referee0_.comments as comments0_0_,
referee0_.name as name0_0_
from
Referee referee0_
where
referee0_.id=?
referee.postLoad method invoked
1438 [main] DEBUG module1.EntityManagerTest - ejecutando el refresh
Hibernate:
select
referee0_.id as id0_0_,
referee0_.comments as comments0_0_,
referee0_.name as name0_0_
from
Referee referee0_
where
referee0_.id=?
referee.postLoad method invoked
Descargas de ejemplos de mapeos JPA, consultas JPQL y operaciones con el EntityManager
Por favor, haga clic en: Descargar ejemplos JPA
Bibliografia
* Java Persistence with Hibernate - Revised Edition fo Hiernate In Action - Christian Bauer and Gavin King - 2007
* EJB 3 Developer Guide - A Practical Guide for developers and architects to the Enterprise Java Beans
Standard - Michael Sikora - 2008