Spring @Transactional - isolamento, propagação

447

Alguém pode explicar para que servem os parâmetros de isolamento e propagação na @Transactionalanotação via exemplo do mundo real?

Basicamente, quando e por que devo escolher alterar seus valores padrão.

Mat B.
fonte

Respostas:

442

Boa pergunta, embora não seja trivial de responder.

Propagação

Define como as transações se relacionam. Opções comuns:

  • Required: O código sempre será executado em uma transação. Cria uma nova transação ou reutiliza uma, se disponível.
  • Requires_new: O código sempre será executado em uma nova transação. Suspende a transação atual, se houver.

Isolamento

Define o contrato de dados entre transações.

  • Read Uncommitted: Permite leituras sujas.
  • Read Committed: Não permite leituras sujas.
  • Repeatable Read: Se uma linha for lida duas vezes na mesma transação, o resultado será sempre o mesmo.
  • Serializable: Executa todas as transações em uma sequência.

Os diferentes níveis têm características de desempenho diferentes em um aplicativo multithread. Eu acho que se você entender o dirty readsconceito, poderá selecionar uma boa opção.


Exemplo de quando uma leitura suja pode ocorrer:

  thread 1   thread 2      
      |         |
    write(x)    |
      |         |
      |        read(x)
      |         |
    rollback    |
      v         v 
           value (x) is now dirty (incorrect)

Portanto, pode ser um padrão sensato (se é que pode ser reivindicado) Read Committed, que apenas permite ler valores que já foram confirmados por outras transações em execução, em combinação com um nível de propagação de Required. Em seguida, você poderá trabalhar a partir daí se o seu aplicativo tiver outras necessidades.


Um exemplo prático de onde uma nova transação sempre será criada ao entrar na provideServicerotina e concluída ao sair:

public class FooService {
    private Repository repo1;
    private Repository repo2;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void provideService() {
        repo1.retrieveFoo();
        repo2.retrieveFoo();
    }
}

Se tivéssemos usado Required, a transação permaneceria aberta se a transação já estivesse aberta ao entrar na rotina. Observe também que o resultado de a rollbackpode ser diferente, pois várias execuções podem participar da mesma transação.


Podemos facilmente verificar o comportamento com um teste e ver como os resultados diferem com os níveis de propagação:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {

    private @Autowired TransactionManager transactionManager;
    private @Autowired FooService fooService;

    @Test
    public void testProvideService() {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        fooService.provideService();
        transactionManager.rollback(status);
        // assert repository values are unchanged ... 
}

Com um nível de propagação de

  • Requires new: esperaríamos fooService.provideService()era não revertida desde que criou seu próprio sub-transação.

  • Required: esperamos que tudo tenha sido revertido e a loja de backup permaneça inalterada.

Johan Sjöberg
fonte
Como esse último link se relaciona com o que você está falando? De acordo com os documentos vinculados, é a sessão que parece indicar qual é a transação atual, não a fábrica de sessões.
Donal Fellows
@ Donal, desculpe, isso não estava claro. Meu argumento foi que, uma vez que sessionFactory.getCurrentTransaction()foi adicionado, não há mais a necessidade de executar HibernateTemplatepara gerenciar transações. Tirei-:)
Johan Sjöberg
Minha pergunta era exatamente para onde o link estava apontando, na verdade. :-)
Donal Fellows
como obter as alterações feitas no transaction- atual stackoverflow.com/questions/36132667/...
Prasanna Kumar HA
304

PROPAGATION_REQUIRED = 0 ; Se o DataSourceTransactionObject T1 já estiver iniciado para o Método M1, se for necessário um objeto de Transação do Método M2, nenhum novo objeto de Transação será criado. O mesmo objeto T1 será usado para o M2

PROPAGATION_MANDATORY = 2 ; O método deve ser executado dentro de uma transação. Se nenhuma transação existente estiver em andamento, uma exceção será lançada

PROPAGATION_REQUIRES_NEW = 3 ; Se o DataSourceTransactionObject T1 já estiver iniciado para o Método M1 e estiver em andamento (executando o método M1) .Se outro método M2 iniciar a execução, T1 será suspenso pela duração do método M2 com o novo DataSourceTransactionObject T2 para M2.M2 executado em seu próprio contexto de transação

PROPAGATION_NOT_SUPPORTED = 4 ; Se o DataSourceTransactionObject T1 já tiver sido iniciado para o Método M1. Se outro método M2 for executado simultaneamente, o M2 não deverá ser executado no contexto da transação. T1 é suspenso até que M2 seja finalizado.

PROPAGATION_NEVER = 5 ; Nenhum dos métodos é executado no contexto da transação.

Um nível de isolamento: trata-se de quanto uma transação pode ser afetada pelas atividades de outras transações simultâneas . Ele suporta a consistência, deixando os dados em várias tabelas em um estado consistente. Envolve o bloqueio de linhas e / ou tabelas em um banco de dados.

O problema com várias transações

Cenário 1 .Se a transação T1 ler dados da tabela A1 que foram gravados por outra transação simultânea T2.Se no modo T2 for reverso, os dados obtidos por T1 serão inválidos.Eg a = 2 são dados originais.Se T1 ler um = Se a reversão de T2, em seguida, a = 1 será revertida para a = 2 no DB.Mas agora, T1 tem a = 1, mas na tabela DB é alterada para a = 2.

Cenario2 .Se T1 transação lê dados da tabela A1.If outra transação simultânea (T2) atualizar os dados na mesa A1.Then os dados que T1 tenha lido é diferente da tabela A1.Because T2 actualiza os dados na tabela A1.Eg se T1 leia a = 1 e T2 atualizou a = 2.Então a! = b.

Cenário 3. Se a transação T1 ler dados da tabela A1 com determinado número de linhas. Se outra transação simultânea (T2) inserir mais linhas na tabela A1. O número de linhas lidas por T1 será diferente das linhas da tabela A1

O cenário 1 é chamado de leituras sujas.

O cenário 2 é chamado de leituras não repetíveis.

O cenário 3 é chamado de leituras fantasmas.

Portanto, o nível de isolamento é a extensão na qual o cenário 1, cenário 2 e cenário 3 podem ser evitados. Você pode obter um nível completo de isolamento implementando o bloqueio. Isso impede que ocorram leituras e gravações simultâneas nos mesmos dados. Mas isso afeta o desempenho. O nível de isolamento depende de aplicativo para aplicativo quanto de isolamento é necessário.

ISOLATION_READ_UNCOMMITTED : Permite ler alterações que ainda não foram confirmadas. Elas sofrem do cenário 1, cenário 2, cenário 3

ISOLATION_READ_COMMITTED : Permite leituras de transações simultâneas que foram confirmadas. Pode sofrer dos cenários 2 e 3. Como outras transações podem estar atualizando os dados.

ISOLATION_REPEATABLE_READ : Várias leituras do mesmo campo produzirão os mesmos resultados até que sejam alteradas por si só.Pode sofrer com o Cenário 3.Porque outras transações podem estar inserindo os dados

ISOLATION_SERIALIZABLE : o cenário 1, o cenário 2, o cenário 3 nunca acontece. É um isolamento completo. Envolve um bloqueio completo. Afeta o desempenho devido ao bloqueio.

Você pode testar usando

public class TransactionBehaviour {
   // set is either using xml Or annotation
    DataSourceTransactionManager manager=new DataSourceTransactionManager();
    SimpleTransactionStatus status=new SimpleTransactionStatus();
   ;


    public void beginTransaction()
    {
        DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
        // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
        // set is either using xml Or annotation
        manager.setPropagationBehavior(XX);
        manager.setIsolationLevelName(XX);

        status = manager.getTransaction(Def);

    }

    public void commitTransaction()
    {


            if(status.isCompleted()){
                manager.commit(status);
        } 
    }

    public void rollbackTransaction()
    {

            if(!status.isCompleted()){
                manager.rollback(status);
        }
    }
    Main method{
        beginTransaction()
        M1();
        If error(){
            rollbackTransaction()
        }
         commitTransaction();
    }

}

Você pode depurar e ver o resultado com diferentes valores para isolamento e propagação.

abishkar bhattarai
fonte
como obter as alterações feitas na transação atual
Prasanna Kumar HA
2
Qual é a interação entre nível de isolamento e propagação ? Se o método 1 inicia uma transação com nível de isolamento, por exemplo, READ_COMMITTED e depois chama o método2 com o nível REPEATABLE_READ, certamente o método 2 deve ser executado em sua própria nova transação, independentemente do comportamento de propagação especificado (por exemplo, apenas REQUIRED)?
Cornel Masson
Isso é realmente tarde para o show, mas quando PROPAGATION_REQUIRES_NEW, o que acontece no T1 (que é usado pelo M1) se outra nova chamada acontecer com o M1? (diga M1.1)
Tim Z.
115

Explicações suficientes sobre cada parâmetro são dadas por outras respostas; No entanto, você pediu um exemplo do mundo real, aqui está o que esclarece o propósito de diferentes opções de propagação :

Suponha que você esteja encarregado de implementar um serviço de inscrição no qual um email de confirmação é enviado ao usuário. Você cria dois objetos de serviço, um para registrar o usuário e outro para enviar e-mails, os quais são chamados dentro do primeiro. Por exemplo, algo como isto:

/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
 ...
 void SignUp(User user){
    ...
    emailService.sendMail(User);
 }
}

/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
 ...
 void sendMail(User user){
  try{
     ... // Trying to send the e-mail
  }catch( Exception)
 }
}

Você deve ter notado que o segundo serviço é do tipo propagação REQUIRES_NEW e, além disso, é possível que exista uma exceção (servidor SMTP inativo, email inválido ou outros motivos). Você provavelmente não deseja que todo o processo seja revertido, como remover as informações do usuário do banco de dados ou outras coisas; portanto, você chama o segundo serviço em uma transação separada.

De volta ao nosso exemplo, desta vez você se preocupa com a segurança do banco de dados e define suas classes DAO da seguinte maneira:

/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

Significando que sempre que um objeto DAO e, portanto, um acesso potencial ao db, é criado, precisamos garantir que a chamada foi feita de dentro de um de nossos serviços, implicando a existência de uma transação ativa; caso contrário, ocorrerá uma exceção. Portanto, a propagação é do tipo MANDATORY .

ye9ane
fonte
26
Exemplo perfeito para REQUIRES_NEW.
Ravi Thapliyal
5
Boa explicação! A propósito, qual é o padrão para propagação? Também seria ainda melhor se você pudesse dar um exemplo como esse também para isolamento. Muito obrigado.
Prakash K
5
@PrakashK O padrão é NECESSÁRIO. ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… )
ihebiheb
59

O nível de isolamento define como as alterações feitas em um repositório de dados por uma transação afetam outras transações simultâneas simultâneas e também como e quando esses dados alterados ficam disponíveis para outras transações. Quando definimos uma transação usando a estrutura Spring, também podemos configurar em qual nível de isolamento a mesma transação será executada.

@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {

}

O nível de isolamento READ_UNCOMMITTED afirma que uma transação pode ler dados que ainda não foram confirmados por outras transações.

O nível de isolamento READ_COMMITTED afirma que uma transação não pode ler dados que ainda não foram confirmados por outras transações.

O nível de isolamento REPEATABLE_READ indica que, se uma transação lê um registro do banco de dados várias vezes, o resultado de todas essas operações de leitura deve sempre ser o mesmo.

O nível de isolamento SERIALIZÁVEL é o mais restritivo de todos os níveis de isolamento. As transações são executadas com bloqueio em todos os níveis (bloqueio de leitura, intervalo e gravação), para que pareçam ter sido executadas de maneira serializada.

Propagação é a capacidade de decidir como os métodos de negócios devem ser encapsulados em transações lógicas ou físicas.

O comportamento Spring REQUIRED significa que a mesma transação será usada se já houver uma transação aberta no contexto de execução do método do bean atual.

O comportamento de REQUIRES_NEW significa que uma nova transação física será sempre criada pelo contêiner.

O comportamento NESTED faz transações Spring aninhadas para usar a mesma transação física, mas define pontos de salvamento entre invocações aninhadas para que as transações internas também possam reverter independentemente das transações externas.

O comportamento MANDATORY indica que uma transação aberta existente já deve existir. Caso contrário, uma exceção será lançada pelo contêiner.

O comportamento NUNCA afirma que uma transação aberta existente ainda não deve existir. Se existir uma transação, uma exceção será lançada pelo contêiner.

O comportamento NOT_SUPPORTED será executado fora do escopo de qualquer transação. Se uma transação aberta já existir, ela será pausada.

O comportamento SUPPORTS será executado no escopo de uma transação se uma transação aberta já existir. Se ainda não houver uma transação aberta, o método será executado de qualquer maneira, mas de maneira não transacional.

reos
fonte
4
Se você pudesse adicionar quando usar qual, seria muito mais benéfico.
Kumar Manish
Dê alguns exemplos, seria muito útil para iniciantes
nitinsridar
23

Uma transação representa uma unidade de trabalho com um banco de dados.

Na Primavera de TransactionDefinitioninterface que define as propriedades de transação Primavera conformes. @TransactionalA anotação descreve atributos de transação em um método ou classe.

@Autowired
private TestDAO testDAO;

@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {

  // Interact with testDAO

}

Propagação (reprodução): é usada para a relação entre transações. (análoga à comunicação entre threads java)

+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value |        Propagation        |                                             Description                                              |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
|    -1 | TIMEOUT_DEFAULT           | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
|     0 | PROPAGATION_REQUIRED      | Support a current transaction; create a new one if none exists.                                      |
|     1 | PROPAGATION_SUPPORTS      | Support a current transaction; execute non-transactionally if none exists.                           |
|     2 | PROPAGATION_MANDATORY     | Support a current transaction; throw an exception if no current transaction exists.                  |
|     3 | PROPAGATION_REQUIRES_NEW  | Create a new transaction, suspending the current transaction if one exists.                          |
|     4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally.                     |
|     5 | PROPAGATION_NEVER         | Do not support a current transaction; throw an exception if a current transaction exists.            |
|     6 | PROPAGATION_NESTED        | Execute within a nested transaction if a current transaction exists.                                 |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+

Isolamento: O isolamento é uma das propriedades ACID (Atomicidade, Consistência, Isolamento, Durabilidade) das transações do banco de dados. O isolamento determina como a integridade da transação é visível para outros usuários e sistemas. Ele usa para bloqueio de recursos, ou seja, controle de concorrência, verifique se apenas uma transação pode acessar o recurso em um determinado ponto.

Percepção de bloqueio: o nível de isolamento determina a duração em que os bloqueios são mantidos.

+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode      |  Read             |   Insert    |   Update    |       Lock Scope       |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED          |  uncommitted data | Allowed     | Allowed     | No Lock                |
| READ_COMMITTED (Default)  |   committed data  | Allowed     | Allowed     | Lock on Committed data |
| REPEATABLE_READ           |   committed data  | Allowed     | Not Allowed | Lock on block of table |
| SERIALIZABLE              |   committed data  | Not Allowed | Not Allowed | Lock on full table     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Percepção de leitura: os seguintes 3 tipos de problemas principais ocorrem:

  • Leituras sujas : lê dados não confirmados de outra TX (transação).
  • Leituras não repetíveis : leituras confirmadasUPDATES de outra TX.
  • Leituras fantasmas : leituras confirmadas INSERTSe / ou DELETESde outra tx

Níveis de isolamento com diferentes tipos de leituras:

+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode      |  Dirty reads   | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED          | allows         | allows               | allows         |
| READ_COMMITTED (Default)  | prevents       | allows               | allows         |
| REPEATABLE_READ           | prevents       | prevents             | allows         |
| SERIALIZABLE              | prevents       | prevents             | prevents       |
+---------------------------+----------------+----------------------+----------------+

por exemplo

Premraj
fonte
20

Você quase nunca deseja usar, Read Uncommitedpois não é realmente ACIDcompatível. Read Commmitedé um bom ponto de partida padrão. Repeatable Readprovavelmente é necessário apenas em cenários de relatórios, rollup ou agregação. Observe que muitos bancos de dados, incluindo o postgres, na verdade não suportam a leitura repetida; você deve usar Serializable. Serializableé útil para coisas que você sabe que precisam acontecer completamente independentemente de qualquer outra coisa; pense nisso como synchronizedem Java. Serializable anda de mãos dadas com a REQUIRES_NEWpropagação.

Eu uso REQUIRESpara todas as funções que executam consultas UPDATE ou DELETE, bem como funções de nível de "serviço". Para funções no nível DAO que executam apenas SELECTs, utilizo as SUPPORTSque participarão de um TX se uma já estiver iniciada (isto é, sendo chamada de uma função de serviço).

AngerClown
fonte
13

Isolamento de Transação e Propagação de Transação, embora relacionados, mas são claramente dois conceitos muito diferentes. Nos dois casos, os padrões são personalizados no componente de limite do cliente, usando o gerenciamento de transações declarativo ou o gerenciamento de transações programáticas . Detalhes de cada nível de isolamento e atributos de propagação podem ser encontrados nos links de referência abaixo.

Isolamento de transação

Para duas ou mais transações / conexões em execução em um banco de dados, como e quando as alterações feitas pelas consultas em uma transação impactam / são visíveis para as consultas em uma transação diferente. Também estava relacionado a que tipo de bloqueio de registro do banco de dados será usado para isolar as alterações nessa transação de outras transações e vice-versa. Isso geralmente é implementado pelo banco de dados / recurso que está participando da transação.

.

Propagação de Transação

Em um aplicativo corporativo para qualquer solicitação / processamento, há muitos componentes envolvidos para realizar o trabalho. Alguns desses componentes marcam os limites (início / fim) de uma transação que será usada no respectivo componente e seus subcomponentes. Para esse limite transacional de componentes, a Propagação de Transação especifica se o respectivo componente participará ou não da transação e o que acontecerá se o componente de chamada já tiver ou não uma transação já criada / iniciada. É o mesmo que os atributos de transação do Java EE. Isso geralmente é implementado pelo gerenciador de transações / conexões do cliente.

Referência:

Gladwin Burboz
fonte
1
Grande, Todas as informações em um só lugar, as ligações são muito úteis, obrigado @Gladwin Burboz
nitinsridar
7

Eu corri outerMethod, method_1e method_2com modo de propagação diferente.

Abaixo está a saída para o modo de propagação diferente.

  • Método externo

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
  • Método 1

    @Transactional(propagation=Propagation.MANDATORY)
    public void method_1() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "XXX");
            session.save(entity);
            System.out.println("Method - 1 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
  • Método_2

    @Transactional()
    @Override
    public void method_2() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "CCC");
            session.save(entity);
            int i = 1/0;
            System.out.println("Method - 2 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
      • outerMethod - Sem transação
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Apenas anotação de transação
      • Saída: method_1 lançará a exceção de que nenhuma transação existente
      • outerMethod - Sem transação
      • method_1 - Apenas anotação de transação
      • method_2 - Propagation.MANDATORY)
      • Saída: method_2 lançará a exceção de que nenhuma transação existente
      • Saída: method_1 persistirá o registro no banco de dados.
      • outerMethod - Com transação
      • method_1 - Apenas anotação de transação
      • method_2 - Propagation.MANDATORY)
      • Saída: method_2 persistirá o registro no banco de dados.
      • Saída: method_1 persistirá o registro no banco de dados. - Aqui principal transação existente externa usada para os métodos 1 e 2
      • outerMethod - Com transação
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Somente anotação de transação e lança exceção
      • Saída: nenhum registro persiste no banco de dados significa reversão feita.
      • outerMethod - Com transação
      • method_1 - Propagation.REQUIRES_NEW)
      • method_2 - Propagation.REQUIRES_NEW) e lança a exceção 1/0
      • Saída: method_2 lança exceção, para que o registro method_2 não persista.
      • Saída: method_1 persistirá o registro no banco de dados.
      • Saída: Não há reversão para o método_1
NIrav Modi
fonte
3

Podemos adicionar para isso:

@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {

    public Customer getDetail(String customername) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateCustomer(Customer customer) {
        // do something
    }
}
Ankit
fonte
1

Você pode usar assim:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}

Você pode usar essa coisa também:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
Ankit
fonte