spring - transaction - Primavera @ Propagación transaccional de solo lectura
spring transaction propagation (4)
En primer lugar, dado que Spring no hace la persistencia en sí misma, no puede especificar qué significa exactamente readOnly
exactamente. Este atributo es solo una sugerencia 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 sesión de hibernación actual, lo que FlushMode.NEVER
que la sesión FlushMode.NEVER
la transacción.
Además, se llamará a setReadOnly (verdadero) en la conexión JDBC, que también es una sugerencia 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
. 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 confirma, y luego se reanuda la primera transacción.
OK, ya casi estamos allí. Veamos qué trae a readOnly
en este escenario.
Si un método en una transacción de lectura / escritura llama a un método que requiere una transacción readOnly , el primero debería suspenderse, porque de lo contrario se produciría un vaciado / confirmación al final del segundo método.
A la inversa, si llama a un método desde una transacción readOnly que requiere lectura / escritura , nuevamente, el primero se suspenderá, ya que no se puede vaciar / confirmar, y el segundo método lo necesita.
En los casos readOnly-to-read-Only , y read / write-to-read / write , la transacción externa no necesita ser suspendida (a menos que especifique la propagación de lo contrario, obviamente).
Estoy experimentando con el uso del patrón de comando para permitir que mi capa web funcione con entidades de Hibernación en el contexto de una sola transacción (evitando así las excepciones de carga lenta). Sin embargo, ahora estoy confundido con cómo debo lidiar con las transacciones.
Mis comandos llaman a los métodos de capa de servicio que están anotados con @Transactional
. Algunos de estos métodos de capa de servicio son de solo lectura, por ejemplo, @Transactional(readOnly=true)
, y algunos son de lectura / escritura.
Mi capa de servicio expone un controlador de comandos que ejecuta los comandos que se le pasan en nombre de la capa web.
@Transactional
public Command handle( Command cmd ) throws CommandException
Supongo que estoy en lo cierto al hacer transaccional el método handle()
del controlador de comandos. Aquí es donde entra la confusión. Si la implementación de un comando hace llamadas a múltiples métodos de la capa de servicio, no hay forma de que el controlador de comandos sepa si las operaciones a las que se llama 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 readOnly=true
que hacer que el método handle()
readOnly=true
, entonces, ¿qué sucede si el comando llama a un método de capa de servicio que se anota con @Transactional(realOnly=false)
?
Apreciaría una mejor comprensión de esto y agradecería sus comentarios ...
Andrés
Llamar a readOnly = false desde readOnly = true no funciona ya que la transacción anterior continúa.
En su ejemplo, el método handle () en su capa de servicio está iniciando una nueva transacción de lectura-escritura. Si el método de manejo a su vez llama a los métodos de servicio que anotaron solo lectura, el de solo lectura no tendrá efecto ya que participará en la transacción de lectura-escritura existente.
Si es esencial que esos métodos sean de solo lectura, puede anotarlos con Propagation.REQUIRES_NEW, y luego iniciarán una nueva transacción de solo lectura en lugar de participar en la transacción de lectura-escritura existente.
Este es un ejemplo práctico: CircuitStateRepository es un repositorio JPA de datos de primavera.
BeanS llama a una transacción = Bean1 de solo lectura, que realiza una búsqueda y llama a una transacción = lectura-escritura Bean2 que guarda un nuevo objeto.
- Bean1 inicia un tx de solo lectura.
31 09: 39: 44.199 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Creando una nueva transacción con el nombre [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly; ''''
- El frijol 2 participa en él.
31 09: 39: 44.230 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Participando en una transacción existente
Nada está comprometido con la base de datos.
Ahora cambie Bean2 @Transactional
anotación @Transactional
para agregar propagation=Propagation.REQUIRES_NEW
- Bean1 inicia un tx de solo lectura.
31 09: 31: 36.418 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Creando una nueva transacción con el nombre [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly; ''''
- Bean2 comienza una nueva lectura-escritura tx
31 09: 31: 36.449 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Suspendiendo la transacción actual, creando una nueva transacción con el nombre [nz.co.vodafone.wcim.business.Bean2.createSomething]
Y los cambios realizados por Bean2 ahora están comprometidos con la base de datos.
Aquí está el ejemplo, probado con datos de primavera, hibernación y oráculo.
@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);
}
}
Parece ignorar la configuración de la transacción activa actual, solo aplica la configuración a 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.
Por defecto, la propagación de la transacción es REQUERIDA, lo que significa que la misma transacción se propagará de un llamante transaccional a un destinatario transaccional. En este caso también se propagará el estado de sólo lectura. Por ejemplo, si una transacción de solo lectura llamará a una transacción de lectura-escritura, toda la transacción será de solo lectura.
¿Podría usar el patrón Abrir sesión en vista para permitir la carga diferida? De esa manera, su método de manejo no necesita ser transaccional en absoluto.