EJB 3 - Transaction no JBoss

8 respostas
Mr_Arthur

Pessoal,

Estou fazendo uma aplicação de testes para seguir com meus estudos de EJB 3.
A aplicação se baseia em uma simples transaction de container que insere dados em duas tabelas em um banco de dados MySQL (InnoDB).

Consegui executar a transação com sucesso, porém me resta uma dúvida.
Segue o código:

@Resource
SessionContext sessionContext;
	
@Resource(mappedName="java:MySqlDS")
DataSource dataSource;

Connection connection;

@PostConstruct
public void criaConexaoBD() throws SQLException {
	System.out.println("Criando conexão com o banco de dados.");
	connection = (Connection) dataSource.getConnection();
	metodosTransacionais = new MetodosTransacionais(connection);
}


@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void executaTransacao(String pNome, String pSenha, String sNome, String sSenha) throws SQLException {
	System.out.println("Iniciando a transação");
	if (connection.isClosed()) {
		criaConexaoBD();
	}
	try {
		metodosTransacionais.insertUsuarioTableOne(pNome, pSenha);
		metodosTransacionais.insertUsuarioTableTwo(sNome, sSenha);
		System.out.println("Transação efetivada com sucesso!");
	} catch (Exception e) {
		System.out.println("Falha na execução da transação! --> " + e.getMessage());
		connection.close();
		metodosTransacionais = null;
		sessionContext.setRollbackOnly();
	} finally {
		System.out.println("Finalizando a transação");
	}
}

os meus métodos insertUsuarioTableOne e insertUsuarioTableTwo tem a possibilidade de lançar uma Exception (com um Math.random()).
Quando não lançam a exception e o fluxo se encerra normalmente (os dados são populados nas tabelas), eu obtenho um certo problema:

[CachedConnectionManager] Closing a connection for you. Please close them yourself: org.jboss.resource.adapter.jdbc.jdk6.WrappedConnectionJDK6@14f0b41 java.lang.Throwable: STACKTRACE at org.jboss.resource.connectionmanager.CachedConnectionManager.registerConnection(CachedConnectionManager.java:278) at org.jboss.resource.connectionmanager.BaseConnectionManager2.allocateConnection(BaseConnectionManager2.java:524) at org.jboss.resource.connectionmanager.BaseConnectionManager2$ConnectionManagerProxy.allocateConnection(BaseConnectionManager2.java:941) at org.jboss.resource.adapter.jdbc.WrapperDataSource.getConnection(WrapperDataSource.java:89) at br.com.arthur.ejb.transactions.TransacaoTesteEJB.criaConexaoBD(TransacaoTesteEJB.java:33) at br.com.arthur.ejb.transactions.TransacaoTesteEJB.executaTransacao(TransacaoTesteEJB.java:43) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.jboss.aop.joinpoint.MethodInvocation.invokeTarget(MethodInvocation.java:122) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:111) at org.jboss.ejb3.EJBContainerInvocationWrapper.invokeNext(EJBContainerInvocationWrapper.java:69) at org.jboss.ejb3.interceptors.aop.InterceptorSequencer.invoke(InterceptorSequencer.java:73) at org.jboss.ejb3.interceptors.aop.InterceptorSequencer.aroundInvoke(InterceptorSequencer.java:59) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.jboss.aop.advice.PerJoinpointAdvice.invoke(PerJoinpointAdvice.java:174) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.interceptors.aop.InvocationContextInterceptor.fillMethod(InvocationContextInterceptor.java:72) at org.jboss.aop.advice.org.jboss.ejb3.interceptors.aop.InvocationContextInterceptor_z_fillMethod_11160700.invoke(InvocationContextInterceptor_z_fillMethod_11160700.java) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.interceptors.aop.InvocationContextInterceptor.setup(InvocationContextInterceptor.java:88) at org.jboss.aop.advice.org.jboss.ejb3.interceptors.aop.InvocationContextInterceptor_z_setup_11160700.invoke(InvocationContextInterceptor_z_setup_11160700.java) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.connectionmanager.CachedConnectionInterceptor.invoke(CachedConnectionInterceptor.java:62) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.entity.TransactionScopedEntityManagerInterceptor.invoke(TransactionScopedEntityManagerInterceptor.java:56) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.AllowedOperationsInterceptor.invoke(AllowedOperationsInterceptor.java:47) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.tx.NullInterceptor.invoke(NullInterceptor.java:42) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.stateless.StatelessInstanceInterceptor.invoke(StatelessInstanceInterceptor.java:68) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:79) at org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:190) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.tx.NullInterceptor.invoke(NullInterceptor.java:42) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.security.RoleBasedAuthorizationInterceptorv2.invoke(RoleBasedAuthorizationInterceptorv2.java:201) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.security.Ejb3AuthenticationInterceptorv2.invoke(Ejb3AuthenticationInterceptorv2.java:186) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:41) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.BlockContainerShutdownInterceptor.invoke(BlockContainerShutdownInterceptor.java:67) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.aspects.currentinvocation.CurrentInvocationInterceptor.invoke(CurrentInvocationInterceptor.java:67) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.ejb3.stateless.StatelessContainer.dynamicInvoke(StatelessContainer.java:421) at org.jboss.ejb3.remoting.IsLocalInterceptor.invokeLocal(IsLocalInterceptor.java:85) at org.jboss.ejb3.remoting.IsLocalInterceptor.invoke(IsLocalInterceptor.java:72) at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) at org.jboss.aspects.remoting.PojiProxy.invoke(PojiProxy.java:62) at $Proxy288.invoke(Unknown Source) at org.jboss.ejb3.proxy.impl.handler.session.SessionProxyInvocationHandlerBase.invoke(SessionProxyInvocationHandlerBase.java:207) at org.jboss.ejb3.proxy.impl.handler.session.SessionProxyInvocationHandlerBase.invoke(SessionProxyInvocationHandlerBase.java:164) at $Proxy384.executaTransacao(Unknown Source) at br.com.arthur.web.transactions.IniciaTransactionTesteServlet.doGet(IniciaTransactionTesteServlet.java:26) at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:190) at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:92) at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126) at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:829) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:598) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Thread.java:619)

Quando eu fecho a conexão assim que termina o meu fluxo, eu não tenho esse problema.

A dúvida é:
Eu tenho que fechar o meu objeto Connection? Ele não é um recurso caro? Toda vez que eu for iniciar uma transaction vou ter que ter um objeto Connection novo?

Obrigado!

8 Respostas

ctosin

Arthur,

Sim, você deve fechar. Na verdade nesse caso você não está realmente fechando a conexão. Você está apenas devolvendo ela ao pool, e trabalhar com conexões em pool não é tão caro quanto abrir e fechar conexões.

O que você não pode é terminar a sua transação, sair do teu método do EJB e manter a conexão aberta. Justamente por ser um recurso caro, você deve devolvê-lo ao pool para que outras requisições possam usá-la.

Abraço!

Mr_Arthur

a chamada do connection.close() então devolve para o pool? não fecha a conexão?
logo a chamada de dataSource.getConnection() apenas me tras uma conexão do pool?

ai, aí… o meu livro não diz esse tipo de coisa.
Isso está relacionado à EJB ou aos Containers?

Muito obrigado mesmo.

ctosin

Exatamente. A data source implementa um pool de conexões. Então quando você chamar o close() na sua Connection, ela volta para o pool.
A data source está relacionada ao seu servidor de aplicações. Qualquer servidor de aplicações que suporta Java (JBoss, GlassFish, etc.) possui a implementação de uma data source com pool de conexões, que você pode usar nos seus EJBs.

Recomendo a você não pegar a conexão quando o bean é construído (no método anotado com @PostConstruct). Porque se o seu EJB for stateless, você vai manter uma conexão aberta para múltiplas requisições e isto certamente lhe trará problemas. Outro detalhe é que se você abre no @PostConstruct, onde você irá fechar a conexão? No @PreDestroy? Mas e se o contêiner não decidir destruir o bean, sua conexão ficará aberta sempre?

Então tome cuidado. A conexão com o banco de dados deve receber uma atenção especial. O ideal é que você remova o atributo Connection do seu bean e pegue a conexão e devolva a conexão para o pool dentro do método do seu EJB. Dessa forma você garante que a conexão está ativa apenas durante o tempo que o seu método é executado.

Abraço!

Mr_Arthur

Carlos Tosin,

Muito obrigado mesmo.
Sua ajuda foi essencial.

Comecei com os estudos também sobre o servidor de aplicação JBoss pelo livro “JBoss in Action”.
Acredito que mesmo que eu não vá trabalhar diretamente com ele em uma empresa, ele vai me dar o conhecimento de que um servidor de aplicação é capaz.

Agradeço mais uma vez.

Mr_Arthur

Mais uma dúvida.

Fiz um teste para verificar como funciona o procedimento que você me informou Carlos.
Em cada chamada para meu EJB, eu impria o hashCode() do meu objeto Connection e o hashCode() do meu objeto EJB Stateless do lado do cliente. Resultado:
Um hash diferente para cada objeto connection, e o mesmo hash para cada EJB Stateless.

Com isso surgiu uma dúvida.
os objetos connection estão mesmo indo para o pool? o DataSource não está novos objetos sempre? por que os hashs de connection estão sempre diferentes?

Valeu galera!

ctosin

É possível sim que você tenha sempre objetos Connection diferentes sendo associados ao seu EJB. Aliás, não é só possível como é bastante provável.

Quando você inicializa o seu servidor de aplicações, são criadas diversas conexões com o banco, que permancem abertas. Estas conexões compõem o pool de conexões. Quando você precisa de uma conexão do pool, a data source entrega um desses objetos pro seu EJB, e quando você não quer mais a conexão você devolve ela pro pool. E é bastante provável que cada vez que o pool entregue pra você uma conexão, o objeto seja outro, e não o mesmo, já que existem diversos objetos Connection no pool, que são reaproveitados entre as requisições.

Bom, já que o assunto é data source, mais algumas coisas importantes… Quando você não devolve a conexão pro pool, chega um hora que o pool detecta que a conexão está demorando muito pra voltar. Quando isso acontece, ele “rouba” a conexão de volta, para poder disponibilizá-la para outras aplicações. Para isso existe um timeout que é configurado de acordo com o servidor de aplicação que está sendo usado.

Outra coisa é: o que acontece se o pool tem 10 conexões e a aplicação precisa de 11 conexões simultâneas? Isso também pode ser definido nas configurações do servidor de aplicação. Normalmente é assim: você especifica um valor mínimo e um valor máximo de conexões. Por exemplo: 10 e 50. Quando o servidor de aplicação é criado, o pool é iniciado com 10 conexões. Quando todas estão em uso e mais conexões são necessárias, o pool pode criar mais 10 conexões, por exemplo (esse número incremental também é configurável). Já quando as 50 conexões estão em uso e mais uma é necessária, a aplicação fica esperando até que alguém devolva alguma conexão pro pool poder distribuí-la. A data source também pode, a seu critério, fechar algumas conexões que estão no pool há bastante tempo e não estão sendo utilizadas para otimizar recursos.

Por isso três coisas são muito importantes:

[list]O dimensionamento do pool, para que ele comporte a quantidade de requisições por conexões de banco de dados.[/list]
[list]Devolver a conexão pro pool sempre que ela não for mais utilizada. Conexões que não estão no pool não podem ser usadas em outras requisições.[/list]
[list]Usar a conexão pelo menor tempo possível. Isso significa: pegar a conexão, fazer o que tem que fazer no banco, e devolver a conexão. Quanto mais você prende a conexão, menos escalável é o teu sistema e menos reaproveitamento de conexões você conseguirá ter.

Acho que é isso aí. Espero ter esclarecido.

Abraço!
Carlos

Mr_Arthur

Esqueci de dizer um fato importante…
Eu criei um cliente que fazia 1000 chamadas em um loop, e ainda assim todos os hashcodes ainda ficaram diferentes.

será que tem alguma má configuração no pool que eu estou utilizando?

Obrigado pela explicação Carlos.

ctosin

Tenho dúvidas se este teste que você está fazendo realmente vai te dizer alguma coisa. A implementação da data source pode variar. Não sei especificamente como funciona no JBoss, mas é perfeitamente possível que um objeto de uma classe que implementa a interface Connection seja criado toda vez que uma conexão é obtida pelo EJB. Internamente, este objeto pode usar uma conexão já aberta com o banco, mas como é construído para atender a uma requisição, você nunca terá a referência ao mesmo objeto. E vai saber como essa classe implementa o método hashCode()! Portanto não dá pra confiar na questão de checar as instâncias e os hash codes para saber o que está acontecendo (lembre-se que você tem uma interface Connection e não sabe como a classe implementa os métodos dessa interface por trás).

Pra você fazer o que você deseja, talvez seja interessante procurar se é possível você registrar um listener ou coisa parecida na data source, pra ser notificado a respeito do uso das conexões do pool. Dessa forma você conseguiria saber o que está acontecendo. Mas sinceramente nunca fiz isso, então tem que dar uma procurada.

Abraço

Criado 17 de dezembro de 2009
Ultima resposta 20 de dez. de 2009
Respostas 8
Participantes 2