EJB3 Transações

Olá pessoal,

Estou com um problema para manter a transação após a chamada sucessiva de session beans.

O problema é o seguinte:

quero que ao se alterar uma entidade, seja registrado um log com as alterações. Se não for possível registrar o log, a alteração será abortada. O log só será escrito se e somente se a alteração for feita com sucesso.

Para isso usei um interceptor no método de modificar que por sua vez chama outro EJB que guarda o log. Segue o código:

A classe de repositório das entidades

@Stateful
public class RepositorioJTA implements RemoteRepositorio, LocalRepositorio {

	@PersistenceContext(unitName = "agenda")
	private EntityManager entityManager;
        (...)

        @Interceptors( { ChangeLoggerInterceptor.class })
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public void modificarUsuario(Usuario usuario) {
		entityManager.merge(usuario);
		
	}
}

A classe interceptor

public class ChangeLoggerInterceptor {

	@PersistenceContext(unitName = "agenda")
	private EntityManager entityManager;

	public ChangeLoggerInterceptor() {

	}

	@AroundInvoke
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public Object registrar(InvocationContext context) throws Exception {
		Object parametroNovo = context.getParameters()[0];

		Object id = null;
		Long version = null;
		Method[] methods = parametroNovo.getClass().getDeclaredMethods();
		int i = 0;
		while ((id == null || version == null) && i < methods.length) {
			methods[i].setAccessible(true);
			if (methods[i].getAnnotation(Id.class) != null) {
				id = methods[i].invoke(parametroNovo);
			}
			if (methods[i].getAnnotation(Version.class) != null) {
				version = (Long) methods[i].invoke(parametroNovo);
			}
			i++;
		}
		if (version == null) {
			version = new Long(0);
		}
		Object parametroAntigo = entityManager.find(parametroNovo.getClass(),
				id);
		Date d = Calendar.getInstance().getTime();
		Object ref = new InitialContext().lookup("ChangeLoggerBean/remote");
		ChangeLogger logger = (ChangeLogger) PortableRemoteObject.narrow(ref,
				ChangeLoggerRemote.class);
		logger.log("eu", d, parametroNovo, parametroAntigo, (Integer) id,
				version);

		return context.proceed();
	}

}

O EJB que registra o log:

@Stateful
public class ChangeLoggerBean implements ChangeLoggerLocal, ChangeLoggerRemote {
	
	@PersistenceContext(unitName="log")
	public EntityManager entityManager;

	

	public Collection<Diferenca> modificacoes(Object novo, Object antigo) {
		Vector<Diferenca> itens = new Vector<Diferenca>();
		Field[] fields = novo.getClass().getDeclaredFields();

		for (Field f : fields) {
			try {
				f.setAccessible(true);
				if (f.isAnnotationPresent(Log.class)
						&& !f.get(novo).equals(f.get(antigo))) {
					Diferenca d = new Diferenca();
					Log log = (Log) f.getAnnotation(Log.class);
					if (log.tipo() == TipoObjetoLog.GRAVAVEL) {

						d.setClasseReferenciada(novo.getClass().toString());
						d.setClasseObjeto(f.get(antigo).getClass().toString());
						d.setNomeCampo(f.getName());
						d.setValorAntigo(f.get(antigo).toString());
						d.setValorNovo(f.get(novo).toString());
						itens.add(d);
					} else if (log.tipo() == TipoObjetoLog.EXPANSIVEL) {
						itens.addAll(modificacoes(f.get(antigo), f.get(novo)));
					}
				}

			} catch (IllegalAccessException e) {

			} finally {
				f.setAccessible(true);
			}
		}
		return itens;
	}
	
	@TransactionAttribute(TransactionAttributeType.REQUIRED)		
	public void log(String usuario, Date modificacao, Object objetoNovo,
			Object objetoAntigo, int id, long version) {
		ItemChangeLog log = new ItemChangeLog();
		log.setDiferencas(this.modificacoes(objetoNovo, objetoAntigo));
		log.setHorario(modificacao);
		log.setIdObject(id);
		log.setType(objetoAntigo.getClass().toString());
		log.setUser(usuario);
		log.setVersion(version);
		entityManager.persist(log);
	}
}

O erro está acontecendo nessa ultima linha “entityManager.persist(log);”. O stacktrace é esse aqui:

Exception occurred during event dispatching:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: Cannot open connection
	at org.jboss.ejb3.tx.Ejb3TxPolicy.handleInCallerTx(Ejb3TxPolicy.java:87)
	at org.jboss.aspects.tx.TxPolicy.invokeInCallerTx(TxPolicy.java:130)
	at org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:195)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:95)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.ejb3.stateful.StatefulInstanceInterceptor.invoke(StatefulInstanceInterceptor.java:83)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.aspects.security.AuthenticationInterceptor.invoke(AuthenticationInterceptor.java:77)
	at org.jboss.ejb3.security.Ejb3AuthenticationInterceptor.invoke(Ejb3AuthenticationInterceptor.java:110)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:46)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.ejb3.asynchronous.AsynchronousInterceptor.invoke(AsynchronousInterceptor.java:106)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.ejb3.stateful.StatefulContainer.dynamicInvoke(StatefulContainer.java:335)
	at org.jboss.ejb3.remoting.IsLocalInterceptor.invokeLocal(IsLocalInterceptor.java:81)
	at org.jboss.ejb3.remoting.IsLocalInterceptor.invoke(IsLocalInterceptor.java:72)
	at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
	at org.jboss.ejb3.stateful.StatefulRemoteProxy.invoke(StatefulRemoteProxy.java:139)
	at $Proxy99.log(Unknown Source)

O que é que estou fazendo errado?

Agradeço desde já a ajuda.

Thiago Jamir

porque vc está usando duas unidades de persistência ?

A idéia é colocar os objetos em um banco, e o log em outro banco à parte.

Eu consegui solucionar o problema acima, usando XADatasource (mais informações aqui. O problema era exatamente esse, para usar duas unidades de persistência, eu deveria usar esse tipo de Datasource.
Obrigado por responder de qualquer maneira.

É isso aí, se vc precisa de dois bancos de dados na mesma transação, precisa de conexões que suportam two phase commit