Onde devo colocar a anotação @Transactional: em uma definição de interface ou em uma classe de implementação?

92

A pergunta do título no código:

@Transactional (readonly = true)
public interface FooService {
   void doSmth ();
}


public class FooServiceImpl implements FooService {
   ...
}

vs

public interface FooService {
   void doSmth ();
}

@Transactional (readonly = true)
public class FooServiceImpl implements FooService {
   ...
}
romano
fonte

Respostas:

123

De http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

A recomendação da equipe do Spring é que você anote apenas classes concretas com a @Transactionalanotação, ao invés de anotar interfaces. Você certamente pode colocar a @Transactionalanotação em uma interface (ou um método de interface), mas isso só funcionará como você esperaria se estiver usando proxies baseados em interface. O fato de as anotações não serem herdadas significa que se você estiver usando proxies baseados em classe, as configurações de transação não serão reconhecidas pela infraestrutura de proxy baseada em classe e o objeto não será empacotado em um proxy transacional (o que seria decididamente ruim ) . Portanto, siga o conselho da equipe do Spring e anote apenas classes concretas (e os métodos de classes concretas) com a @Transactionalanotação.

Observação: como esse mecanismo é baseado em proxies, apenas chamadas de método 'externas' que chegam por meio do proxy serão interceptadas. Isso significa que a 'auto-invocação', ou seja, um método dentro do objeto de destino chamando algum outro método do objeto de destino, não levará a uma transação real em tempo de execução, mesmo se o método invocado estiver marcado com @Transactional!

(Ênfase adicionada à primeira frase, outra ênfase do original.)

Romain Hippeau
fonte
Grande +1 BTW, tomei a liberdade de fazer a citação uma citação, para que as pessoas não confundissem, e adicionei de volta o itálico e outros itens do original.
TJ Crowder
Eu li essa declaração antes no Spring docu, mas ainda não consigo entender por que as configurações de transação não serão reconhecidas pela infraestrutura de proxy baseada em classe . Pessoalmente, acho que isso é apenas uma limitação da implementação do Spring, não o problema da infraestrutura de proxy subjacente.
dma_k
Olhando para as fontes do Spring, pode-se descobrir que JdkDynamicAopProxypassa por todos os orientadores de bean em cada chamada de método (consulte também DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()), que no caso de configuração de transação declarativa, inclui BeanFactoryTransactionAttributeSourceAdvisor. Por sua vez, TransactionAttributeSourcePointcut#matches()é invocado, que deve coletar as informações relativas à transação. É passada a classe de destino e sempre pode atravessar todas as interfaces que esta classe implementa. Agora: por que isso não pode não funcionar de forma confiável?
dma_k
2
@dma_k - O Java runtime só dá acesso direto às anotações de classe herdadas de superclasses ou anotações de método sem herança alguma. Portanto, Pointcut.matches () teria que percorrer recursivamente todas as interfaces, encontrar o método "real" sobrescrito com reflexão, lidar com métodos de ponte, sobrecarga, varargs, genéricos, proxies e, claro, fazer isso rápido . Então você tem que lidar com a herança do diamante - qual das possivelmente muitas @Transactionalanotações herdadas se aplicaria? Portanto, embora seja possível , não culpo Spring. jira.springsource.org/browse/SPR-975
Peter Davis
9

Você pode colocá-los na interface, mas esteja avisado que as transações podem não acontecer em alguns casos. Veja a segunda dica na seção 10.5.6 dos documentos do Spring:

Spring recomenda que você apenas anote classes concretas (e métodos de classes concretas) com a anotação @Transactional, ao invés de anotar interfaces. Você certamente pode colocar a anotação @Transactional em uma interface (ou um método de interface), mas isso funciona apenas como você esperaria se estiver usando proxies baseados em interface. O fato de que as anotações Java não são herdadas de interfaces significa que se você estiver usando proxies baseados em classe (proxy-target-class = "true") ou o aspecto baseado em tecelagem (mode = "aspectj"), então as configurações de transação são não reconhecido pela infraestrutura de proxy e tecelagem, e o objeto não será empacotado em um proxy transacional, o que seria decididamente ruim.

Eu recomendaria colocá-los na implementação por esse motivo.

Além disso, para mim, as transações parecem um detalhe de implementação, portanto, devem estar na classe de implementação. Imagine ter implementações de wrapper para log ou implementações de teste (simulações) que não precisam ser transacionais.

AngerClown
fonte
9

A recomendação do Spring é que você anote as implementações concretas em vez de uma interface. Não é incorreto usar a anotação em uma interface, é apenas possível usar indevidamente esse recurso e inadvertidamente ignorar sua declaração @Transaction.

Se você marcou algo transacional em uma interface e depois se referiu a uma de suas classes de implementação em outro lugar no spring, não é muito óbvio que o objeto que o spring cria não respeitará a anotação @Transactional.

Na prática, é mais ou menos assim:

public class MyClass implements MyInterface { 

    private int x;

    public void doSomethingNonTx() {}

    @Transactional
    public void toSomethingTx() {}

}
zmf
fonte
1

Apoiando @Transactional nas classes concretas:

Eu prefiro arquitetar uma solução em 3 seções geralmente: uma API, uma Implementação e uma Web (se necessário). Eu tento o meu melhor para manter a API o mais leve / simples / POJO possível, minimizando as dependências. É especialmente importante se você jogar em um ambiente distribuído / integrado onde você precisa compartilhar muito as APIs.

Colocar @Transactional requer bibliotecas Spring na seção API, que IMHO não é eficaz. Portanto, prefiro adicioná-lo na Implementação onde a transação está sendo executada.

Firdous Amir
fonte
0

Colocá-lo na interface não tem problema, desde que todos os implementadores previsíveis de seu IFC se importem com os dados TX (transações não são problemas com os quais apenas bancos de dados lidam). Se o método não se importa com TX (mas você precisa colocá-lo lá para Hibernate ou qualquer outra coisa), coloque-o no impl.

Além disso, pode ser um pouco melhor colocar @Transactionalos métodos na interface:

public interface FooService {
    @Transactional(readOnly = true)
    void doSmth();
}
engfer
fonte