E aí pessoal, beleza?

Muito se fala da utilidade de usarmos a anotação @TransactionAttribute, mas pouco se comenta sobre os problemas que podemos encontrar no seu uso e como resolvê-los. Há pouco tempo me deparei com um desses problemas, invocar um método anotado com TransactionAttribute através de outro do mesmo EJB.

[NOTA] Se você não está familizarizado com transações e @TransactionAttribute, recomendo a leitura deste tutorial oficial, ele auxilia no entendimento do conceito do que é e de como funcionam os diferentes tipos de transações. Tem outros artigos bacanas que podem ser vistos aqui, aqui e/ou aqui.

O problema

No exemplo abaixo, vamos imaginar um cenário onde processamos dados em lote:

1
2
3
4
5
6
7
8
@Stateless
public class BatchBeanImpl implements BatchBean { 

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void processarDados() {
     // faz algum processamento com os dados

  }
}

Para melhorar nossa performance e tratar melhor os erros, vamos dividir a transação principal em várias transações menores. O método principal não será transacionado, pois ele apenas lê dados de algum lugar, então usamos uma transação do tipo TransactionAttributeType.NEVER, que indicará que processarDados não possuirá transação.

No submétodo processaUmaPequenaColecaoDeDados, vamos usar o tipo TransactionAttributeType.REQUIRES_NEW, que indica que uma nova transação será aberta sempre que o método for chamado.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Stateless
public class BatchBeanImpl implements BatchBean { 

  @TransactionAttribute(TransactionAttributeType.NEVER)
  public void processarDados() {
    //ler dados

    while (aindaTemDadosParaProcessar) {
      processaUmaPequenaColecaoDeDados(colecaoDeDados);
    }
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void processaUmaPequenaColecaoDeDados(List dados) {
     // faz alguma coisa que precisa de uma transação

  }
} 

Se chamássemos processarDados, o que deveria ocorrer? O método processaUmaPequenaColecaoDeDados deveria ou não funcionar?

A resposta é que esse código está errado, pois processaUmaPequenaColecaoDeDados não cria uma nova transação.

Oxem, mas e o REQUIRES_NEW não deveria criar uma nova transação?  Sim, deveria. Porém, não é bem assim que funciona. Para que uma nova transação seja criada a invocação do método processaUmaPequenaDeColecaoDeDados deve passar pelo proxy, em outras palavras, o método deve ser invocado por uma interface de negócio.

As soluções

A primeira solução que podemos usar é a injeção do próprio BatchBean em si mesmo (self-inject). Dessa forma, o comportamento da anotação TransactionAttribute não será ignorado e uma nova transação será criada sempre que o método for invocado.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Stateless
public class BatchBeanImpl implements BatchBean {

  @EJB
  private BatchBean batchBean; 

  @TransactionAttribute(TransactionAttributeType.NEVER)
  public void processarDados() {
    //ler dados

    while (aindaTemDadosParaProcessar) {
      batchBean.processaUmaPequenaColecaoDeDados(colecaoDeDados);
    }
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void processaUmaPequenaColecaoDeDados(List dados) {
     // faz alguma coisa que precisa de uma transação

  }
} 

Outra solução, é usar o SessionContext para obter uma referência para o BatchBean.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Stateless
public class BatchBeanImpl implements BatchBean {

  private BatchBean batchBean;

  @Resource
  private SessionContext ctx;

  @PostConstruct
  public void init() {
      batchBean = ctx.getBusinessObject(BatchBean.class);
  } 

  @TransactionAttribute(TransactionAttributeType.NEVER)
  public void processarDados() {
    //ler dados

    while (aindaTemDadosParaProcessar) {
      batchBean.processaUmaPequenaColecaoDeDados(colecaoDeDados);
    }
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void processaUmaPequenaColecaoDeDados(List dados) {
     // faz alguma coisa que precisa de uma transação

  }
} 

Não vou entrar no mérito se o design da implementação está bom ou ruim, muito menos entrar nos detalhes para saber qual das duas modalidades é a mais performática. Fica como dever de casa para todos nós. 😉

A transação é uma arma muito poderosa que pode nos ajudar em diversos cenários, porém, não conhecer o seu funcionamento pode fazer com que cometamos erros.

Então é isso pessoal, abraços e até a próxima.