VRaptor + Hibernate Instancias de banco de dados distintos [RESOLVIDO]

Boa Tarde, estou com o seguinte problema, e não sei como resolver qual a melhor forma.

Preciso desenvolver um sistema WEB, que deve ser multiempresas. Terei apenas um sistema, mas que pode acessar bancos diferentes conforme a empresa informada.

Para isto utilizarei o Vraptor + PostGresql + Jquery ou Extjs.

Na tela de login, o usuário deve identificar sua empresa, o usuário e senha.

Para cada id de empresa, terei uma instancia do banco dados especifico. Todos no mesmo servidor.

Como posso fazer, para que o SessionFactory do Vraptor, saiba qual banco de dados ele precisa usar?
Tem como eu mudar o hibernate.cfg.xml, conforme for usar.

Espero ter sido claro na exposição do problema, e conto com a ajuda.

Obrigado

O que vc quer fazer se chama Multi Tenancy, e existe um jeito de fazer usando o Hibernate 4 nativamente.

Outra coisa que vc pode fazer é criar um componente application scoped que tenha as sessionFactories de todas as empresas, e um ComponentFactory que recebe o usuario logado e seleciona a sessionfactory apropriada para abrir a sessão.

[quote=Lucas Cavalcanti]O que vc quer fazer se chama Multi Tenancy, e existe um jeito de fazer usando o Hibernate 4 nativamente.

Outra coisa que vc pode fazer é criar um componente application scoped que tenha as sessionFactories de todas as empresas, e um ComponentFactory que recebe o usuario logado e seleciona a sessionfactory apropriada para abrir a sessão.[/quote]

Lucas, com base no que você falou, e pesquisando aqui mesmo no guj, cheguei a uma possível solução. seria corretep.

@ApplicationScoped
@Component
public class CriadorDeSessionFactory implements ComponentFactory<SessionFactory> {

	private Map<Tenant, SessionFactory> conexoes = new HashMap<Tenant, SessionFactory>();
	private Tenant tenant; 
	
	public CriadorDeSessionFactory(x) {
		this.tenant = new Tenant();
	}
	
	public SessionFactory  abre() {
		SessionFactory factory;	
		
		Configuration configuration = new Configuration();
		String arquivoConfiguracao = new StringBuilder().append(this.tenant).append(".cfg.xml").toString();
		configuration.configure(arquivoConfiguracao);
		
		factory = configuration.buildSessionFactory();
		this.conexoes.put(this.tenant, factory);

		return factory;
	}
	
	public SessionFactory getInstance() {
		SessionFactory conexao = this.conexoes.get(tenant); 
		if (conexao == null) {
			conexao = abre();
		}
		
		return conexao;
	}
	
}
@RequestScoped
@Component
public class CriadorDeSession implements ComponentFactory<Session> {

		private final SessionFactory factory;
		private Session session;
		
		public CriadorDeSession(SessionFactory factory) {
			this.factory = factory;
		}
		
		@PostConstruct
		public void abre() {
			this.session = factory.openSession();
		}
		
		public Session getInstance() {
			return this.session;
		}
		
		@PreDestroy
		public void fecha() {
			this.session.close();
		}
}
@Component
public class Tenant {

	private String tenantName;
	
	public Tenant() {
		this.tenantName = "demo";
	}

	public String getTenantName() {
		return tenantName;
	}
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>MyApp</display-name>
  <filter>
    <filter-name>vraptor</filter-name>
    <filter-class>br.com.caelum.vraptor.VRaptor</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>vraptor</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
  </filter-mapping>
  <session-config>
    <session-timeout>15</session-timeout>
  </session-config>
  <context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
    <param-value>pt_BR</param-value>
  </context-param>
  <context-param>
    <param-name>br.com.caelum.vraptor.packages</param-name>
    <param-value>br.com.caelum.vraptor.util.extjs</param-value>
  </context-param>
</web-app>

demo.cfg.xml

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration> 

<session-factory>
	<property name="hibernate.connection.username">postgres</property>
	<property name="hibernate.connection.password">admin</property>
	<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/cygnus</property>

	<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
	<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
	<property name="hibernate.hbm2ddl.auto">update</property>
	<property name="show_sql">true</property>
	<property name="format_sql">true</property>
	
	<property name="hibernate.connection.provider_class">
  		org.hibernate.connection.C3P0ConnectionProvider
	</property>
	<property name="hibernate.c3p0.min_size">1</property>
	<property name="hibernate.c3p0.max_size">20</property>
	<property name="hibernate.c3p0.timeout">30</property>
	<property name="hibernate.c3p0.idle_test_period">100</property>
	
	<mapping class="br.com.cygnus.desktop.dominio.Application"/>
	<mapping class="br.com.cygnus.desktop.dominio.ApplicationConfig"/>
	<mapping class="br.com.cygnus.desktop.dominio.ApplicationPermission"/>
	<mapping class="br.com.cygnus.desktop.dominio.Configuration"/>
	<mapping class="br.com.cygnus.desktop.dominio.Users"/>
	
</session-factory>
</hibernate-configuration>

o componente Tenant deveria ser @applicationScoped também pra isso funcionar.

neste tenant, vou colocar a logica para buscar qual usar, a principio…o usuario ira passar no login. pensei em deixar isto numa classe de user, sessionscope.

dai deixo esta classe como aplicationscope, e trato no gettenantname?

vc precisa do tenant pra sessionFactory?

Ou só pra session?

na verdade eu trocaria o ComponentFactory por um componente normal chamado TenantSessionFactories, @applicationScoped que sabe retornar uma SessionFactory dado um Tenant

daí a ComponentFactory usa essa classe pra abrir a sesssion, dado um tenant.

[quote=Lucas Cavalcanti]vc precisa do tenant pra sessionFactory?

Ou só pra session?

na verdade eu trocaria o ComponentFactory por um componente normal chamado TenantSessionFactories, @applicationScoped que sabe retornar uma SessionFactory dado um Tenant

daí a ComponentFactory usa essa classe pra abrir a sesssion, dado um tenant.[/quote]

Não entendi… complicou tudo agora.heheeh

[quote=Lucas Cavalcanti]vc precisa do tenant pra sessionFactory?

Ou só pra session?

na verdade eu trocaria o ComponentFactory por um componente normal chamado TenantSessionFactories, @applicationScoped que sabe retornar uma SessionFactory dado um Tenant

daí a ComponentFactory usa essa classe pra abrir a sesssion, dado um tenant.[/quote].

Lucas, fiz umas alterações… mais ainda tenho uma duvida na minha classe Tenant. Onde esta usersLoggedIn = new UsersLoggedIn(), queria ver o usuario que esta na sessão. mas se colocar a criação desta classe no construtor da erro dizendo que sessionscope não pode ser usado no momento.

@Component
@SessionScoped
public class UsersLoggedIn {

	private Users user;
	private String empresa;
	
	public String getEmpresa() {
		return empresa;
	}


	public void setEmpresa(String empresa) {
		this.empresa = empresa;
	}


	public boolean isLogged() {
		return this.user != null;
	}

	
	public void setUser(Users user) {
		this.user = user;
	}


	public Users getUser() {
		return user;
	}	
}
@ApplicationScoped
@Component
public class Tenant {

	private UsersLoggedIn usersLoggedIn;

	public String getTenantName() {
		usersLoggedIn = new UsersLoggedIn();
		
		if (this.usersLoggedIn.getUser() == null) {
			return new String("adm");
		} else {
			return this.usersLoggedIn.getEmpresa();
		}
	}
}
@RequestScoped
@Component
public class CriadorDeSession implements ComponentFactory<Session> {

		private Session session;
		private TenantSessionFactories tenantSessionFactories;
		
		public CriadorDeSession(TenantSessionFactories tenantSessionFactories) {
			this.tenantSessionFactories = tenantSessionFactories;
		}
		
		@PostConstruct
		public void abre() {
			this.session = this.tenantSessionFactories.abre();
		}
		
		public Session getInstance() {
			return this.session;
		}
		
		@PreDestroy
		public void fecha() {
			this.session.close();
		}
}
@ApplicationScoped
@Component
public class TenantSessionFactories {

	private Map<String, SessionFactory> conexoes = new HashMap<String, SessionFactory>();
	private Tenant tenant; 
	
	public TenantSessionFactories(Tenant tenant) {
		this.tenant = tenant;
	}
	
	public Session abre() {
		SessionFactory conexao = this.conexoes.get(tenant.getTenantName());	
		if (conexao == null) {
			Configuration configuration = new Configuration();

			configuration.configure("hibernate.cfg.xml");
			configuration.setProperty("hibernate.connection.url", "jdbc:postgresql://localhost:5432/"+this.tenant.getTenantName());
			conexao = configuration.buildSessionFactory();
			this.conexoes.put(this.tenant.getTenantName(), conexao);
		}
		
		return conexao.openSession();
	}
}

Não queria pegar o tenant por base na url, por exemplo: minhaempresa.app.com.br… pois um mesmo usuario podera acessar mais de 1 empresa. dai queria que ele escolhesse no login

  • Tire o @ApplicationScope do Tenant
  • tire o Tenant do construtor da TenantSessionFactories.
  • receba Tenant no método abre do TenantSessionFactories.
  • Receba o Tenant também no construtor do CriadorDeSession e o use para abrir a sessao.
  • receba o UsersLoggedIn no construtor do Tenant.