2009-10-23 28 views
33

Estoy experimentando con el uso del patrón de comando para permitir que mi capa web funcione con entidades Hibernate dentro del contexto de una sola transacción (evitando así las excepciones de carga lenta). Sin embargo, estoy confundido ahora con la forma en que debo lidiar con las transacciones.Spring @Transactional solo lectura propagación

Mis comandos llaman a los métodos de capa de servicio anotados con anotaciones @Transactional. Algunos de estos métodos de capa de servicio son de solo lectura, p. @Transactional(readOnly=true) - y algunos son de lectura/escritura.

Mi capa de servicio expone un controlador de comandos que ejecuta los comandos pasados ​​a él en nombre de la capa web.

@Transactional 
public Command handle(Command cmd) throws CommandException 

Asumo que tengo razón en la toma del controlador de comandos handle() método transaccional. Aquí es donde entra la confusión. Si la implementación de un comando realiza llamadas a múltiples métodos de capa de servicio, el manejador de comandos no tiene forma de saber si las operaciones llamadas dentro del comando serán de solo lectura, lectura/escritura o una combinación de los dos.

No entiendo cómo funciona la propagación en este ejemplo. Si tuviera que hacer el método handle()readOnly=true, ¿qué ocurre si el comando llama entonces a un método de capa de servicio anotado con @Transactional(realOnly=false)?

lo agradecería una mejor comprensión de esto y sus comentarios ...

Andrew

+1

Entonces, ¿cuál de las dos respuestas contradictorias es cierto? ¿Alguien se ha molestado en verificar? – LuGo

+0

Dado que 'handle()' _may_ call métodos que escriben, la transacción debe permitir escrituras. Eso estaría bien y correcto como una solución. Si realmente quisiera, podría investigar el inicio programático de TX y cambiar solo a read_ly, quizás a través de un atributo de Command, pero dudo seriamente que valga la pena el esfuerzo. –

Respuesta

51

En primer lugar, desde la primavera no hace persistencia en sí, no se puede especificar lo que debe significar exactamente readOnly . Este atributo es solo una pista para el proveedor, el comportamiento depende, en este caso, de Hibernate.

Si especifica readOnly como true, el modo de descarga se establecerá como FlushMode.NEVER en la actual sesión de Hibernate prevención de la sesión de confirmar la transacción. También se llamará a setReadOnly (verdadero) en la conexión JDBC, que también es una pista para la base de datos subyacente. Si su base de datos lo admite (lo más probable es que lo haga), esto tiene básicamente el mismo efecto que FlushMode.NEVER, pero es más fuerte ya que ni siquiera puede vaciar manualmente.

Ahora veamos cómo funciona la propagación de transacciones.

Si no establece explícitamente readOnly en true, tendrá transacciones de lectura/escritura. Dependiendo de los atributos de la transacción (como REQUIRES_NEW), a veces su transacción se suspende en algún momento, se inicia una nueva y finalmente se compromete, y después de eso se reanuda la primera transacción.

Bien, ya casi llegamos. Veamos qué trae readOnly en este escenario.

Si un método de una lectura /escribir transacción llama a un método que requiere una transacción readOnly, el primero debe suspenderse, porque de lo contrario un rubor/commit que sucedería al final del segundo método.

Por el contrario, si se llama a un método desde dentro de un transacción readOnly que requiere de lectura/escritura, de nuevo, se suspenderá el primero, ya que no se puede lavar/comprometido, y el segundo método necesita eso.

En el readOnly-a-readOnly, y el de lectura/escritura de leer/escribir casos la transacción externa no necesita ser suspendido (a menos que se especifique de otro modo de propagación, obviamente).

+0

¿Estás seguro? ¿Será "de solo lectura" realmente anular la política de propagación especificada? Me costó encontrar referencias, pero al menos esta publicación dice lo contrario: http://imranbohoran.blogspot.ch/2011/01/spring-transactions-readonly-whats-it.html –

+11

Si llamas a un bean que tiene solo lectura, y luego este bean llama a otro bean con lectura-escritura, una nueva transacción no se inicia, el segundo bean participa en la transacción existente de solo lectura, y los cambios que realiza el segundo bean no se comprometen. –

+7

Incorrecto: como @dancarter dice, un método ** de lectura/escritura ** llamado dentro de una transacción ** readOnly ** no se comprometerá en forma silenciosa, al menos con la integración de Hibernate de Spring. Dado que el interceptor TX más externo es de solo lectura, la sesión de Hibernate nunca se vacía ... y no se ejecutan actualizaciones de SQL. (Eso es con los atributos de propagación predeterminados; puede intentar 'REQUIRES_NEW', pero no es la solución correcta para la mayoría de los escenarios.) –

8

Por defecto, la propagación de la transacción es OBLIGATORIA, lo que significa que la misma transacción se propagará de una llamada transaccional a una llamada en transacción. En este caso, también se propagará el estado de solo lectura. P.ej. si una transacción de solo lectura llamará a una transacción de lectura y escritura, toda la transacción será de solo lectura.

¿Podría utilizar el patrón Abrir sesión en vista para permitir la carga diferida? De esta forma, su método de manejo no necesita ser transaccional en absoluto.

+1

Como dice @sijk, el estado de solo lectura se propaga hacia adentro, sin advertencias ni diagnósticos de por qué Hibernate no 'commit :( –

+0

Estoy usando jpa + hibernate + spring, y en el caso donde una transacción de solo lectura llamó una transacción readwrite y toda la acción estaba en la transacción readwrite, las entidades que fueron persistentes fueron confirmadas pero las entidades fueron alteradas por getters/setters no se comprometieron. Bastante confuso. –

9

Es bastante fácil probar la propagación.

BeanS llama a un transaccional = solo lectura Bean1, que hace una búsqueda y llama a transactional = read-write Bean2 que guarda un nuevo objeto.

  • Bean1 inicia un tx de solo lectura.

31 09: 39: 44,199 [piscina-1-hilo-1] DEBUG osorm.jpa.JpaTransactionManager - Crear nueva transacción con el nombre [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly; ''

  • Bean 2 pariticipates en él.

31 09: 39: 44,230 [piscina-1-hilo-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Participar en transacción existente

Nada se ha comprometido a la base de datos.

Ahora cambiar Bean2 @Transactional anotación para añadir propagation=Propagation.REQUIRES_NEW

  • Bean1 inicia un tx de sólo lectura.

31 09: 31: 36,418 [piscina-1-hilo-1] DEBUG osorm.jpa.JpaTransactionManager - Crear nueva transacción con el nombre [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly; ''

  • Bean2 inicia una nueva lectura-escritura tx

31 09: 31: 36,449 [piscina-1-hilo-1] DEBUG osorm.jpa.JpaTransactionManager - Suspensión de transacción actual, creando nueva transacción con nombre [nz.co.vodafone.wcim.business.Bean2.createSomething]

Y los cambios realizados por Bean2 ahora se confirman en la base de datos.

Aquí está el ejemplo, probado con spring-data, hibernate y oracle.

@Named 
public class BeanS { 

@Inject 
Bean1 bean1; 

@Scheduled(fixedRate = 20000) 
public void runSomething() { 
    bean1.startSomething(); 
} 

} 


@Named 
@Transactional(readOnly = true) 
public class Bean1 { 

Logger log = LoggerFactory.getLogger(Bean1.class); 

@Inject 
private CircuitStateRepository csr; 

@Inject 
private Bean2 bean2; 

public void startSomething() { 

    Iterable<CircuitState> s = csr.findAll(); 
    CircuitState c = s.iterator().next(); 
    log.info("GOT CIRCUIT {}", c.getCircuitId()); 
    bean2.createSomething(c.getCircuitId()); 

} 

} 


@Named 
@Transactional(readOnly = false) 
public class Bean2 { 

    @Inject 
    CircuitStateRepository csr; 

    public void createSomething(String circuitId) { 

     CircuitState c = new CircuitState(circuitId + "-New-" + new DateTime().toString("hhmmss"), new DateTime()); 

     csr.save(c); 

    } 


} 
4

Se parecen ignorar la configuración de la transacción activa actual, sólo se aplica a la configuración de una nueva transacción:

 
org.springframework.transaction.PlatformTransactionManager 
TransactionStatus getTransaction(TransactionDefinition definition) 
         throws TransactionException 
Return a currently active transaction or create a new one, according to the specified propagation behavior. 
Note that parameters like isolation level or timeout will only be applied to new transactions, and thus be ignored when participating in active ones. 
Furthermore, not all transaction definition settings will be supported by every transaction manager: A proper transaction manager implementation should throw an exception when unsupported settings are encountered. 
An exception to the above rule is the read-only flag, which should be ignored if no explicit read-only mode is supported. Essentially, the read-only flag is just a hint for potential optimization.
Cuestiones relacionadas