Transacciones con Spring

El tratar transacciones con Spring e Hibernate es una de las casuísticas más utilizadas por los proyectos de programación y aun así continúa siendo una de las menos conocidas.

Introducción

Una transacción de base de datos es un conjunto de instrucciones que se ejecutan en bloque. Por ejemplo, hago una consulta, modifico un registro A en la base de datos y elimino un registro B. Si en alguna de estas instrucciones se produce un error todo el proceso se echa atrás. De esta manera si luego consulto la base de datos veré que el registro A no ha sido alterado. Este proceso de «tirar atrás» las instrucciones realizadas se le dice hacer un rollback, mientras que el proceso de confirmar todas las instrucciones en bloque una vez hemos visto que no se ha producido ningún error se le llama hacer un commit.

Las transacciones se empiezan y terminan a nivel de servicio, nunca a nivel de DAO. Si lo pensamos tiene lógica, el servicio es el que se encarga de gestionar toda la lógica de negocios: llamará a los DAOs que necesite para consultar, guardar o modificar registros y lo ha de hacer de manera atómica. Por ejemplo, supongamos un proceso batch que trabaja por la noche y que se encarga de recalcular la hipoteca de los clientes. En el primer paso obtiene el registro de la hipoteca, en el segundo recalcula la hipoteca y en el tercero actualiza el registro del cliente con una marca para señalar que el proceso ya ha procesado ese cliente. El DAO se encarga de la consulta, de la actualización de la hipoteca y de la actualización del registro con la marca de manera individual, primero con un método que lance una query, después con un update de la hipoteca y posteriormente con un update de la tabla de avisos del cliente. Pero es el servicio, o su equivalente batch, el que se encarga de que todo este proceso se trate como una unidad. Si empezase la transacción a nivel del DAO no tendría manera de controlar la atomicidad de todo el proceso o, peor aún, debería trasladar la lógica del negocio a la capa del DAO.

Por otra parte, con JPA/Hibernate cada vez que deseemos hacer una modificación sobre la base de datos necesitamos una transacción activa. Esto es así a pesar de que sólo vayamos a insertar un dato y no un bloque de acciones.

Transacciones con Spring

La manera más común de tratar las transacciones en Spring es mediante la anotación @Transactional en la cabecera del método de una clase (nunca un interfaz) gestionada por Spring:


@Transactional
public void hazAlgoTransaccionalmente() {
	// Soy transaccional!
}

y la propiedad


<tx:annotation-driven />

en el fichero de contexto.

Ahora bien, no todo es tan simple como parece. Spring llama a los beans mediante proxies y esto es lo que añade complejidad al asunto. No voy a entrar en detalle de lo que es un proxy, lo dejo para una próxima entrada del glosario, simplemente señalar que debido a esto la anotación sólo funcionará en métodos públicos desde los cuales se acceda a la clase. O sea:


@Transactional
private void hazAlgoTransaccionalmente() {
	// No soy transaccional a pesar de la anotación
}

no funciona ya que el método de acceso es privado. Pero lo peor no es que no funcione, lo peor es que no funcionará y no te avisará de que te estás equivocando. Y es que la anotación @Transactional es tratada como un metadato, si lo puede aprovechar lo hace pero si no lo ignora.

Otro código erróneo que tampoco funcionará:


@Transactional
protected void hazAlgoTransaccionalmente() {
	// No soy transaccional a pesar de la anotación
}

Como antes el método no es público.

Veamos ahora un caso que da muchos quebraderos de cabeza:


public class A {

	@Autowired
	private B b;

	public void hazAlgoEnB() {
		b.hazAlgo();
	}

}

public class B {

	public void hazAlgo() {
		hazAlgoTransaccionalmente();
	}

	@Transactional
	public void hazAlgoTransaccionalmente() {
		// Soy transaccional?
	}
}

Tenemos una clase A que llama al método hazAlgo de la clase B que a su vez llama al método hazAlgoTransaccionalmente. Esta vez la anotación @Transactional está en un método público ¿Tendré una transacción activa? La respuesta es no. Puesto que el método con el que hemos entrado en la clase B es hazAlgo y este no está marcado como transaccional, no existe transacción.

En cambio


public class A {

	@Autowired
	private B b;

	public void hazAlgoEnB() {
		b.hazAlgo();
	}

}

public class B {

	@Transactional
	public void hazAlgo() {
		hazAlgoTransaccionalmente();
	}

	public void hazAlgoTransaccionalmente() {
		// Solo soy transaccional si se me llama
		// desde el método hazAlgo
	}
}

sí que funciona. Ahora la anotación @Transactional sí que está sobre el método de entrada a la clase con lo que se activa una transacción.

Ojo con esto de los métodos de entrada porque he visto a más de uno dándose golpes contra la pared por su culpa. La regla es simple, la anotación sólo tiene efecto en la cabecera de un método por el cual se acceda a la clase. Evidentemente se puede marcar todos los métodos como transaccionales, e incluso la propia clase (con lo que todos sus métodos públicos lo serán) pero como veremos más adelante eso nos puede dar algún problema por lo que hay que ser cautos.

Ámbito de la transacción

Una cosa que se ha de tener clara es cuanto dura una transacción. Esto es, cuando empieza y cuando termina. Si tenemos anotado un método con @Transactional, la transacción empezará justo antes de la primera línea del método y terminará justo después de la última. Si dentro de esto método existen llamadas a otros métodos estos otros métodos son llamados dentro de la transacción sin necesidad de anotarlos con @Transactional.

Ahora bien ¿qué sucede cuando un método anotado con @Transactional llama a otro método anotado también con @Transactional? Por ejemplo:


public class A {

	@Autowired
	private B b;

	@Transactional
	public void hazAlgoA() {
		b.hazAlgoB();
	}

}

public class B {

	@Transactional
	public void hazAlgoB() {
		hazAlgoTransaccionalmente();
	}
}

¿Se mantiene la misma transacción? ¿Se abre una nueva?… Bueno, a esto se le llama propagación de la transacción. Por defecto una transacción tiene una propagación de PROPAGATION_REQUIRED, si la transacción no existe la crea y si la tiene la aprovecha. Según esto, el primer método abre la transacción y el segundo la aprovecha por lo que todo está dentro de la misma transacción.

Ahora supongamos que queremos justo lo contrario. Necesitamos que el método de la clase B utilice una nueva transacción, de esta manera si algo falla en B, la transacción de A no hace rollback. Tal como está actualmente no nos sirve por lo que hemos de modificar la propagación de B de la manera siguiente.


@Transactional( propagation = Propagation.REQUIRES_NEW )
public void hazAlgoB() {
	hazAlgoTransaccionalmente();
}

Ahora, al entrar en B, sí se generará una nueva transacción.

Spring soporta las siguientes propagaciones:

  • PROPAGATION_REQUIRED – Es la que viene por defecto, así que no es necesaria especificarla. Si existe transacción la aprovecha y sino la crea
  • REQUIRES_NEW – Abre una transacción nueva y pone en suspenso la anterior. Una vez el método marcado como REQUIRES_NEW termina se vuelve a la transacción anterior.
  • PROPAGATION_SUPPORTS – Si existe transacción la aprovecha, sino no crea ninguna.
  • PROPAGATION_MANDATORY – Si no existe una transacción abierta se lanza una excepción. Hay gente que anota sus DAO con esta opción.
  • PROPAGATION_NEVER – Si existe una transacción abierta se lanza una excepción. No se me ocurre ningún ejemplo donde esto sea necesario pero seguro que alguno hay.
  • PROPAGATION_NOT_SUPPORTED – Si existe una transacción la pone en suspenso, la transacción se reactiva al salir del método.

Errores comunes con @Transactional

Transacción obligatoria

Una de las confusiones más comunes que existen es que en Spring/Hibernate se requiere una transacción para hacer una consulta a la base de datos lo cual no es del todo cierto. Si en el test de DBUnit ponemos lo siguiente.


@Test
public void getAllTest() {
	List resultados = genericDao.getAll(Persona.class);
	Assert.assertEquals(3, resultados.size());
}

Notad que no hay @Transactional pero el método ejecuta perfectamente y devuelve el resultado correcto. Ergo, no hace falta transacciones para consultas de datos aisladas. Vale, hago trampas, en realidad Hibernate sí que abre una transacción pero lo hace sin decirnos nada y de manera transparente.

Transacciones read-only

Otra confusión es la propiedad readonly. Si ponemos


@Transactional(readonly=true)
public void hazAlgoTransaccionalmente() {
	// Soy transaccional!
}

¿qué se supone que hace? Según la documentación una transacción readonly puede ser utilizada cuando quieres que tu código lea pero no modifique ningún dato y deja intuir que puede ser beneficioso en términos de rendimiento.

Ahora bien, ¿cómo afecta a esto a la hora de hacer inserciones y/o actualizaciones? Supongamos el siguiente código lanzado con JUnit sobre una base de datos HSQLDB:


@Test
@Transactional(readonly=true)
public void insertTest() {
	List resultados = genericDao.getAll(Persona.class);
	Assert.assertEquals(3, resultados.size());

	Persona p = new Persona();
	p.setNombre("Test");
	p.setEdad(33);
	Assert.assertEquals(0, p.getId());

	genericDao.insert(p);
	resultados = genericDao.getAll(Persona.class);
	Assert.assertEquals(4, resultados.size());
}

¿Qué sucede? La verdad es que esto tiene algo de trampa. Uno puede pensar que si he marcado la transacción como readonly la inserción de la persona no debería hacerse y el test fallaría. Pero no, Hibernate pasa del readonly e inserta el registro sin problema. Otros pensarán: No, que no te enteras, lo que sucede es que la entidad persona queda marcada como de solo lectura y lo que no puedo es modificarla y luego hacer un update. Pues tampoco, le cambio la edad y para la base de datos. Los más avispados pueden pensar, lo que sucede es que no haces un flush antes de la query. Vale, aceptamos barco. Eso sí que es cierto. O sea, que parece que la documentación no es correcta y el readonly prácticamente no hace nada.

El problema es cuando sales del test y vas a producción y el comportamiento es diferente y empiezan a saltarte errores. WTF! ¿Qué sucede aquí? ¡Si mis tests funcionan perfectamente! Pues sucede que el parámetro readonly es dependiente de a) la implementación JPA y b) el dirver jdbc que estés utilizando.

Por ejemplo, el test anterior lo lancé en una base de datos HSQLDB pero el comportamiento en Oracle puede ser diferente. Con Oracle 11 sí que puede generar una transacción de solo lectura, con HSQLDB no existe esa posibilidad. Ojo, esto no pasa en todas las versiones de Oracle ya que depende del driver que utilices. Si el driver sobreescribe correctamente el método setReadOnly del interfaz Connection tendremos una transacción readonly. Si no lo tenemos, pues sólo tendremos que no nos hace un flush antes de una query.

Y lo malo de todo esto es que no está documentado en ninguna parte. Para que uno se fíe de la documentación. Así que ojo.

Modificar la transacción

Hay gente que intenta modificar los parámetros de la transacción una vez abierta. Por ejemplo, algo que ya he visto alguna vez:


public class A {

	@Autowired
	private B b;

	public void hazAlgoA() {
		b.hazAlgoB();
	}

}

class B {

	@Autowired
	private C c;

	@Transactional
	public void hazAlgoB() {
		// inserto un dato en bbdd
		try {
 			c.hazAlgoC();
		catch( NullPointerExecption e) {
		}
		// inserto otro dato en bbdd

	}
}

class C {

	@Transactional(readonly=true)
	public void hazAlgoC() {
		// leo un dato en bbdd
	}
}

Se abre una transacción para B se llama a C y cómo se quiere hacer una lectura se marca la transacción como readonly. Bueno, esto no funciona. Una vez abierta una transacción no se puede modificar su estado.

Commit y Rollback

Ok, ya lo tenemos (casi) todo claro. Ahora lo que queremos es controlar el commit o el rollback de nuestra transacción. La cosa es simple, si un método anotado como @Transaccional lanza una excepción que herede de RuntimeException se producirá un rollback. En caso contrario, un commit.

Ok, vamos a dar vueltas a esto. Primero lo más evidente:


public class A {

	@Autowired
	private B b;

	public void hazAlgoA() {
		try {
			b.hazAlgoB();
		}
		catch( NullPointerExecption e) {
		}
	}

}

public class B {

	@Transactional
	public void hazAlgo(Entidad entidad) {
		// inserto un dato en bbdd

 		throw new NullPointerException();

		// inserto otro dato en bbdd
	}
}

lanza una NullPointerException, que hereda de RuntimeException, luego se produce un rollback y la primera inserción no se realiza (ni la segunda, claro).

Veamos esto


public class A {

	@Autowired
	private B b;

	public void hazAlgoA() {
		try {
			b.hazAlgoB();
		}
		catch( FileNotFoundException e) {
		}
	}

}

public class B {

	@Transactional
	public void hazAlgo(Entidad entidad) {
		// inserto un dato en bbdd

 		throw new FileNotFoundException();

		// inserto otro dato en bbdd
	}
}

¿Qué hará? ¿Inserta un dato? ¿Dos? ¿Ninguno? Veamos, FileNotFoundException no hereda de RuntimeException por lo que no debería hacer rollback, pero lanzamos la excepción antes de insertar el segundo dato así que el flujo del programa no llegaría al código de insertarlo, pero si hace el primero, ergo solo insertamos un dato. Podemos imaginar que hay algo así:


public void hazAlgo(Entidad entidad) {
	try{
		tx.begin

		// inserto un dato en bbdd
		throw new FileNotFoundException();

		// inserto otro dato en bbdd
	}
	catch(Exception e ) {
		if( e instanceof RuntimeException ) {
			tx.rollback();
			tx.close();
		}
		else {
			tx.commit();
			tx.close();
		}
		throw e;
	}
}

¡Esto no es el código que hay realmente! Tan sólo es poner en pseudocódigo lo explicado anteriormente.

Compliquemoslo un poco más:


public class A {

	@Autowired
	private B b;

	public void hazAlgoA() {
		b.hazAlgoB();
	}

}

class B {

	@Autowired
	private C c;

	@Transactional
	public void hazAlgoB() {
		// inserto un dato en bbdd
		try {
 			c.hazAlgoC();
		catch( NullPointerExecption e) {
		}
		// inserto otro dato en bbdd

	}
}

class C {

	@Transactional
	public void hazAlgoC() {
		// inserto un dato en bbdd

 		throw new NullPointerException();

		// inserto otro dato en bbdd
	}
}

Esta es más interesante. La clase C lanza una NullPointerException que hereda una RuntimeException ergo debería hacer un rollback. Pero, pero, pero, la clase B, donde se inicia la transacción ya tenía previsto este error, así que captura la excepción y no la vuelve a lanzar. ¿Hará un rollback? ¿Hará un commit? Hagan sus apuestas.

Bueno, hace un rollback. El punto es que si una RuntimeExcepcition sale de un método anotado como @Transactional, toda la transacción queda marcada como rollback. No importa que no fuese el método desde el que se inició la transacción, si su propagación es normal (esto es, no es una REQUIRES_NEW), en cuanto lanzamos la excepción estamos vendidos.

Esto es importante por un motivo, hay gente que, ante la duda, anota todos los métodos o las clases con @Transactional pero esto supone un problema de concepto de construcción. El encargado de saber si la transacción ha de marcarse como rollback o commit es siempre el método desde donde se inicia la transacción, nunca uno de los métodos a los que llama. Si yo llamo a un método externo que está marcado como transaccional y este por algún motivo me lanza una RuntimeExcepción, automáticamente me obliga a hacer un rollback. No me deja la opción de que sea yo el que valore si puedo continuar o no, quizás ya tenía previsto que podía saltar esa excepción y la capturaba para tratarla. Lamentablemente, al utilizar una librería que anotó su método con @Transactional me limita de cualquier intento de recuperarme de la excepción.

Moraleja, sólo hay que poner @Transactional en métodos donde se abre, cierra y controla la transacción. En el resto de métodos su colocación puede ser contraproducente.

Controlar las excepciones que hagan commit o rollback

El hecho anterior, que una transacción haga rollback si se lanza una RuntimeExcecption y commit si no, es algo demasiado rígido para muchos casos. Hay momentos que no queremos que se haga un rollback para alguna excepción en concreto o lo contrario, no queremos que haga un commit cuando se lanza una excepción que no herede de RuntimeException. Para esto tenemos las propiedades noRollbackFor y rollbackFor, con estas opciones podemos configurar el commit y el rollback según nuestras necesidades.

Por ejemplo:


@Transactional(noRollbackFor={NumberFormatException.class,ArithmeticException.class})
public void hazAlgoTransaccionalmente() {
	// Soy transaccional!
}

Hará un commit incluso si lanza las excepciones NumberFormatException o ArithmeticException o excepciones que hereden de estas. Lo de las excepciones que hereden de esta es muy importante para tener en cuenta su alcance. Hay gente que lo aprovecha para hacer cosas del tipo:


@Transactional(noRollbackFor={RuntimeException.class})
public void hazAlgoTransaccionalmente() {
	// Soy transaccional!
}

lo cual es una muy pésima idea porque implica que el método nunca hará un rollback.

Tal como he dicho existe la propiedad inversa, rollbackFor. Con esta podremos conseguir que la transacción haga un rollback para las excepciones que no hereden de RuntimeException.

Por ejemplo


@Transactional(rollbackFor={FileNotFoundException.class})
public void hazAlgoTransaccionalmente() {
	// Soy transaccional!
}

hará un rollback si lanza una FileNotFoundException a pesar de que esta no hereda de RuntimeException.

Transacciones programáticas

No siempre se pueden abrir transacciones con @Transactional. A veces necesitamos crear una transacción en algún lugar que la anotación no nos permite (un método privado, por ejemplo) así que hemos de buscarnos un método alternativo. Para esto utilizaremos las transacciones programáticas  Con estas podremos abrir y cerrar transacciones sin necesidad de anotaciones

Veamos el siguiente código:


Obejct o = new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
	public Object doInTransaction(TransactionStatus status) {
		// Soy Transaccional!
	}
});

Con él podemos abrir la transacción sin necesidad de anotaciones.

El código se puede modificar para añadir diferentes tipos de transacciones (propagación o aislamiento), para que nos devuelva un resultado ((TransactionCallback) o no (TransactionCallbackWithoutResult), etc. Para más información leer el manual

Lo que me dejo en el tintero

Lo más evidente es el aislamiento de la transacción (Isolation). Por ejemplo, por defecto la transacción no  nos permite hacer una consulta y que en ella aparezcan datos que se han insertado pero que todavía no se han commiteado pero modificando el nivel de aislamiento de la transacción sí que es posible. Las opciones de aislamiento no son muchas (cuatro) pero las consecuencias de estas opciones dan para mucho, así que lo dejo para otro momento.

Otra cosa es la duración de la transacción. Hay gente que hace transacciones enormes de centenares o miles de inserciones y/o modificaciones. Esto puede ser problemático debido a la memoria que consume y a la necesidad real de generar transacciones muy largas. En general es mejor tener transacciones pequeñas y bien definidas.

Hay gente que utiliza transacciones mediante aspectos. Con librerías tipo AspectJ se puede saltar las limitaciones de anotaciones en métodos privados o la (engorrosa) obligatoriedad de poner la anotación en el método de entrada de la clase. Lamentablemente primero tendría que explicar la programación orientada a aspectos y esto ocuparía más que su ejemplo en transacciones.

También existe la posibilidad de definir las transacciones en los ficheros de configuración de Spring. Esto te permite hacer cosas  más potentes como, por ejemplo, que todos los métodos de los DAO cuyo nombre empiezan por get abran transacciones readonly y los que empiezan por insert abran transacciones normales. Todo, por supuesto, sin tener que añadir nada al código. A la hora de la verdad algo tan genérico no sirve de mucha ayuda y, yo al menos, ni lo utilizo ni lo he visto en ningún proyecto en el que haya estado. En cualquier caso es algo que es útil conocer por sus posibilidades.

Esta entrada fue publicada en Programación, Programación-JPA, Programación-Spring y etiquetada , , , . Guarda el enlace permanente.

13 respuestas a Transacciones con Spring

  1. Roberto dijo:

    Una pregunta sobre readonly. El comportamiento que explicas es referente al atributo readonly de la anotación @Transactional, pero no se refiere a la propiedad mutable de una entidad que por ejemplo este mapeada con un fichero hbm.xml. ¿No? Me refiero a que si tu en tu fichero de entidad hbm.xml seteas el atributo mutable=false no hace updates sobre la base de datos, independientemente de la base de datos, no? Gracias. Saludos.

    • igochan dijo:

      Hola, es tal como dices. El readonly de la transacción no tiene nada que ver con el mutable=false del mapeo de la clase. El mutable no te actualizará la entidad nunca, mientras que el readonly simplemente «optimizará» la transacción para que vaya más rápido. El ejemplo ideal de mutable=false sería para mapear una tabla maestra de países, por ejemplo, entidades que sabes que no se modificarán nunca; mientras que el readonly podría ser para una transacción que hace un informe, hay entidades que generalmente se modifican pero en ese caso concreto sabes que sólo vas a utilizarlas en solo lectura. Saludos

  2. mlgrdcd dijo:

    Hola, estoy desarrollando un sistema con spring mvc (Controller, Service y DAO). Ahora bien mis daos heredan un GenericDao que utiliza la plantilla Hibernate (HibernateTemplate). Resulta que desde mi servicio llamo a varios daos (insert update’s). Ahora bien en mi servicio e colocado un @transactional y en mis los metodos de mis daos un @transacional(support). Cuando corro la aplicación me aparece la excepción: UnexpectedRollbackException: : Transaction rolled back because it has been marked as rollback-only. A la espera de tus comentarios, gracias

  3. igochan dijo:

    Hola. No tiene mucho misterio. Si la transacción ha quedado marcada como rollback es que te ha saltado una excepción que hereda de RuntimeException. Has de averiguar qué excepción es y porqué sale. Saludos

  4. Santi dijo:

    Muchisimas gracias por este tutorial, me ha servido para entender muchas cosas! Ahora lo pondré en practica 🙂

    • Santi dijo:

      En mi caso estoy trabajando con Mybatis también, tengo un metodo en el controller que llama a varios servicios, cada uno de ellos borrar o inserta en BBDD, este método lo tengo con el @Transactional y controlando el rollbackFor = Exception.class para que haga el rollback en cualquier excepcion… pero no lo hace… veo que por cada insert o delete se habre una sqlSession, la cual hace commit segun termina… por lo tanto si mas adelante cualquiera de los deletes o los inserts falla, el commit ya esta hecho de las operaciones anteriores…
      Que es lo que pasa?? no funciona el Transactional, no veo ninguna traza que indique nada acerca del rollback 😦

      • igochan dijo:

        Hola, el problema de la anotación @Transactional es que si hay algo mal configurado no te avisa, simplemente la ignora. Comprueba que la configuración es la correcta.
        Segundo, lo más evidente. Si dentro de tu controller haces catch de la eception y no la dejas salir del método, nunca te haría un rollback. Comprueba que la excepción sale, porque por lo que me comentas no parece que lo esté haciendo.
        Si no es nada de todo esto, la verdad es que desconozco como funciona Mybatis. En Hibernate, si no hay una transacción abierta al hacer un insert te salta una excepción lo cual te deja ver que no has configurado bien Spring. Pero si haces una consulta y no hay transacción abierta, hibernate crea una transacción para la consulta y luego la cierra él mismo. El comportamiento que me comentas es parecido. ¿Quizás MyBatis abre el mismo la transacción al no encontrar ninguna? Siento no poder ayudarte más.
        Saludos

      • Santi dijo:

        Creo que los tiros pueden ir por aqui… http://stackoverflow.com/questions/8176465/mybatis-spring-setup-not-using-transactions
        Pero no según dice como lo resolvió, haciendo un «spring-managed bean» el método anotado con @Transactional… como se hace eso??

        Por otra parte, voy a intentar explicar mejor como tengo montado el tinglao. De modo bastante resumido.

        Tengo un controller, con los siguientes metodos:

        ————————————————————————————————————–
        public void metodo1() {
        metodo2()
        }

        @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
        public metodo2() throws IllegalStateException, IOException, SQLException, Exception {
        borrarDatos();
        insertarInsertarDatos();
        }

        public borrarDatos() throws IllegalStateException, IOException, SQLException, Exception{
        try {
        servicio.borraDatos();
        } catch (Exception e){
        System.out.println(«error al borrar datos»):
        throw e;
        }
        }

        public insertarDatos() throws IllegalStateException, IOException, SQLException, Exception{
        try {
        servicio.insertaDatos();
        } catch (Exception e){
        System.out.println(«error al insertar datos»):
        throw e;
        }
        }

        ————————————————————————————————————-

        En mi caso, tengo los datos preparados para que al insertar, salte sqlException, por lo tanto deberia hacer un rollback de los datos que ha borrado, pero no lo hace, se borran los datos y no se inserta ninguno….

        Gracias!

  5. Santi dijo:

    Y este es mi fichero de configuracion

    • igochan dijo:

      Hola. Estas llamando a método2 desde método1. La anotación @Trandactional ha de estar siempre en el método de entrada a la clase, en tu caso método1. Piensa que las clases de Spring son proxies. Esta explicado casi al principio del tutorial.
      Saludos

  6. hector dijo:

    Hola, esta genial el tutorial, me gustaría saber si no tienes un ejemplo completo porque en mi caso no se como deberían estar los DAO’s y donde poner la configuración de Spring para que acepte las transacciones.

    Saludos

Deja un comentario