Gerenciamento eficiente de transações na primavera

Bom dia a todos!

Bem, o final do mês é sempre intenso e resta apenas um dia para o início do segundo fluxo do curso "Developer on Spring Framework" , um curso maravilhoso e interessante ministrado pelo igualmente bonito e zangado Yuri (como alguns alunos chamam de acordo com o nível de requisitos) em DZ), então vamos ver outro material que preparamos para você.

Vamos lá

1. Introdução

Na maioria das vezes, os desenvolvedores não atribuem importância ao gerenciamento de transações. Como resultado, a maior parte do código precisa ser reescrita posteriormente, ou o desenvolvedor implementa o gerenciamento de transações sem saber como ele realmente deve funcionar ou quais aspectos devem ser usados ​​especificamente no caso deles.

Um aspecto importante no gerenciamento de transações é determinar os limites corretos de uma transação, quando uma transação deve começar e quando terminar, quando os dados devem ser adicionados ao banco de dados e quando devem ser bombeados de volta (no caso de uma exceção).



O aspecto mais importante para os desenvolvedores é entender como implementar melhor o gerenciamento de transações em um aplicativo. Então, vamos olhar para as várias opções.

Métodos de gerenciamento de transações

As transações podem ser gerenciadas das seguintes maneiras:

1. Programe o controle escrevendo código personalizado

Este é um método antigo de gerenciamento de transações.

EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); EntityManager entityManager = entityManagerFactory.createEntityManager(); Transaction transaction = entityManager.getTransaction() try { transaction.begin(); someBusinessCode(); transaction.commit(); } catch(Exception ex) { transaction.rollback(); throw ex; } 

Prós :

  • Os limites da transação são óbvios no código.

Contras :

  • É repetitivo e propenso a erros.
  • Qualquer erro pode ter um impacto muito grande.
  • Você também precisa escrever muitos modelos, se quiser chamar outro método a partir desse método, precisará controlá-lo novamente a partir do código.

2. Usando o Spring para o gerenciamento transacional

O Spring suporta dois tipos de gerenciamento de transações

1. Gerenciamento de transações de software : você deve gerenciar transações por meio de programação. Este método é flexível o suficiente, mas difícil de manter.

2. Gerenciamento de transações declarativo : você separa o gerenciamento de transações da lógica de negócios. Você usa apenas anotações na configuração baseada em XML para gerenciamento de transações.

É altamente recomendável usar transações declarativas. Se você deseja conhecer os motivos, continue lendo, caso contrário, vá diretamente para a seção Gerenciamento de Transações Declarativas, se desejar implementar esta opção.

Agora vamos ver cada abordagem em detalhes.

2.1 Gerenciamento programático de transações:

A estrutura do Spring fornece duas ferramentas para gerenciamento programático de transações.

a. Usando o TransactionTemplate (recomendado pela equipe Spring):

Vamos ver como implementar esse tipo usando o código de exemplo abaixo (extraído da documentação do Spring com algumas alterações)

Observe que os trechos de código são retirados do Spring Docs.

Arquivo XML de contexto:

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Definition for ServiceImpl bean --> <bean id="serviceImpl" class="com.service.ServiceImpl"> <constructor-arg ref="transactionManager"/> </bean> 

Classe de Service :

 public class ServiceImpl implements Service { private final TransactionTemplate transactionTemplate; //       PlatformTransactionManager public ServiceImpl(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } //       ,   ,    //       xml  this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); //30  ///    public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { //         public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); }} 

Se não houver valor de retorno, use a conveniente classe TransactionCallbackWithoutResult com uma classe anônima, como mostrado abaixo:

 transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } }); 

  • As instâncias da classe TransactionTemplate são seguras para threads, portanto, nem todos os estados de diálogo são suportados.
  • No entanto, as instâncias TransactionTemplate suportam o estado de configuração; portanto, se uma classe precisar usar um TransactionTemplate com configurações diferentes (por exemplo, um nível de isolamento diferente), será necessário criar duas instâncias TransactionTemplate diferentes, embora algumas classes possam usar a mesma instância TransactionTemplate.

b. Usando a implementação PlatformTransactionManager diretamente:

Vamos olhar para esta opção no código novamente.

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> public class ServiceImpl implements Service { private PlatformTransactionManager transactionManager; public void setTransactionManager( PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } DefaultTransactionDefinition def = new DefaultTransactionDefinition(); //     -  ,       def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { //   -  } catch (Exception ex) { txManager.rollback(status); throw ex; } txManager.commit(status); } 

Agora, antes de avançar para o próximo método de gerenciamento de transações, vamos ver como decidir qual tipo de gerenciamento de transações escolher.

Escolhendo entre Gerenciamento de Transações Programático e Declarativo :

  • O gerenciamento programático de transações é uma boa opção apenas se você tiver um pequeno número de operações transacionais. (Na maioria dos casos, essas não são transações.)
  • Um nome de transação só pode ser definido explicitamente no Program Transaction Management.
  • O gerenciamento de transações programáticas deve ser usado quando você deseja controlar explicitamente o gerenciamento de transações.
  • Por outro lado, se seu aplicativo contiver inúmeras operações transacionais, vale a pena usar o gerenciamento declarativo.
  • O gerenciamento declarativo não permite gerenciar transações na lógica de negócios e não é difícil de configurar.

2.2 Transações declarativas (geralmente usadas em quase todos os cenários de qualquer aplicativo da web)

Etapa 1 : defina o gerenciador de transações no arquivo xml de contexto do seu aplicativo spring.

 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/> <tx:annotation-driven transaction-manager="txManager"/> 

Etapa 2 : ative o suporte à anotação adicionando uma entrada no arquivo xml de contexto do seu aplicativo spring.

OU adicione @EnableTransactionManagement ao seu arquivo de configuração, conforme mostrado abaixo:

 @Configuration @EnableTransactionManagement public class AppConfig { ... } 

O Spring recomenda a anotação apenas de classes específicas (e métodos de classes específicas) com a anotação @Transactional comparação com as interfaces de anotação.

A razão para isso é porque você coloca a anotação no nível da interface e, se você usa classes de proxy ( proxy-target-class = «true» ) ou o aspecto entrelaçado ( mode = «aspectj» ), os parâmetros da transação não são reconhecidos pela infraestrutura do proxy e plexos, por exemplo, o comportamento transacional não se aplicará.

Etapa 3 : adicione a anotação @Transactional à classe (método da classe) ou interface (método da interface).

 <tx:annotation-driven proxy-target-class="true"> 

Configuração padrão: proxy-target-class="false"

  • @Transactional pode ser colocada antes de uma definição de interface, um método de interface, uma definição de classe ou um método de classe pública.
  • Se você quiser que alguns métodos de classe (marcados com anotação @Transactional ) tenham configurações de atributo diferentes, como nível de isolamento ou nível de propagação, coloque a anotação no nível do método para substituir as configurações de atributo no nível de classe.
  • No modo proxy (que é definido por padrão), apenas as chamadas de método "externo" que passam pelo proxy podem ser interceptadas. Isso significa que uma "chamada independente", por exemplo, um método no destino que chama algum outro método do destino, não levará a uma transação real no tempo de execução, mesmo se o método chamado estiver marcado com @Transactional .

Agora vamos @Transactional diferença entre os @Transactional anotação @Transactional

@Transactional (isolation=Isolation.READ_COMMITTED)

  • O padrão é Isolation.DEFAULT
  • Na maioria dos casos, você usará as configurações padrão até ter requisitos especiais.
  • Informa ao gerenciador de transações ( tx ) que o próximo nível de isolamento deve ser usado para a tx atual. Ele deve ser instalado no ponto de partida de tx , porque não podemos alterar o nível de isolamento após o início de tx.

PADRÃO : Use o nível de isolamento padrão no banco de dados base.

READ_COMMITTED (lendo dados fixos): Uma constante indicando que a leitura suja foi impedida; Podem ocorrer leituras não repetidas e fantasmas.

READ_UNCOMMITTED (ler dados não confirmados): este nível de isolamento indica que uma transação pode ler dados que ainda não foram excluídos por outras transações.

REPEATABLE_READ (repetibilidade de leitura): Uma constante indicando que a leitura suja e a leitura não repetível são impedidas; leitura fantasma pode aparecer.

SERIALIZABLE : Permanente, indicando que leitura suja, leitura não repetível e leitura fantasma são impedidas.

O que esses jargões significam: leitura "suja", leitura fantasma ou leitura repetida?

  • Leitura Suja : A transação A grava. Enquanto isso, a transação "B" lê o mesmo registro até a transação A. Mais tarde, a transação A decide reverter e agora temos alterações na transação B que são incompatíveis. Esta é uma leitura suja. A transação B funcionou no nível de isolamento READ_UNCOMMITTED, para poder ler as alterações feitas pela transação A antes da conclusão da transação.
  • Leitura não repetível : a transação "A" lê alguns registros. A transação "B" grava esse registro e o confirma. Posteriormente, a transação A lê o mesmo registro novamente e pode receber valores diferentes, uma vez que a transação B fez alterações nesse registro e as confirmou. Esta é uma leitura não repetida.
  • Leitura fantasma : a transação "A" lê uma série de registros. Enquanto isso, a transação "B" insere um novo registro na mesma linha que a transação A. Mais tarde, a transação A lê o mesmo intervalo novamente e também recebe o registro que a transação B. acabou de inserir. Esta é uma leitura fantasma: a transação recuperou uma série de registros várias vezes do banco de dados e recebeu conjuntos de resultados diferentes (contendo registros fantasmas).

@Transactional(timeout=60)

O padrão é o tempo limite padrão para o sistema de transações subjacente.

Informa o gerente de TX sobre o tempo de espera até que a TX fique ociosa antes de decidir reverter as transações não respondidas.

@Transactional(propagation=Propagation.REQUIRED)

Se não especificado, o comportamento de propagação padrão é REQUIRED .

Outras opções são REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER e NESTED .

NECESSÁRIO

Indica que o método de destino não pode funcionar sem uma TX ativa. Se o tx já estiver em execução antes de chamar esse método, ele continuará no mesmo tx ou o novo tx será iniciado logo após a chamada desse método.

REQUIRES_NEW

  • Indica que um novo tx deve ser executado sempre que o método de destino for chamado. Se o tx já estiver em execução, ele será pausado antes de iniciar um novo.

Mandato

  • Indica que o método de destino requer tx ativo. Se tx não continuar, não falhará, lançando uma exceção.

SUPORTE

  • Indica que o método de destino pode ser executado independentemente de tx. Se o tx funcionar, ele participará do mesmo tx. Se executado sem tx, ainda será executado se não houver erros.

  • Os métodos que recuperam dados são os melhores candidatos para esta opção.

NOT_SUPPORTED

  • Indica que o método de destino não requer propagação de contexto de transação.
  • Basicamente, os métodos executados em uma transação, mas que executam operações com RAM, são os melhores candidatos para essa opção.

Nunca

  • Indica que o método de destino lançará uma exceção se executado em um processo transacional.
  • Essa opção na maioria dos casos não é usada em projetos.

@Transactional (rollbackFor=Exception.class)

Valor padrão: rollbackFor=RunTimeException.class

No Spring, todas as classes de API lançam uma RuntimeException, o que significa que, se algum método falhar, o contêiner sempre reverte a transação atual.

O problema é apenas com exceções verificadas. Portanto, esse parâmetro pode ser usado para reverter declarativamente uma transação se ocorrer uma Checked Exception verificada.

@Transactional (noRollbackFor=IllegalStateException.class)

Indica que a reversão não deve ocorrer se o método de destino gerar essa exceção.

Agora, o último, mas mais importante passo no gerenciamento de transações, é postar a anotação @Transactiona . Na maioria dos casos, surge uma confusão onde a anotação deve estar localizada: no nível do serviço ou no DAO?

@Transactional : nível de serviço ou DAO?

Serviço é o melhor local para colocar @Transactional , o nível de serviço deve conter o comportamento do caso de uso no nível de detalhe da interação do usuário, que logicamente entra na transação.

Existem muitos aplicativos CRUD que não possuem lógica comercial significativa, com um nível de serviço que simplesmente transfere dados entre controladores e objetos de acesso a dados, o que não é útil. Nesses casos, podemos colocar a anotação da transação no nível do DAO.

Portanto, na prática, você pode colocá-los em qualquer lugar, depende de você.

Além disso, se você colocar @Transactional na camada DAO e se a camada DAO for reutilizada por serviços diferentes, será difícil colocá-la na camada DAO, pois serviços diferentes podem ter requisitos diferentes.

Se o seu nível de serviço recuperar objetos usando o Hibernate, e digamos que você tenha inicializações preguiçosas na definição de um objeto de domínio, será necessário abrir a transação no nível do serviço, caso contrário, você encontrará uma LazyInitializationException lançada pelo ORM.

Considere outro exemplo em que seu nível de serviço pode chamar dois métodos diferentes de DAO para executar operações de banco de dados. Se sua primeira operação do DAO falhar, as outras duas poderão ser transferidas e você encerrará o estado inconsistente do banco de dados. A anotação de nível de serviço pode salvá-lo de tais situações.

Espero que este artigo tenha ajudado você.

O FIM

É sempre interessante ver seus comentários ou perguntas.

Source: https://habr.com/ru/post/pt431508/


All Articles