Acessar dois bancos de dados da mesma aplicação

Pessoal finalmente chegou o dia em que vou precisar acessar dois bancos de dado diferente na mesma aplicação (Um oracle e um Mysql) , como faço isso de maneira elegante no vraptor 3 ? Lembro que vi algum topico um dia sobre essa mesma questão , mas procurei procurei e não encontrei ? Uso hibernate puro como ORM .

Entao eu teria 2 arquivos de configuracao certo ? hibernate.oracle.cgf.xml e hibernate.mysql.cgf.xml cada um com sua conexão etc. E o resto o que eu preciso ??

Olá boneazul,

você pretende usar os dois bancos simultaneamente? Pois se não, você pode usar a configuração programática.

E ai fica fácil de trocar entre os SessionFactories…

Você cria 2 SessionFactories um para cada banco.

A configuração programática fica da seguinte forma:

[code]AnnotationConfiguration configuration = new AnnotationConfiguration();

	configuration.setProperty("hibernate.connection.username",
			"user");

	configuration.setProperty("hibernate.connection.password",
			"senha");

	configuration.setProperty("hibernate.connection.url",
			"url");

	configuration.setProperty("hibernate.connection.driver_class",
			"driver");

	configuration.setProperty("hibernate.dialect",
			"dialect");

	configuration.setProperty("hibernate.hbm2ddl.auto",
			"update");

	configuration.setProperty("show_sql", "false");

	configuration.setProperty("format_sql",
			"false");

	configuration.setProperty("hibernate.connection.provider_class",
			"org.hibernate.connection.C3P0ConnectionProvider");
	configuration.setProperty("hibernate.c3p0.timeout", "30");
	configuration.setProperty("hibernate.c3p0.min_size", "5");
	configuration.setProperty("hibernate.c3p0.max_size", "20");
	configuration.setProperty("hibernate.c3p0.max_statements", "50");

	configuration.setProperty("hibernate.generate_statistics", "true");

            configuration.addAnnotatedClass(Classe.class);

	return configuration.buildSessionFactory();[/code]

[quote=Rafael Guerreiro]Olá boneazul,

você pretende usar os dois bancos simultaneamente? Pois se não, você pode usar a configuração programática.

E ai fica fácil de trocar entre os SessionFactories…

Você cria 2 SessionFactories um para cada banco.

A configuração programática fica da seguinte forma:

[code]AnnotationConfiguration configuration = new AnnotationConfiguration();

	configuration.setProperty("hibernate.connection.username",
			"user");

	configuration.setProperty("hibernate.connection.password",
			"senha");

	configuration.setProperty("hibernate.connection.url",
			"url");

	configuration.setProperty("hibernate.connection.driver_class",
			"driver");

	configuration.setProperty("hibernate.dialect",
			"dialect");

	configuration.setProperty("hibernate.hbm2ddl.auto",
			"update");

	configuration.setProperty("show_sql", "false");

	configuration.setProperty("format_sql",
			"false");

	configuration.setProperty("hibernate.connection.provider_class",
			"org.hibernate.connection.C3P0ConnectionProvider");
	configuration.setProperty("hibernate.c3p0.timeout", "30");
	configuration.setProperty("hibernate.c3p0.min_size", "5");
	configuration.setProperty("hibernate.c3p0.max_size", "20");
	configuration.setProperty("hibernate.c3p0.max_statements", "50");

	configuration.setProperty("hibernate.generate_statistics", "true");

            configuration.addAnnotatedClass(Classe.class);

	return configuration.buildSessionFactory();[/code][/quote]

Sim os bancos são usados simultaneamente por toda a aplicação . Mas o spring não vai deixar eu criar 2 SessionFactories que retornam o mesmo objeto no caso Session , como vou diferenciar qual Session ele está recebendo na injeção de dependencia ??

Tem algum exemplo mais concreto com o a SessionFactory se possível ?

Eu gerencio isso manualmente. Eu uso um para a injeção de dependências e outro eu uso manualmente.

Mas a diferença é que eu não uso ao mesmo tempo.

vc pode criar duas sessionFactories diferentes sim, mas elas precisam ter nomes diferentes…

no spring mais novo deve dar pra fazer:

@Component // do spring ou qqer outra anotação dele
@Named("oracle")
public class CriaSFDaOracle ... {

}

e na hora de receber vc faz:

@Autowired
@Named("oracle")
private SessionFactory factory;

vc pode trocar esse @Named por @Qualifier qu deve funcionar tb

Lucas,

muito interessante isso, vou tentar implementá-lo nos meus códigos. Fica muito mais limpo.

[quote=Lucas Cavalcanti]vc pode criar duas sessionFactories diferentes sim, mas elas precisam ter nomes diferentes…

no spring mais novo deve dar pra fazer:

@Component // do spring ou qqer outra anotação dele
@Named("oracle")
public class CriaSFDaOracle ... {

}

e na hora de receber vc faz:

@Autowired
@Named("oracle")
private SessionFactory factory;

vc pode trocar esse @Named por @Qualifier qu deve funcionar tb[/quote]

Bom vamo lá então…

Minha factory

@Component
@ApplicationScoped
@Qualifier(value="mysql")
@SuppressWarnings("deprecation")
public class MysqlSessionFactoryCreator implements ComponentFactory<SessionFactory> {

	private SessionFactory factory;

	@PostConstruct
	public void create() {
		factory = new AnnotationConfiguration().configure("hibernate.mysql.cfg.xml").buildSessionFactory();
	}

	public SessionFactory getInstance() {
		return factory;
	}

	@PreDestroy
	public void destroy() {
		factory.close();
	}

}

Meu controller

@Component
public class AcaoRepositoryImpl extends GenericRepositoryImpl<Acao> implements
		AcaoRepository {

	@Qualifier(value="mysql")
	@Autowired
	private SessionFactory factory;
	public AcaoRepositoryImpl(Session session,SessionFactory factory) {
		super(session);
		this.factory=factory;
	}
}

gera a seguinte exception

No unique bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: [br.com.jslsolucoes.seguranca.provider.mysql.MysqlSessionFactoryCreator, br.com.caelum.vraptor.util.hibernate.SessionFactoryCreator]

Pois ja utilizo o pacote br.com.caelum.vraptor.util.hibernate no web.xml para a implementacao padrao que é do oracle.

Não estou entendendo direito a ideia to usando o spring 3.0.5

A ideia é que você dá nome ao parametro que quer receber e passa a recebê-lo, chamando pelo nome.

vc não vai poder usar o padrão do VRaptor… na verdade acho que nem o ComponentFactory vai funcionar…

tire o pacote do hibernate do web.xml e faça os dois componentFactory anotados com @Qualifier. Se vc usa session direto, vc também vai precisar fazer a mesma coisa com as factories de session.

se não funcionar com ComponentFactory, tente usar o FactoryBean do spring… se não funcionar também, troque o @Component do VRaptor pelo do spring

PS: esse tipo de configuração no Guice fica mais legal, pq dá pra deixar um dos caras como padrão, e o outro só aceitar se estiver anotado. Se puder trocar pro Guice posso te mostrar como fazer isso.

[quote=Lucas Cavalcanti]vc não vai poder usar o padrão do VRaptor… na verdade acho que nem o ComponentFactory vai funcionar…

tire o pacote do hibernate do web.xml e faça os dois componentFactory anotados com @Qualifier. Se vc usa session direto, vc também vai precisar fazer a mesma coisa com as factories de session.

se não funcionar com ComponentFactory, tente usar o FactoryBean do spring… se não funcionar também, troque o @Component do VRaptor pelo do spring

PS: esse tipo de configuração no Guice fica mais legal, pq dá pra deixar um dos caras como padrão, e o outro só aceitar se estiver anotado. Se puder trocar pro Guice posso te mostrar como fazer isso.

[/quote]

Já troquei pelo guice , como ficaria pra deixar a session factory e session padrao para o oracle e receber a session anotada do mysql quando precisar ???

primeiro vc precisa criar essa classe e configuração:

dentro da classe CustomModule, crie dois métodos:

@Provides
public SessionFactory sessionFactoryPadrao() {
   //cria o sf padrão e retorna
}
@Provides @Named("mysql")
public SessionFactory sessionFactoryMysql() {
   //cria o sf do mysql e retorna
}

daí vc vai apagar os componentFactories de sessionFactory

isso seria o suficiente para as sfs… daí qdo vc quer o oracle é só pedir uma SessionFactory… e se vc quiser o mysql é só pedir um @Named(“mysql”) SessionFactory

como vc pretende usar as sessions?

PS: mais info: http://code.google.com/p/google-guice/wiki/ProvidesMethods

Estou tendo um problema parecido. Porém não posso mudar o pro guice, tenho que continuar usando o spring.

Resumo do problema:

Acessar duas bases de dados diferentes e obter Session para cada banco em tempo de compilação. Ex: MysqlDao tem uma Session referente ao banco mysql e OracleDao tem uma Session referente ao banco oracle.

Como estou fazendo:

MysqlSessionFactory

@Component
@ApplicationScoped
@Qualifier("mysql")
@SuppressWarnings("deprecation")
public class MysqlSessionFactoryCreator implements ComponentFactory<SessionFactory> {

	private SessionFactory factory;

	@PostConstruct
	public void create() {
		factory = new AnnotationConfiguration().configure("hibernate.mysql.cfg.xml").buildSessionFactory();
	}
	
	@Override
	public SessionFactory getInstance() {
		return factory;
	}

	@PreDestroy
	public void destroy() {
		factory.close();
	}

}

OracleSessionFactory

@Component
@Qualifier("oracle")
@ApplicationScoped
@SuppressWarnings("deprecation")
public class OracleSessionFactoryCreator implements ComponentFactory<SessionFactory> {
	
	private SessionFactory factory;

	@PostConstruct
	public void create() {
		factory = new AnnotationConfiguration().configure("hibernate.oracle.cfg.xml").buildSessionFactory();
	}

	@Override
	public SessionFactory getInstance() {
		return factory;
	}

	@PreDestroy
	public void destroy() {
		factory.close();
	}
}

MysqlSessionCreator

@Component
@RequestScoped
@Qualifier("mysql")
public class MysqlSessionCreator implements ComponentFactory<Session> {
	@Autowired
	@Qualifier("mysql")
	private final SessionFactory factory;
	private Session session;

	public MysqlSessionCreator(SessionFactory factory) {
		this.factory = factory;
	}

	@PostConstruct
	public void create() {
		this.session = factory.openSession();
	}

	@Override
	public Session getInstance() {
		return session;
	}
	
	@PreDestroy
	public void destroy() {
		this.session.close();
	}

}

OracleSessionCreator

@Component
@Qualifier("oracle")
@RequestScoped
public class OracleSessionCreator implements ComponentFactory<Session> {
	@Autowired
	@Qualifier("oracle")
	private final SessionFactory factory;
	private Session session;

	public OracleSessionCreator(SessionFactory factory) {
		this.factory = factory;
	}

	@Override
	public Session getInstance() {
		return session;
	}
	
	@PostConstruct
	public void create() {
		this.session = factory.openSession();
	}

	@PreDestroy
	public void destroy() {
		this.session.close();
	}

}

HibernateProvider

public class HibernateProvider extends SpringProvider {

	@Override
	protected void registerCustomComponents(ComponentRegistry registry) {
		registry.register(OracleSessionCreator.class, OracleSessionCreator.class);
		registry.register(OracleSessionFactoryCreator.class, OracleSessionFactoryCreator.class);
		registry.register(MysqlSessionCreator.class, MysqlSessionCreator.class);
		registry.register(MysqlSessionFactoryCreator.class, MysqlSessionFactoryCreator.class);
	}
}

E finalmente o Dao

MysqlDao

@Component
public class MysqlDao {
	@Autowired
	@Qualifier("mysql")
	private Session session;

	public MysqlDao(Session session) {
		this.session = session;
	}
	
	public void algumMetodo(){
		return;
	}
}

Estou recebendo o seguinte erro:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: [br.jus.tjpi.correicao.hibernate.ThemisSessionFactoryCreator, br.jus.tjpi.correicao.hibernate.CorreicaoSessionFactoryCreator]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:796)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:703)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:795)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:723)
	... 96 more

Somente a anotação @Component do MysqlDao é do vraptor as demais são do spring.

Alguém pode me ajudar?

remova a classe HibernateProvider…

no caso do spring, vc não pode usar o mesmo qualifier pra session e pra sessionFactory, tente mudar.

Lucas,

fiz o que vc sugeriu, mas apareceu agora está aparecendo o erro:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [org.hibernate.SessionFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:920)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:789)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:703)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:795)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:723)
	... 28 more

Criei o applicationContext.xml e coloquei o caminho do pacote, mas o erro continuou.
Alguma outra sugestão?

se vc usou @Qualifier na classe, vc não pode colocar o @Component do VRaptor, tem que ser as anotações do spring (tem que ter o component-scan no xml do spring)

troca por @Component do spring, deixe os @Qualifier e troque o ComponentFactory por FactoryBean do spring…

isso deve funcionar.

Lucas,

consegui resolver o problema aqui.

O erro anterior estava acontecendo porque esqueci de incluir o listener do spring no web.xml, depois de incluído voltou a aparecer o erro:

No unique bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: 

Após algumas debugadas percebi que o que estava faltando era uma anotação para indicar qual das implementações seria a primária. Fiz isso através da anotação @Primary.
Tive que mudar de ComponentFactory para FactoryBean. Também acabei trocando a anotação @Qualifer por @Named, mas isso não interfere nesse problema.

Vou postar aqui como ficou.

MysqlSessionFactory

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Named;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;


@Primary
@Component
@SuppressWarnings("deprecation")
@Named("mysqlSessionFactory")
public class MysqlSessionFactoryCreator implements FactoryBean<SessionFactory> {

	private SessionFactory factory;

	@PostConstruct
	public void create() {
		factory = new AnnotationConfiguration().configure("hibernate.mysql.cfg.xml").buildSessionFactory();
	}

	@PreDestroy
	public void destroy() {
		factory.close();
	}

	@Override
	public SessionFactory getObject() throws Exception {
		return factory;
	}

	@Override
	public Class<?> getObjectType() {
		return SessionFactory.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

}

OracleSessionFactory

@Component
@SuppressWarnings("deprecation")
@Named("oracleSessionFactory")
public class OracleSessionFactoryCreator implements FactoryBean<SessionFactory> {
	
	private SessionFactory factory;

	@PostConstruct
	public void create() {
		factory = new AnnotationConfiguration().configure("hibernate.oracle.cfg.xml").buildSessionFactory();
	}

	@PreDestroy
	public void destroy() {
		factory.close();
	}

	@Override
	public SessionFactory getObject() throws Exception {
		return factory;
	}

	@Override
	public Class<?> getObjectType() {
		return SessionFactory.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}
}

MysqlSessionCreator

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Named;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Primary
@Component
@Named("mysql")
@Scope(value="request")
public class MysqlSessionCreator implements FactoryBean<Session> {
	@Autowired
	@Named("mysqlSessionFactory")
	private SessionFactory factory;
	private Session session;

	public MysqlSessionCreator(SessionFactory factory) {
		this.factory = factory;
	}

	@PostConstruct
	public void create() {
		this.session = factory.openSession();
	}

	@PreDestroy
	public void destroy() {
		this.session.close();
	}

	@Override
	public Session getObject() throws Exception {
		return session;
	}

	@Override
	public Class<?> getObjectType() {
		return Session.class;
	}

	@Override
	public boolean isSingleton() {
		return false;
	}

}

OracleSessionCreator

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Named;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Named("oracle")
@Scope(value="request")
public class OracleSessionCreator implements FactoryBean<Session> {
	@Autowired
	@Named("oracleSessionFactory")
	private final SessionFactory factory;
	private Session session;

	public OracleSessionCreator(SessionFactory factory) {
		this.factory = factory;
	}

	@PostConstruct
	public void create() {
		this.session = factory.openSession();
	}

	@PreDestroy
	public void destroy() {
		this.session.close();
	}

	@Override
	public Session getObject() throws Exception {
		return session;
	}

	@Override
	public Class<?> getObjectType() {
		return Session.class;
	}

	@Override
	public boolean isSingleton() {
		return false;
	}

}

MysqlDao

import javax.inject.Named;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;

import br.com.caelum.vraptor.ioc.Component;
@Component  
public class MysqlDao {  
    @Autowired
	@Named("mysql")
	private Session session; 
  
    public MysqlDao(Session session) {  
        this.session = session;  
    }  
      
    public void algumMetodo(){  
        return;  
    }  
}  

Configuração do listener web.xml

	<listener>  
		<listener-class>  
	        org.springframework.web.context.ContextLoaderListener  
		</listener-class>  
	</listener>

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

       <context:component-scan base-package="br.com.foo.hibernate"/>
       <context:annotation-config/>
</beans>

Existem alguma possibilidade de futuramente o vraptor já trazer esse tipo de configuração?

Obrigado pela ajuda.

o que vc quer dizer com “esse tipo de configuração”?

o VRaptor já tem algo pronto pra um banco só… se vc precisa de dois ou mais, tem que ser algo na sua app mesmo, é mais difícil generalizar.

como vc tá usando o Spring, vc pode usar o gerenciamento de sessão e transação pelo spring, bem mais fácil do que criar essas classes.

e IMPORTANTE: NÃO coloque as classes que criam a Session em escopo de “session”!!! senão cada usuário do seu sistema vai segurar uma session/conexão com o banco diferente! Mude o escopo pra “request”

Lucas,

Com “esse tipo de configuração” quis dizer o seguinte:

Por que não utilizar a anotação @Named em conjunto as anotações do vraptor @Component, @ApplicationScoped, @SessionScoped, etc
e com o gerenciamento que o vraptor já faz, “encapsulando” o spring, para possibilitar que caso exista mais de uma implementação para
uma mesma interface possa ser utilizado a anotação @Named para instanciar a desejada?

Desta forma não seria necessário se preocupar com as configurações do spring, apesar de serem poucas.

PS: Errei o nome do escopo. Você tem razão, o escopo correto é request e não session como eu coloquei. Vou editar o post para evitar que alguém cometa esse erro.

o @Named (javax.inject se eu não me engano) funciona tanto no Spring qto no Guice, e pode ser que funcione no Pico também…

da mesma forma o @Inject funciona para receber as dependências, no spring, guice e pico.