El concepto de JPA Transaction es uno de los conceptos claves cuando uno comienza a trabajar con JPA ( Java Persistence API) . Sin embargo una transacción es un concepto que pertenece más a las bases de datos que a JPA propiamente dicho .
¿Que es exactamente una transacción?.
Una transacción hace referencia a una operación que se realiza contra una base de datos y que cumple con las propiedades ACID.
Atomic : Habla de cuando una operación es indivisible o se realiza de forma completa o no se realiza ninguna de sus partes . Esto es muy habitual en base de datos ya que si por ejemplo queremos realizar dos inserciones si las ejecutamos dentro de una transacción o se realizarán ambas o no se realizará ninguna. Cualquier fallo hará que la transacción se revierta y no se aplique ninguna modificación.
Consistente: Es decir si nosotros por ejemplo realizamos dos modificaciones en la base de datos una que me elimina 100 euros de mi saldo y otra que inserta 100 euros en otra cuenta . El concepto de consistencia hace referencia a que los números deben cuadrar si resto 100 euros de mi cuenta en la otra deben de entrar 100 no pueden entrar 70.
Isolated: Aislada , la operación que nosotros realizamos no puede verse afectada por otras operaciones que se estén ejecutando en ese momento
Durable: La información queda grabada en la base de datos para el futuro y persiste
JPA Transaction
Vamos a manejar estos conceptos dentro de JPA . Para ello nos vamos a crear una clase que podamos persistir en la base de datos
package com.arquitecturajava.dominio; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "libros") public class Libro { @Id private String isbn; private String titulo; private String autor; public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public String getAutor() { return autor; } public void setAutor(String autor) { this.autor = autor; } public Libro(String isbn, String titulo, String autor) { super(); this.isbn = isbn; this.titulo = titulo; this.autor = autor; } public Libro() { super(); } public Libro(String isbn) { super(); this.isbn = isbn; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((isbn == null) ? 0 : isbn.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Libro other = (Libro) obj; if (isbn == null) { if (other.isbn != null) return false; } else if (!isbn.equals(other.isbn)) return false; return true; } }
Una vez construida esta clase deberemos generar el Persistence.xml
Persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="biblioteca" transaction-type="RESOURCE_LOCAL"> <class>com.arquitecturajava.dominio.Libro</class> <properties> <property name= "hibernate.show_sql" value="true" /> <!-- cuando nos conectamos desde JPA hay q elegir el tipo de motor --> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:8889/biblioteca" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="root" /> </properties> </persistence-unit> </persistence>
Es momento de construir el programa Main y definir un EntityTransaction a la hora de persistir los datos en la base de datos:
package com.arquitecturajava.main; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import com.arquitecturajava.dominio.Libro; public class Principal { public static void main(String[] args) { // unidad de persistencia EntityManagerFactory emf= Persistence.createEntityManagerFactory("biblioteca"); EntityManager em= emf.createEntityManager(); Libro libro= new Libro("1A","java","pedro"); EntityTransaction t= em.getTransaction(); try { t.begin(); em.persist(libro); t.commit(); } catch (Exception e) { // TODO Auto-generated catch block t.rollback(); } } }
Una vez tenemos todo construido ejecutamos el código y nos insertará un registro en la base de datos :
Acabamos de insertar una nueva fila en la base de datos
JPA Transaction y rollbacks
Es momento de realizar la misma operación pero de una forma más transaccional por ejemplo podemos solicitar que nuestro código inserte dos libros.
package com.arquitecturajava.main; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import com.arquitecturajava.dominio.Libro; public class Principal { public static void main(String[] args) { // unidad de persistencia EntityManagerFactory emf= Persistence.createEntityManagerFactory("biblioteca"); EntityManager em= emf.createEntityManager(); Libro libro2= new Libro ("3A","JPA","Gema"); Libro libro3= new Libro ("4A","Java","David"); EntityTransaction t= em.getTransaction(); try { t.begin(); em.persist(libro2); em.persist(libro3); t.commit(); } catch (Exception e) { // TODO Auto-generated catch block t.rollback(); } } }
Todo sigue funcionando correctamente . La gestión transaccional la podemos ver con mayor claridad cuando insertamos otros libros pero alguno de ellos previamente existe.
package com.arquitecturajava.main; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import com.arquitecturajava.dominio.Libro; public class Principal { public static void main(String[] args) { // unidad de persistencia EntityManagerFactory emf= Persistence.createEntityManagerFactory("biblioteca"); EntityManager em= emf.createEntityManager(); Libro libro2= new Libro ("6B","Spring","Gema"); Libro libro3= new Libro ("4A","Java","David"); EntityTransaction t= em.getTransaction(); try { t.begin(); em.persist(libro2); em.persist(libro3); t.commit(); } catch (Exception e) { // TODO Auto-generated catch block t.rollback(); System.out.println("no hemos podido insertar"); } } }
En este caso el Libro 6B no existe en la base de datos y deberíamos ser capaces de insertarlo . Pero estamos dentro de una transacción por lo tanto si el insert de la siguiente linea 4A no se puede insertar porque ya existe , nos encontraremos que la operación es atómica y ninguna de las dos registros se persiste.
Acabamos de hacer una operación de rollback.
Acabamos de ejecutar una operación transaccional sobre la base de datos . En la cual si algo falla todo los cambios se desechan o se vuelven para atrás ya que es una operación atómica e indivisible.
Hola Cecilio como estas?
Ando con Java Spark y ando viendo de implementar transacciones, vi el package:
javax.transaction
javax.transaction-api
${transaction-api.version}
he instalado y me disponibiliza el @Transactional, pero no funciona, sabras o tendras algun tuto de como hacerlo funcionar?
Excelente contenido.
Saludos.
Ya lo siento Martin no tengo nada sobre este tema
Un saludo 🙁
Muchas gracias Cecilio.
de nada 🙂
Un artículo interesante, pero me queda la dudad de cómo se aplicaría este concepto con Spring Data? está relacionado con la anotación @ Transactional
Si por supuesto la anotación @Transactional comienza ella misma una transacción en su configuración por defecto
Un saludo