Usos de @Transactional

Buenas tardes

Alguien podría darme algunos consejos prácticos sobre @Transactional de spring?

Dónde se suele utilizar regularmente la anotación, en las interfaces o en las implementaciones de esas interfaces?
Qué sentido tiene @Transactional(readonly=true), se supone que si una transacción es readonly, no tiene porqué hacer rollback, entonces qué sentido tiene hacer una transacción que es de solo lectura??

No sería mejor poner todos mis métodos como @Transactional?

Muchas gracias por su tiempo y ayuda

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

implementaciones

La anotación es mejor ponerla en las implementaciones de tus interfaces. Puedes anotar los métodos que necesites. Si tienes una clase que quieres que todos sus métodos se ejecuten en una transacción, entonces simplemente ponle la anotación a la clase y no a cada método. PERO toda en cuenta que en tiempo de ejecución se crea un proxy (por default un proxy dinámico de Java 6), por eso es importante implementar una interfaz, y que el proxy solamente puede interceptar las llamadas que vienen de fuera del objeto. Si necesitas interceptar llamadas internas, tienes que usar AspectJ. Por ejemplo si tienes algo así:

 

Si tienes una referencia a una instancia de esta clase, administrada por Spring, entonces realmente tienes una referencia a un proxy. Si invocas por ejemplo  , eso se ejecutará en una transacción. Pero si ejecutas  , ese método no está anotado por lo que no se crea una transacción al invocarlo; y una vez dentro de ese método se invoca   pero no se crea la transacción porque la invocación ya es desde dentro del objeto y el proxy no se da cuenta. Hay que tener cuidado con eso (una manera de arreglar esta situación particular, es anotar   con  ).

En cuanto a las transacciones de sólo lectura, su utilidad reside en la validez de los datos que vas a leer. Suponte que tienes una tabla donde manejas saldos o algo así. Los métodos que afectan los saldos obviamente deben ser transaccionales, para que solamente uno de esos métodos pueda afectar el saldo a la vez, y en un ambiente con concurrencia, pues si una transacción lee el registro X, cualquier otra transacción que quiera leer el registro X tendrá que esperar a que la primera transacción termine para poder leer el saldo de ese registro.

Si una consulta de saldo no la haces transaccional, puede que la lectura de todas maneras se bloquee porque hay una transacción modificándolo. Si la consulta de saldo la marcas como una transacción de sólo lectura, tu RDBMS puede permitirle a esa transacción leer el saldo de un registro aunque otra transacción lo esté modificando (esto depende del aislamiento con que definas la transacción de sólo lectura y creo que también la transacción que está afectando el saldo).

Imagen de neko069

Yo alguna leí la propagación

Yo alguna leí la propagación de transacción de la siguiente forma, existen los tipos

MANDATORY : Participa de la transacción. Tira una excepción si no existe.
NEVER : Se ejecuta sin transacción. Tira una excepción si existe una.
NOT_SUPPORTED : Se ejecuta sin transacción. Suspende la transacción si existe una.
REQUIRED : Participa de la transacción. Crea una nueva si no existe.
REQUIRES_NEW : Crea una nueva transacción siempre. Suspende la transacción actual si existe.
SUPPORTS : Participa de la transacción si existe. Se ejecuta sin transacción si no existe.

Según, si en tus métodos que recuperan datos, anotas con

 

estarías haciendo que tu recuperado de datos participe en una transacción si la hay, de no ser así
no se crea transacción alguna, así que, como dice @ezamudio, depende del nivel de aislamiento que
requieras

Imagen de ezamudio

propagacion != aislamiento

Propagacion se refiere a que se crea una transaccion si es necesario, o se usa una si existe, o se crea una anidada siempre, o se usa la existente solo si existe y si no existe una pues no se usa nada...

El aislamiento se refiere a la validez de los datos que lees. Que si por ejemplo se permite una "lectura sucia", es decir leer un registro aun cuando otra transaccion activa lo esta modificando, y se obtienen los datos que ya posiblemente son invalidos (una copia vieja por verlo de alguna forma). O esperar a que la otra transaccion desbloquee el registro para obtener los datos validos; y por otra parte, una transaccion de solo lectura no va a bloquear datos para las otras transacciones, mientras que si se marca como una transaccion normal, los registros que lea quedaran bloqueados para otras transacciones.

Imagen de neko069

Ups!!

Tienes razón... me confundí de concepto, entonces los niveles de aislamiento, se encontrarían en la propiedad

 

Donde nivel se refiere a: :

DEFAULT: Utiliza el nivel establecido en el SMBD
READ_COMMITED: éste nivel, hace que la transacción sólo lea registros que ya estén guardados
READ_UNCOMMITED:éste nivel, hace lo contrario que el anterior, te permite lecturas sobre registros que aún no estén guardados (lecturas sucias), puede provocar que tus consultas contengan registros inválidos
REPEATABLE_READ:éste nivel prohibe lecturas sobre filas que no tengan cambios guardados, también prohibe la situación donde una transacción lee un registro, una segunda transacción altera el registro, y la primera transacción vuelve a leer el registro, obteniendo así diferentes valores la segunda ocasión.
SERIALIZABLE: éste nivel prohíbe lecturas sucias, lecturas repetibles y lecturas fantasma, la situación donde se hace una consulta, se obtiene una serie de registros, y una transacción inserta un nuevo registro donde se satisface la condición WHERE de la consulta, el nuevo registro sería el fantasma.

Mea culpa, pero bueno, aquí tienes los niveles existentes...

Gracias por la corrección, si volví a regar el tepache, háganmelo saber, yo sólo recuerdo ésos niveles...

Duda propagación en las transacciones de spring

Hola ezamudio, muchas gracias por tu interés y tu tiempo para ayudarme

@ezamudio:

Pero si ejecutas ejemplo.metodo2(), ese método no está anotado por lo que no se crea una transacción al invocarlo; y una vez dentro de ese método se invoca metodo1 pero no se crea la transacción porque la invocación ya es desde dentro del objeto y el proxy no se da cuenta

Según entiendo se creará una transacción al invocar metodo1 porque por default propagation=PROPAGATION_REQUIRED lo cual significa que creará una nueva transacción si no existe una; pero tú dices que no, estoy un poco confundido...
Gracias nuevamente por tu ayuda

Imagen de ezamudio

No

Lee nuevamente lo que escribí. Si ejecutas metodo2(), ese método no está anotado. El proxy no detecta que haya que iniciar ninguna transacción, simplemente le pasa el control a tu código. Tu código invoca metodo1() desde metodo2(), pero no hay transacción ni se va a iniciar nada mágicamente porque a estas alturas la magia ya pasó, la hacía el proxy que interceptó la llamada a metodo2().

Ok, el proxy

Ok, lo que me confunde es que yo suponía el proxy debería detectar el @Transactional de método1 (al mirar que no hay una transacción debería crear una).

Sin embargo con tu explicación entiendo que por alguna razón el proxy deja de ver eso...
Eso quiere decir que sería conveniente marcar todos mis métodos con @Transactional, los de busqueda como @Transactional(readonly=true) y los de escritura con @Transactional(readonly=false) ?

Gracias nuevamente

Imagen de ezamudio

Otra vez...

Imagínate el proxy como un disfraz que se le pone encima a tu componente. El proxy implementa la misma interfaz de tu componente, por lo cual puede hacerse pasar por tu componente y los demás componentes que hacen referencia a la interfaz pues ni cuenta se dan. El proxy recibe entonces las llamadas a los métodos que los otros componentes quieren invocar de tu componente; ahí verifica si el método invocado ya en tu componente tiene la anotación   y si es así, entonces crea una transacción, invoca tu método, y al final si no se arroja ninguna RuntimeException, hace commit a esa transacción, o si se arrojó una RuntimeException, pues hace rollback.

En mi ejemplo original,   está anotado como transaccional pero   no está anotado. Entonces, si invocas metodo2 desde fuera, el proxy no abre ninguna transacción porque el método no está anotado. Y si el código de metodo2 invoca a   entonces no hay transacción, porque eso ya ocurre dentro de tu componente, el proxy nunca se da cuenta de esa invocación. El proxy solamente intercepta llamadas que sean de componentes externos directamente a   y en ese caso ejecuta el método dentro de una transacción.

El proxy no es mágico, no puede darse cuenta de que el código de tu componente en   internamente llama a  . La única manera de lograr eso sería con mecanismos más sofisticados de AspectJ y/o CGLIB, donde no se usa un proxy que envuelva a tu componente, sino que se modifica el bytecode de tu clase en tiempo de ejecución para meter código al principio y al final de cada método. En ese caso, cuando tu metodo2 llame a metodo1 sí se abriría una transacción porque el código para abrir y cerrar la transacción fue insertado al principio y al final del metodo1. Pero un proxy dinámico no puede hacer eso.

Ahora sí ya?

Imagen de ezamudio

readonly, etc

De lo último que dices, pues es importante marcar un método con   si necesitas que los datos que se lean ahí dentro sean lo más actual (por ejemplo el saldo de una cuenta bancaria; si otro proceso está afectando ese saldo, quieres que la consulta se bloquee hasta que esa otra transacción termine para que entonces puedas leer el saldo actualizado, de otra forma leerías el saldo que estaba antes de esa afectación). Los de escritura deben ir con readonly=false pero ese es el default así que no es necesario especificarlo.

Pero nuevamente: si metodo2 lo anotaras con transaccionalidad de sólo lectura, y usas proxy dinámico, entonces si metodo2 invoca metodo1 el metodo1 se ejecutará únicamente dentro de la transacción de sólo lectura que fue creada para metodo2.

Imagen de ezamudio

solución

Es importante que entiendas el problema para que puedas entender la solución. Si usas proxy dinámico (que es el default de Spring, a menos que detecte que tienes AspectJ y CGLIB y modifiques algunas cosas en la configuración), entonces debes tener cuidado de que los métodos de tu componente transaccional sean aislados. Que un método transaccional solamente invoque internamente a otros métodos que requieran la mismita transaccionalidad y los métodos no transaccionales no invoquen a métodos transaccionales (pero los métodos transaccionales pueden invocar métodos no transaccionales sin ningún problema).

Es decir, que tendrías que hacer algún refactoring para que   no invoque  , sino que desde afuera invoques a metodo2 y posteriormente a metodo1, de modo que esa segunda invocación sí cause que el metodo1 se ejecute dentro de una transacción.

Menuda respuesta joven !

Muchas gracias por tu tiempo y tus respuestas. Ahora mismo hago ejemplos para entender lo que me explicas, en cuanto tenga resultados los posteo

...

...