Propagação e isolamento de transações na primavera @Transactional
1. Introdução
Neste tutorial, abordaremos a anotação _ @ Transactional_ e suas configurações de isolamento e propagation.
2. O que é _ @ Transactional? _
Podemos usar _https://www..com/transaction-configuration-with-jpa-and-spring [@Transactional] _ para quebrar um método em uma transação de banco de dados.
Permite definir condições de propagação, isolamento, tempo limite, somente leitura e reversão para nossa transação. Além disso, podemos especificar o gerenciador de transações.
2.1. _ @ Transacional_ Detalhes da implementação
O Spring cria um proxy ou manipula o código de bytes da classe para gerenciar a criação, confirmação e reversão da transação. No caso de um proxy, o Spring ignora @ Transactional em chamadas de método internas.
Simplificando, se tivermos um método como callMethod e o marcarmos como _ @ Transactional, _ Spring envolverá algum código de gerenciamento de transações em torno da chamada: _ @ Transactional_ method chamado:
createTransactionIfNecessary();
try {
callMethod();
commitTransactionAfterReturning();
} catch (exception) {
completeTransactionAfterThrowing();
throw exception;
}
2.2. Como usar _ @ Transactional_
Podemos colocar a anotação nas definições de interfaces, classes ou diretamente nos métodos. Eles se anulam de acordo com a ordem de prioridade; do menor para o maior, temos: Interface, superclasse, classe, método de interface, método de superclasse e método de classe.
*Spring aplica a anotação em nível de classe a todos os métodos públicos dessa classe que não anotamos com __ @ Transactional__.*
*No entanto, se colocarmos a anotação em um método privado ou protegido, o Spring a ignorará sem erro.*
Vamos começar com um exemplo de interface:
@Transactional
public interface TransferService {
void transfer(String user1, String user2, double val);
}
Geralmente, não é recomendável definir _ @ Transactional_ na interface. No entanto, é aceitável para casos como _ @ Repository_ com Spring Data. Podemos colocar a anotação em uma definição de classe para substituir a configuração de transação da interface/superclasse:
@Service
@Transactional
public class TransferServiceImpl implements TransferService {
@Override
public void transfer(String user1, String user2, double val) {
//...
}
}
Agora vamos substituí-lo, definindo a anotação diretamente no método:
@Transactional
public void transfer(String user1, String user2, double val) {
//...
}
3. Propagação de Transação
A propagação define o limite de transação da nossa lógica de negócios. O Spring consegue iniciar e pausar uma transação de acordo com nossa configuração propagation.
- O Spring chama _TransactionManager
-
getTransaction_ para obter ou criar uma transação de acordo com a propagação. Ele suporta algumas propagações para todos os tipos de TransactionManager, mas há algumas delas suportadas apenas por implementações específicas de TransactionManager.
Agora vamos analisar as diferentes propagações e como elas funcionam._ + _
3.1. REQUIRED Propagação
REQUIRED é a propagação padrão. O Spring verifica se existe uma transação ativa e, em seguida, cria uma nova, se nada existir. Caso contrário, a lógica de negócios será anexada à transação ativa no momento:
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) {
//...
}
Além disso, como REQUIRED é a propagação padrão, podemos simplificar o código descartando-o:
@Transactional
public void requiredExample(String user) {
//...
}
Vamos ver o pseudo-código de como a criação de transações funciona para a propagação REQUIRED:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return createNewTransaction();
3.2. SUPPORTS Propagação
Para SUPPORTS, o Spring primeiro verifica se existe uma transação ativa. Se existir uma transação, a transação existente será usada. Se não houver uma transação, ela será executada não transacional:
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) {
//...
}
Vamos ver o pseudocódigo da criação da transação para SUPPORTS:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return emptyTransaction;
3.3. MANDATORY Propagation
Quando a propagação for MANDATORY, se houver uma transação ativa, ela será usada. Se não houver uma transação ativa, o Spring lançará uma exceção:
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) {
//...
}
E vamos ver novamente o pseudo-código:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
throw IllegalTransactionStateException;
3.4. *Propagação NEVER
Para lógica transacional com propagação NEVER, o Spring lança uma exceção se houver uma transação ativa:
@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) {
//...
}
Vamos ver o pseudo-código de como a criação de transações funciona para a propagação NEVER:
if (isExistingTransaction()) {
throw IllegalTransactionStateException;
}
return emptyTransaction;
3.5.* NOT_SUPPORTED Propagation *
O Spring inicialmente suspende a transação atual, se existir, a lógica de negócios é executada sem uma transação.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
//...
}
*O _JTATransactionManager_ suporta a suspensão real de transações pronta para uso. Outros simulam a suspensão mantendo uma referência à existente e limpando-a do contexto do encadeamento*
3.6. REQUIRES_NEW Propagação
Quando a propagação é REQUIRES_NEW, o Spring suspende a transação atual, se existir, e cria uma nova:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) {
//...
}
*Semelhante a _NOT_SUPPORTED_, precisamos do _JTATransactionManager_ para suspensão real da transação.*
E o pseudo-código é assim:
if (isExistingTransaction()) {
suspend(existing);
try {
return createNewTransaction();
} catch (exception) {
resumeAfterBeginException();
throw exception;
}
}
return createNewTransaction();
3.7. NESTED Propagação
Para propagação NESTED, o Spring verifica se existe uma transação e, se sim, marca um ponto de salvamento. Isso significa que, se nossa execução da lógica de negócios gerar uma exceção, as reversões de transações serão realizadas nesse ponto de salvamento. Se não houver uma transação ativa, ela funcionará como REQUIRED.
*_DataSourceTransactionManager_ suporta essa propagação pronta para uso. Além disso, algumas implementações de _JTATransactionManager_ podem oferecer suporte a isso.*
*https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/jpa/JpaTransactionManager.html [_JpaTransactionManager_] suporta _NESTED_ apenas para conexões JDBC. No entanto, se definirmos o sinalizador _nestedTransactionAllowed_ como _true_, ele também funcionará para o código de acesso JDBC nas transações JPA se o nosso driver JDBC suportar pontos de salvamento. +*
Por fim, vamos definir a propagation como NESTED:
@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) {
//...
}
4. Isolamento de transação
O isolamento é uma das propriedades comuns do ACID: Atomicidade, Consistência, Isolamento e Durabilidade. O isolamento descreve como as alterações aplicadas pelas transações simultâneas são visíveis entre si.
Cada nível de isolamento evita zero ou mais efeitos colaterais de simultaneidade em uma transação:
-
Leitura suja: leia a alteração não confirmada de uma transação simultânea
-
Leitura não repetível : obtém um valor diferente na releitura de uma linha se uma transação simultânea atualizar a mesma linha e confirmar
-
Leitura fantasma: obtenha linhas diferentes após a reexecução de uma consulta de intervalo, se outra transação adicionar ou remover algumas linhas do intervalo e confirmar
- Podemos definir o nível de isolamento de uma transação por _ @ Transactional
-
isolamento._ Ele possui essas cinco enumerações no Spring: DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE.
4.1. Gerenciamento de isolamento na primavera
O nível de isolamento padrão é DEFAULT. Portanto, quando o Spring criar uma nova transação, o nível de isolamento será o isolamento padrão do nosso RDBMS. Portanto, devemos ter cuidado se alterarmos o banco de dados.
- Também devemos considerar casos em que chamamos uma cadeia de métodos com isolamento diferente . No fluxo normal, o isolamento só se aplica quando uma nova transação é criada. Portanto, se por algum motivo não queremos permitir que um método seja executado em isolamento diferente, precisamos definir _TransactionManager
-
setValidateExistingTransaction_ como true. Em seguida, o pseudo-código de validação da transação será:
if (isolationLevel != ISOLATION_DEFAULT) {
if (currentTransactionIsolationLevel() != isolationLevel) {
throw IllegalTransactionStateException
}
}
Agora vamos nos aprofundar em diferentes níveis de isolamento e seus efeitos.
4.2. READ_UNCOMMITTED Isolação
READ_UNCOMMITTED é o nível de isolamento mais baixo e permite o acesso simultâneo.
Como resultado, ele sofre dos três efeitos colaterais de simultaneidade mencionados. Portanto, uma transação com esse isolamento lê dados não confirmados de outras transações simultâneas. Além disso, podem ocorrer leituras não repetíveis e fantasmas. Assim, podemos obter um resultado diferente na releitura de uma linha ou na reexecução de uma consulta de intervalo.
Podemos definir o nível isolation para um método ou classe:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
//...
}
*O Postgres não suporta isolamento _READ_UNCOMMITTED_ e volta para _READ_COMMITED _.* * *Além disso, o Oracle não suporta e permite _READ_UNCOMMITTED_. +*
4.3. READ_COMMITTED Isolação
O segundo nível de isolamento, READ_COMMITTED, impede leituras sujas.
O restante dos efeitos colaterais da simultaneidade ainda podem ocorrer. Portanto, alterações não confirmadas nas transações simultâneas não têm impacto sobre nós, mas, se uma transação confirmar suas alterações, nosso resultado poderá ser alterado mediante nova consulta.
Aqui, definimos o nível isolation:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
//...
}
*_READ_COMMITTED_ é o nível padrão do Postgres, SQL Server e Oracle.*
4.4. REPEATABLE_READ Isolação
O terceiro nível de isolamento, REPEATABLE_READ, , impede leituras sujas e não repetíveis. Portanto, não somos afetados por alterações não confirmadas nas transações simultâneas.
Além disso, quando consultamos novamente uma linha, não obtemos um resultado diferente. Porém, na reexecução de consultas de intervalo, podemos obter linhas adicionadas ou removidas recentemente.
Além disso, é o nível mais baixo necessário para evitar a atualização perdida. A atualização perdida ocorre quando duas ou mais transações simultâneas lêem e atualizam a mesma linha. REPEATABLE_READ não permite acesso simultâneo a uma linha. Portanto, a atualização perdida não pode acontecer.
Aqui está como definir o nível de isolation para um método:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void log(String message){
//...
}
*_REPEATABLE_READ_ é o nível padrão no Mysql.* *_* O Oracle não suporta *REPEATABLE_READ _.*
4.5. SERIALIZABLE Isolação
SERIALIZABLE é o nível mais alto de isolamento. Ele evita todos os efeitos colaterais de simultaneidade mencionados, mas pode levar à menor taxa de acesso simultâneo, porque executa sequências simultâneas.
Em outras palavras, a execução simultânea de um grupo de transações serializáveis tem o mesmo resultado que a execução em série.
Agora vamos ver como definir SERIALIZABLE como o nível isolation:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
//...
}
5. Conclusão
Neste tutorial, exploramos a propriedade de propagação de _ @ Transaction_ em detalhes. Depois, aprendemos sobre os efeitos colaterais da simultaneidade e os níveis de isolamento.
Como sempre, você pode encontrar o código completo over no GitHub.