Onde pertence a anotação @Transactional?

528

Se você colocar o @Transactionalnas DAOclasses e / ou os seus métodos ou é melhor para anotar as classes de serviço que estão chamando usando os objetos DAO? Ou faz sentido anotar as duas "camadas"?

Thomas Einwaller
fonte

Respostas:

537

Eu acho que as transações pertencem à camada Serviço. É quem conhece as unidades de trabalho e os casos de uso. É a resposta certa se você tiver vários DAOs injetados em um Serviço que precisam trabalhar juntos em uma única transação.

duffymo
fonte
Eu concordo com isso. Às vezes isso não importa, mas às vezes você pode se beneficiar disso, por exemplo, a sessão do Hibernate é estendida para a transação while, para que todos os objetos carregados estejam no cache de 1º nível e você não precise anexar novamente os objetos à sessão novamente, além de carregar lentamente propriedades funcionam sem distorção.
precisa saber é
5
Uma transação global pode consistir em mais de um desses @Transactional (propagation = Propagation.REQUIRED)? Ou @Transactional é sempre um limite para uma transação? Não sei se obtive isso na documentação, mas parece um pouco que você pode criar transações uniformes que consistem em métodos @Transactional e tudo o que é executado dentro
Lisak
1
Sim, acho que o @Transactional mais externo é aquele que se torna o limite para a transação se Propagation.REQUIRED estiver ativado.
duffymo
309

Em geral, concordo com os outros afirmando que as transações geralmente são iniciadas no nível de serviço (dependendo da granularidade necessária, é claro).

Entretanto, nesse meio tempo, também comecei a adicionar @Transactional(propagation = Propagation.MANDATORY)à minha camada DAO (e outras camadas que não têm permissão para iniciar transações, mas exigem as existentes), porque é muito mais fácil detectar erros nos quais você esqueceu de iniciar uma transação no chamador ( por exemplo, o serviço). Se o seu DAO for anotado com propagação obrigatória, você receberá uma exceção informando que não há transação ativa quando o método é chamado.

Também tenho um teste de integração em que verifico todos os beans (processador de pós-bean) para esta anotação e falho se houver um @Transactional anotação com propagação diferente de Obrigatória em um bean que não pertença à camada de serviços. Dessa forma, garanto que não iniciamos transações na camada errada.

mnp
fonte
3
Isso deve ser feito na camada dao (interfaces), no dao impl. camada ou ambos?
Johan
3
Não sei se isso se encaixa nessa discussão, mas outra dica poderia ser adicionar @Transactional (readOnly = true) no dao impl nas operações sem gravação.
maxivis
10
O @Johan Spring recomenda colocar as anotações de transação nas classes de implementação, em vez das interfaces.
Mathias G.
Eu realmente gosto dessa idéia, tentando isso para meus projetos também
Thomas Einwaller
1
Deixe-me ver se eu entendi ... você está dizendo que eu deveria colocar @Transactionala classe de implementação de Serviço e a implementação de classe @Transactional(propagation = MANDATORY)DAO (repositório)?
John Henckel
93

As anotações transacionais devem ser colocadas em torno de todas as operações que são inseparáveis.

Por exemplo, sua chamada é "alterar senha". Isso consiste em duas operações

  1. Mude a senha.
  2. Audite a mudança.
  3. Enviar por e-mail ao cliente que a senha foi alterada.

Portanto, acima, se a auditoria falhar, a alteração da senha também falhará? Nesse caso, a transação deve ser em torno de 1 e 2 (na camada de serviço). Se o email falhar (provavelmente deve ter algum tipo de segurança contra falhas para não falhar), deve reverter a senha de alteração e a auditoria?

Esse é o tipo de pergunta que você precisa fazer ao decidir onde colocar o arquivo @Transactional.

Michael Wiles
fonte
41

A resposta correta para as arquiteturas tradicionais do Spring é colocar a semântica transacional nas classes de serviço, pelos motivos que outros já descreveram.

Uma tendência emergente no Spring é o DDD ( Domain-driven Design ). O Spring Roo exemplifica bem a tendência. A idéia é tornar os POJOs de objetos de domínio muito mais ricos do que nas arquiteturas típicas do Spring (geralmente são anêmicos ) e, em particular, colocar semântica de transação e persistência nos próprios objetos de domínio. Nos casos em que tudo o que é necessário é simples operações CRUD, os controladores da Web operam diretamente nos POJOs de objetos de domínio (eles estão funcionando como entidades nesse contexto) e não há camada de serviço. Nos casos em que há algum tipo de coordenação necessária entre objetos de domínio, você pode ter um bean de serviço que, com@Transactionconforme a tradição. Você pode definir a propagação da transação nos objetos do domínio para algo como, REQUIREDpara que os objetos do domínio usem transações existentes, como transações iniciadas no bean de serviço.

Tecnicamente, essa técnica utiliza o AspectJ e <context:spring-configured />. O Roo usa definições entre tipos do AspectJ para separar a semântica da entidade (transações e persistência) das coisas do objeto do domínio (basicamente campos e métodos de negócios).

naXa
fonte
38

O caso normal seria fazer anotações no nível da camada de serviço, mas isso realmente depende dos seus requisitos.

Fazer anotações em uma camada de serviço resultará em transações mais longas que anotações no nível DAO. Dependendo do nível de isolamento da transação que pode causar problemas, as transações simultâneas não verão as mudanças uma da outra, por exemplo. LEITURA REPETÍVEL.

Fazer anotações nos DAOs manterá as transações o mais curto possível, com a desvantagem de que a funcionalidade que sua camada de serviço está expondo não será feita em uma única transação (reversível).

Não faz sentido anotar as duas camadas se o modo de propagação estiver definido como padrão.

hammarback
fonte
31

Coloco o @Transactionalna @Servicecamada e defino rollbackForqualquer exceção e readOnlypara otimizar ainda mais a transação.

Por padrão @Transactional, apenas procurará RuntimeException(Exceções não verificadas). Ao definir a reversão para Exception.class(Exceções verificadas), a reversão será feita para qualquer exceção.

@Transactional(readOnly = false, rollbackFor = Exception.class)

Consulte Exceções verificadas versus não verificadas .

user2601995
fonte
17

Ou faz sentido anotar as duas "camadas"? - não faz sentido anotar a camada de serviço e a dao - se alguém quiser garantir que o método DAO seja sempre chamado (propagado) a partir de uma camada de serviço com propagação "obrigatória" no DAO. Isso forneceria alguma restrição para os métodos DAO serem chamados da camada de interface do usuário (ou controladores). Além disso - ao testar a unidade DAO em particular - a anotação do DAO também garantirá que ela seja testada quanto à funcionalidade transacional.

tweekran
fonte
Isso não resultaria em transações aninhadas? E todos os problemas sutis que o acompanham?
HDave
11
Não, não há transações aninhadas na JPA. Colocá-los em ambos seria ótimo - se você já estiver em uma transação ao pressionar o DAO, essa transação será continuada.
fool4jesus
4
Transação aninhada pode ocorrer se você usar propagation=Propagation.REQUIRES_NEW. Caso contrário, na maioria dos casos, incluindo a propagação = obrigatória, o DAO participará apenas da transação existente iniciada pela camada de serviço.
precisa
15

Para transação no nível do banco de dados

usei principalmente @Transactionalno DAO apenas no nível do método, portanto a configuração pode ser específica para um método / usando o padrão (obrigatório)

  1. O método do DAO que obtém a busca de dados (selecione ..) - não precisa @Transactional disso, pode levar a uma sobrecarga por causa do interceptador de transações / e do proxy AOP que precisam ser executados também.

  2. Os métodos do DAO que inserem / atualizam receberão @Transactional

muito bom blog sobre transicional

Para o nível do aplicativo -
estou usando a lógica transacional para negócios, gostaria de poder reverter em caso de erro inesperado

@Transactional(rollbackFor={MyApplicationException.class})
public void myMethod(){

    try {    
        //service logic here     
    } catch(Throwable e) {

        log.error(e)
        throw new MyApplicationException(..);
    }
}
lukass77
fonte
6
+1 artigo muito bom sobre TransactionalinJava
oak
11

Geralmente, deve-se colocar uma transação na camada de serviço.

Mas, como afirmado anteriormente, a atomicidade de uma operação é o que nos diz onde uma anotação é necessária. Portanto, se você usar estruturas como o Hibernate, em que uma única operação "salvar / atualizar / excluir / ... modificação" em um objeto tem o potencial de modificar várias linhas em várias tabelas (por causa da cascata no gráfico de objetos), de Claro, também deve haver gerenciamento de transações nesse método DAO específico.

yannick555
fonte
10

@TransactionalAs anotações devem ser colocadas em torno de todas as operações que são inseparáveis. O uso da @Transactionalpropagação de transações é tratado automaticamente. Nesse caso, se outro método for chamado pelo método atual, esse método terá a opção de ingressar na transação em andamento.

Então, vamos dar um exemplo:

Temos 2 modelos ie Countrye City. O mapeamento relacional Countrye o Citymodelo é como se você Countrypudesse ter várias cidades; portanto, o mapeamento é como,

@OneToMany(fetch = FetchType.LAZY, mappedBy="country")
private Set<City> cities;

Aqui, o país foi mapeado para várias cidades com sua busca Lazily. Então, aqui vem a função de @Transactinalquando recuperamos o objeto País do banco de dados, obteremos todos os dados do objeto País, mas não obteremos Conjunto de cidades porque estamos buscando cidades LAZILY.

//Without @Transactional
public Country getCountry(){
   Country country = countryRepository.getCountry();
   //After getting Country Object connection between countryRepository and database is Closed 
}

Quando queremos acessar o conjunto de cidades a partir do objeto país, obteremos valores nulos nesse conjunto porque o objeto do conjunto criado apenas este conjunto não é inicializado com os dados para obter valores do conjunto que usamos @Transactional, ou seja,

//with @Transactional
@Transactional
public Country getCountry(){
   Country country = countryRepository.getCountry();
   //below when we initialize cities using object country so that directly communicate with database and retrieve all cities from database this happens just because of @Transactinal
   Object object = country.getCities().size();   
}

Então, basicamente, o @TransactionalServiço pode fazer várias chamadas em uma única transação sem fechar a conexão com o ponto final.

Harshal Patil
fonte
2
muito informativo, obrigado! Apenas o que eu estava procurando, uma explicação do que @Transactionalrealmente é
Cybex
6

É melhor tê-lo na camada de serviço! Isso está claramente explicado em um dos artigos que me deparei ontem! Aqui está o link que você pode conferir!

sundário
fonte
5

insira a descrição da imagem aqui

A @Transactionaldeve ser usado em camada de serviço, uma vez que contém a lógica de negócios. A camada DAO geralmente possui apenas operações CRUD de banco de dados.

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

Documento da primavera: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

Alin
fonte
5

A camada de serviço é o melhor local para adicionar @Transactionalanotações, pois a maioria das lógicas de negócios presentes aqui contém o comportamento dos casos de uso no nível de detalhe.

Suponha que o adicionemos ao DAO e, a partir do serviço, estamos chamando duas classes do DAO, uma com falha e outra com êxito, neste caso se @Transactional não no serviço, um banco de dados será confirmado e outro será revertido.

Portanto, minha recomendação é usar esta anotação com sabedoria e usar apenas na camada Serviço.

Projeto Github - java-algos

VishalTambe
fonte
Exceções como ObjectOptimisticLockingFailureException ocorrem somente após a conclusão da transação. Se você tiver threads separados para outras operações, como o serviço de email, esse design falhará totalmente. Estamos sofrendo agora. A única solução restante seria AOP.
Nabin Kumar Khatiwada
4

Primeiro de tudo, vamos definir onde devemos usar a transação ?

Eu acho que a resposta correta é: quando precisamos garantir que a sequência de ações seja concluída em conjunto como uma operação atômica ou que nenhuma alteração será feita, mesmo que uma das ações falhe.

É uma prática bem conhecida colocar lógica de negócios em serviços. Portanto, os métodos de serviço podem conter ações diferentes que devem ser executadas como uma única unidade lógica de trabalho. Se sim - então esse método deve ser marcado como Transacional . Obviamente, nem todo método exige essa limitação, portanto você não precisa marcar todo o serviço como transacional .

E ainda mais - não se esqueça de levar em consideração que o @Transactional obviamente pode reduzir o desempenho do método. Para ter uma visão geral, é necessário conhecer os níveis de isolamento da transação. Saber que isso pode ajudá-lo a evitar o uso do @Transactional quando não for necessariamente necessário.

smaiakov
fonte
3

É melhor manter @Transactional em uma camada intermediária separada entre o DAO e a Camada de Serviço. Como a reversão é muito importante, você pode colocar toda a manipulação do banco de dados na camada intermediária e escrever a lógica comercial na Camada de Serviço. A camada intermediária irá interagir com suas camadas DAO.

Isso o ajudará em muitas situações, como ObjectOptimisticLockingFailureException - essa exceção ocorre somente após o término da sua transação. Portanto, você não pode capturá-lo na camada intermediária, mas pode capturar sua camada de serviço agora. Isso não seria possível se você tiver @Transactional na camada Service. Embora você possa capturar o Controller, o Controlador deve estar o mais limpo possível.

Se você estiver enviando correio ou sms em um segmento separado após concluir todas as opções de salvar, excluir e atualizar, poderá fazer isso no serviço após a conclusão da transação na camada intermediária. Novamente, se você mencionar @Transactional na camada de serviço, o e-mail será enviado mesmo que sua transação falhe.

Portanto, ter uma camada @Transaction intermediária ajudará a tornar seu código melhor e fácil de manusear. Caso contrário, se você usar a camada DAO, talvez não seja possível reverter todas as operações. Se você usa a camada Serviço, pode ter que usar AOP (Aspect Oriented Programming) em certos casos.

Nabin Kumar Khatiwada
fonte
2

Idealmente, a camada de serviço (Manager) representa sua lógica de negócios e, portanto, deve ser anotada com @Transactional. A camada de serviço pode chamar DAO diferente para executar operações de banco de dados. Vamos assumir uma situação em que você tem N número de operações DAO em um método de serviço. Se sua primeira operação do DAO falhar, outras ainda poderão ser transmitidas e você acabará com o estado inconsistente do banco de dados. Anotar a camada Serviço pode salvá-lo de tais situações.

salsinga
fonte
1

Eu prefiro usar @Transactionalna camada de serviços no nível do método.

Spark-Beginner
fonte
1

@Transactionalusa na camada de serviço que é chamada usando a camada controladora ( @Controller) e a camada de serviço chamada para a camada DAO ( @Repository), ou seja, operação relacionada à base de dados.

Sentou
fonte