Mais de uma base de dados no Hibernate - URGENTE

Pessoal,
Estou desenvolvendo uma aplicação que utiliza o legado da empresa, e precisarei acessar duas bases de dados.
Conforme sugere a documentação do hibernate, estou utilizando HibernateUtil para adquirir uma nova sessão e um filtro para interceptar toda a requisição ao servidor.
A questão é, dependendo do bean que estou acessando precisou conectar a base de dados correta.
Depois de uma semana de pesquisa cheguei a conclusão que preciso de dois SessionFactory, um para cada configuração.
Tentei utilizar um filtro parametrizável para adquirir a sessão correta, mas ainda não consegui fazer o rebuild da session conforme minha necessidade.
Pelo que li a respeito precisarei implementar a interface Interceptor para tal, mas sinceramente não consegui realizar com sucesso.

Alguém poderia me ajudar?? Segue abaixo o código do meu HibernateUtil e do Filtro

public class HibernateUtil {

	private static final SessionFactory sessionFactorySuporte;
	private static final SessionFactory sessionFactoryBaan;
	private static ThreadLocal<Session> sessions = new ThreadLocal<Session>();

	static {
		try {
			sessionFactoryBaan = new AnnotationConfiguration().configure(
					"hibernate.baan.cfg.xml").buildSessionFactory();
			sessionFactorySuporte = new AnnotationConfiguration().configure(
					"hibernate.suporte.cfg.xml").buildSessionFactory();
		} catch (Throwable ex) {
			System.out.println("Falha na criação da SessionFactory" + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	/**
	 * Abre uma nova sessão ou retorna a sessão corrente
	 * 
	 * @return Session: corrente
	 * @param bd:
	 *            int indicando de qual SessionFactory a session deve ser
	 *            retornada, sendo: 
	 *            	1 - Base Suporte (SQL Server) 
	 *            	2 - Base BaaN (Oracle)
	 */
	public static Session openSession(int bd) {
		switch (bd) {
		case 1:	
			sessions.set(sessionFactorySuporte.openSession());
			break;
		case 2:
			sessions.set(sessionFactoryBaan.openSession());
			break;
		}
		return sessions.get();
	}

	/**
	 * Fecha a sessão corrente
	 */
	public static void closeCurrentsession() {
		sessions.get().close();
		sessions.set(null);
	}

	/**
	 * Retorna a sessão corrente
	 * 
	 * @return Session: Sessão corrente
	 */
	public static Session currentSession() {
		return sessions.get();
	}

}
public class HibernateSessionFilter implements Filter {

	private static Log log = LogFactory.getLog(HibernateSessionFilter.class);

	@SuppressWarnings("unchecked")
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		
		// Verifica a SessionFactory necessária para requisição
		HttpServletRequest httpReq = (HttpServletRequest) request;
		String url = httpReq.getRequestURL().toString();
		int base = 1;
		
		if (url.toLowerCase().contains("/suporte/")) base = 1;
		if (url.toLowerCase().contains("/baan/")) base = 2;
		
		try {
			log.debug("Iniciando Transação");
			System.out.println("Factory: " + base);
			HibernateUtil.openSession(base);
			HibernateUtil.currentSession().beginTransaction();

			// Chama próximo filtro (continua processando requisição
			chain.doFilter(request, response);

			// Comita transação
			log.debug("Comitando transação na base de dados");
			HibernateUtil.currentSession().getTransaction().commit();

		} catch (StaleObjectStateException staleEx) {
			throw staleEx;
		} catch (Throwable ex) {
			// Somente rollback
			ex.printStackTrace();
			try {
				if (HibernateUtil.currentSession().getTransaction().isActive()) {
					log.debug("Tentando rollblack após exceção");
					HibernateUtil.currentSession().getTransaction().rollback();
				}
			} catch (Throwable rbEx) {
				log.error("Não foi possível efetuar rollback", rbEx);
			}
			throw new ServletException(ex);
		}
	}

	public void init(FilterConfig filterConfig) throws ServletException {
	}

	public void destroy() {
	}

}

Dificilmente você vai conseguir implementar isso de maneira elegante com Hibernate.

Usar dois bancos de dados requer transação distribuída. E isso é um problemão.

Talvez você consiga algo melhor com um servidor de aplicação e entity beans do EJB3, com transação declarativa.

Gabriel, em nenhum momento ele disse que precisa de transacao entre mais de um banco. Ele so precisa que o sistema saiba qual das duas bases acessar de acordo com uma configuracao.

Wilson, pra mim isso ai ta certo (apesar de eu nao gostar desse INT sendo passado como argumento). Porque voce diz que nao funciona? Que erro da? Qual a stack trace? etc

Paulo,

Não dá erro nenhum. O problema é que dentro do mesmo handler as vezes eu preciso transacionar com as duas bases.
Consegui resolver de outra maneira.
Inclusive gostaria de compartilhar com vocês para saber se é uma forma segura e correta.
Estou utilizando interceptor com annotation, através da interface MethodInterceptor.
Encontrei essa solução através de um artigo do Davi Luan Carneiro - “Controle Transacional no Hibernate 3 com Anotações”. Com esse artigo, muito bom por sinal, pude adaptar para a minha necessidade.
Com ela eu consigo interceptar o método que vou precisar de uma transação com o banco e de qual banco.
Assim, eu só abro uma transação quando necessário. Bem diferente dos Filters simples, pois se eu estiver acessando um JSP estático ele vai abrir uma transação mesmo assim.

Segue abaixo os códigos para avaliação de vocês e comentários.

  1. Primeiramente eu criei um annotation com um atributo para informar a base que quero transacionar.
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HibernateTransaction {
	String sessionFactory() default "";
}
  1. Depois criei o interceptor para tratar as transações com o banco.
public class HibernateInterceptor implements MethodInterceptor {

	public Object intercept(Object object, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
		Object result = null;
		String sessionFactory = "";
		if (isTransactional(object, method)) {
			sessionFactory = getValueHibernateTransaction(object, method);
			if (sessionFactory.trim().compareToIgnoreCase("baan") == 0) {
				HibernateUtilBaan.beginTransaction();
			} else {
				if (sessionFactory.trim().compareToIgnoreCase("suporte") == 0) {
					HibernateUtilSuporte.beginTransaction();
				} 
			}
		}
		try {
			result = methodProxy.invokeSuper(object, args);
			if (sessionFactory.trim().compareToIgnoreCase("baan") == 0) {
				HibernateUtilBaan.commitTransaction();
			} else {
				if (sessionFactory.trim().compareToIgnoreCase("suporte") == 0) {
					HibernateUtilSuporte.commitTransaction();
				} 
			}
		} catch (Exception e) {
			if (sessionFactory.trim().compareToIgnoreCase("baan") == 0) {
				HibernateUtilBaan.rollbackTransaction();
			} else {
				if (sessionFactory.trim().compareToIgnoreCase("suporte") == 0) {
					HibernateUtilSuporte.rollbackTransaction();
				} 
			}
			throw e;
		}
		return result;
	}

	private boolean isTransactional(Object object, Method method)
			throws Exception {
		Annotation annotation = method
				.getAnnotation(HibernateTransaction.class);
		return annotation == null ? false : true;
	}

	private String getValueHibernateTransaction(Object object, Method method)
			throws Exception {
		return method.getAnnotation(HibernateTransaction.class)
				.sessionFactory();
	}

}
  1. Depois tive que criar a classe TransactionClass.
    Esta classe deve ser utilizada para interceptar os métodos utilizando CGLib.
    Todas as classes que deverão ter seus métodos interceptados devem ser criadas
    através desta classe a partir do objeto Enhancer.
public class TransactionClass {
	@SuppressWarnings("unchecked")
	public static Object create(Class beanClass, Class interceptorClass) throws Exception {
		HibernateInterceptor interceptor = (HibernateInterceptor) interceptorClass
				.newInstance();
		Object object = Enhancer.create(beanClass, interceptor);
		return object;
	}
  1. E por fim basta anotar os métodos que precisam de transação.
@HibernateTransaction(sessionFactory = "baan")
	public void salvar(ApontamentoDeQuantidade apontamentoDeQuantidade)
			throws Exception {
		Dao<ApontamentoDeQuantidade> dao = new Dao<ApontamentoDeQuantidade>(
				HibernateUtilBaan.currentSession(),ApontamentoDeQuantidade.class);
		dao.salvar(apontamentoDeQuantidade);
	}

Com isso, o método salvar é interceptado e uma transação é aberta antes de sua invocação.
Assim que saio do método, a transação é comitada.
Além desses códigos também tenho um HibernateUtil e um cfg.xml para cada base de dados que preciso.

Fiz assim e está funcionando, o que vocês acham??

obrigado,

Luiz Roberto Z. Barrilari
Analista de Sistemas
Cestari Indl. e Coml. SA

Bom… pelo que entendi você realmente não tem transação distribuida. Você precisa ter duas transações ao mesmo tempo com duas bases diferentes. É isso?

Sugiro você usar a classe ManagedSessionContext do hibernate.
Se você já criou dois tipos de HibernateUtil (eu preferia um só), o interceptor é desnecessário, mas é uma solução elegante usando annotations. Você poderia ter descomplicado um pouco usando Aspectos ao invés desse interceptor. Além do que, acho que teria uma performance melhor.

[]'s

Legal Márcio…

Você tem algum exemplo mais concreto dessa solução, ou algum artigo sobre isso para eu analisar.

Quanto ao seu comentário, realmente como tenho dois HibernateUtil não precisaria do interceptor.
Entretanto, para evitar ter que escrever sempre as mesmas coisas em todos os métodos transacionais (abre sessão, abre transação, commit ou rollback da transação) eu optei em utilizar o interceptor para que faça isso por mim.

Obrigado,