Multi Tenancy Vraptor 3.5

134 views
Skip to first unread message

Marcelo Venancio

unread,
May 19, 2015, 1:45:11 PM5/19/15
to caelum-...@googlegroups.com
Boa tarde,

Estou tentando implementar a seguinte solução em meu projeto que foi a única que encontrei sobre o assunto:
https://groups.google.com/forum/#!topic/caelum-vraptor/mgVtYp7mev0

Minha dúvida é a seguinte, como faço para gerar o tenant identifier que se pede na classe SchemaResolver? Além dessas duas classes eu devo também alterar o meu hibernate utils?

Eu gostaria que esse identifier fosse o usuário, eu posso setar esse usuário na sessão e recuperá-lo por injeção de dependência após o login, visto que terei uma tabela public com usuário, senha e schema? Qual seria o melhor jeito de se fazer isso?

Outra pergunta é se eu preciso dar um "grant" no usuário por schema em meu banco no getConnection() da classe MultiTenantProvider ou só o set schema já é suficiente?

Obs: Ainda estou aprendendo a trabalhar com Multi Tenancy e estou tentando implementá-lo em meu projeto do vraptor 3.5 com hibernate 4, desde já agradeço ...

Manoel Calixto

unread,
May 20, 2015, 7:49:33 AM5/20/15
to caelum-...@googlegroups.com
Bom dia Marcelo!

O hibernateUtils só se você quisesse usar sessionFactory.withOptions().tenantIdentifier(tenanty).openSession() para passar o tenanty em vez da SchemaResolver.

Eu tive dificuldades com isso, pois tinha pegar o usuário antes de efetuar o login para abrir a sessão e fazer o procedimento de logar, não era? assim tive que usar a forma acima já que pela pressa não foi possível correr muito atrás disso.

Quem precisa ter acesso aos schemas é o usuário configurado para se conectar no banco no teu hibernate.

--
Você recebeu essa mensagem porque está inscrito no grupo "caelum-vraptor" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para caelum-vrapto...@googlegroups.com.
Para postar nesse grupo, envie um e-mail para caelum-...@googlegroups.com.
Acesse esse grupo em http://groups.google.com/group/caelum-vraptor.
Para mais opções, acesse https://groups.google.com/d/optout.



--

Atenciosamente,

Manoel Calixto

Programador

Grupo 3corações
www.3coracoes.com.br

Marcelo Venancio

unread,
May 26, 2015, 8:31:43 AM5/26/15
to caelum-...@googlegroups.com
Bom dia Manoel,

Então, se eu adicionar o tenant identifier na sessão eu consigo, após o login do usuário, carregar o schema correspondente, porém eu tenho alguns processos que rodam antes do login e ainda não consegui achar uma forma legal de lidar com isso. Eu queria que fosse por usuário e não por domínio pq acho mais fácil. Obrigado pela resposta Manoel. Se mais alguém puder me indicar uma forma de fazer isso .... agradecido.

Marcelo Venancio

unread,
Nov 3, 2015, 1:59:04 PM11/3/15
to caelum-vraptor
Boa tarde!

Ainda estou tentando a implementação acima sem sucesso, segue meu hibernateUtils e o meu hibernateInterceptor que eu utilizo para controlar as transações e onde inicialmente eu seto o tenantId.

public class HibernateUtils {
   
private static SessionFactory sessionFactory = buildSessionFactory();
   
private static ThreadLocal<Session> sessions = new ThreadLocal<Session>();
   
private static ServiceRegistry serviceRegistry;
   
private static ThreadLocal<String> tenants = new ThreadLocal<String>();
   
   
private static SessionFactory buildSessionFactory() {
       
try {
           
Configuration cfg = new Configuration();
            cfg
.configure("hibernate.cfg.xml");
            serviceRegistry
= new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry();
            sessionFactory
= cfg.buildSessionFactory(serviceRegistry);
           
return sessionFactory;
       
}catch(Throwable ex) {
           
System.err.println("Initial SessionFactory creation failed.");
            ex
.printStackTrace();
           
throw new ExceptionInInitializerError(ex);
       
}
   
}
   
   
private static Session openSession() {
       
return sessionFactory.withOptions().tenantIdentifier(getTenantId()).openSession();
   
}
   
   
public static Session getSession() {
       
if(sessions.get() == null) {
            sessions
.set(openSession());
       
}
       
return sessions.get();
   
}
   
   
public static void closeSession() {
       
if(sessions.get() != null) {
            sessions
.get().close();
            sessions
.remove();
       
}
   
}
   
   
public static void setTenantId(String tenantId) {
        tenants
.set(tenantId);
   
}
   
   
public static String getTenantId() {
       
if(tenants.get() == null) {
           
throw new RuntimeException("TenantId não definido pelo Interceptor");
       
}
       
return tenants.get();
   
}
   
   
public static void removeTenantId() {
        tenants
.remove();
   
}
}

@Intercepts
public class HibernateInterceptor implements Interceptor {
   
private final Sessao sessao;

   
public HibernateInterceptor(Sessao sessao) {
       
this.sessao = sessao;
   
}

   
@Override
   
public boolean accepts(ResourceMethod method) {
       
return true;
   
}

   
@Override
   
public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) throws InterceptionException {
       
try {
           
HibernateUtils.setTenantId(sessao.getTenantId() != null ? sessao.getTenantId() : "public");
           
HibernateUtils.getSession().beginTransaction();
            stack
.next(method, instance);
           
HibernateUtils.getSession().getTransaction().commit();
       
}catch(RuntimeException err) {
           
HibernateUtils.getSession().getTransaction().rollback();
           
throw err;
       
}finally {
           
HibernateUtils.closeSession();
           
HibernateUtils.removeTenantId();
       
}
   
}
}

Grato se alguém puder me ajudar!!!


Em terça-feira, 19 de maio de 2015 14:45:11 UTC-3, Marcelo Venancio escreveu:

Marcelo Venancio

unread,
Nov 9, 2015, 12:49:38 PM11/9/15
to caelum-vraptor
Boa tarde,

Atualizei a versão do VRaptor para o 4 e passei a utilizar o plugin do vraptor-hibernate, ou seja, eliminei meu hibernate utils e passei a injetar a session do hibernate no construtor. Porém, mesmo assim, ainda não funcionou o multi tenancy. Será que essa implementação que eu citei (https://groups.google.com/forum/#!topic/caelum-vraptor/mgVtYp7mev0) funciona com esse plugin do vraptor-hibernate? Como que eu sei que a conexão do plugin é a mesma utilizada no exemplo citado da implementação, já que o mesmo utiliza c3p0?


Em terça-feira, 19 de maio de 2015 14:45:11 UTC-3, Marcelo Venancio escreveu:

Clairton Rodrigo Heinzen

unread,
Nov 10, 2015, 12:00:31 PM11/10/15
to caelum-vraptor
Marcelo, Boa Tarde.

Fiz um lib para me ajudar, esta em https://github.com/clairton/tenant ela integra com o https://github.com/clairton/repository usando CDI. Funciona quando tem todos os dados em uma mesma base de dados.

Para mim ficou bem funcional e testável. Em um dos casos, uso ele em uma aplicação para simular financiamentos para a Cooperativa de Crédito que trabalho, onde os usuários são lojistas e cada um pode ser acesso somente as propostas que ele emitiu.

Como usa CDI e JPA, fica independente de implementação(hibernate, eclipselink, batoojpa, etc)

Qualquer coisa estou a disposição.

Marcelo Venancio

unread,
Nov 11, 2015, 6:16:01 AM11/11/15
to caelum-vraptor
Oi Clairton bom dia,

Achei bem interessante a sua solução, mas pelo que vi rapidamente vc não faz a separação do tenant por schema correto? ou seja, todos os seus registros estão na mesma base e no mesmo schema, estou certo? Eu preciso fazer essa implementação por schema para ter uma melhor separação dos clientes e também para backups, porém, nunca implementei isso antes e estou tendo uma série de dificuldades. Atualmente estou usando o plugin do vraptor-hibernate, e ao que parece as conexões desse plugin e da implementação que citei acima estão diferentes, porém não estou conseguindo testar isso e resolver o problema. Muito obrigado pela ajuda, vlw ...

Ivo Sestren Junior

unread,
Nov 11, 2015, 6:47:24 AM11/11/15
to caelum-...@googlegroups.com
Eu fiz a implementação de multi tenant por base de dados.
Utilizei o vraptor-hibernate, não como biblioteca, peguei as classes e coloquei no meu projeto e dividi ele em duas partes.
Sendo uma MASTER com uma base de dados comum a todos.
E outra sendo CLIENT com a base especifica de cada um dos clientes.

Só criei os qualificadores do CDI para poder pegar a Session do MASTER ou do CLIENT conforme necessitava.

E na parte do hibernate CLIENT, ainda criei um List para guardar as Factorys conforme cada um dos clientes.

Não sei se foi a melhor maneira de implementar isto, mas esta funcionando e muito bem.

Em 11 de novembro de 2015 09:16, Marcelo Venancio <marcelove...@gmail.com> escreveu:
Oi Clairton bom dia,

Achei bem interessante a sua solução, mas pelo que vi rapidamente vc não faz a separação do tenant por schema correto? ou seja, todos os seus registros estão na mesma base e no mesmo schema, estou certo? Eu preciso fazer essa implementação por schema para ter uma melhor separação dos clientes e também para backups, porém, nunca implementei isso antes e estou tendo uma série de dificuldades. Atualmente estou usando o plugin do vraptor-hibernate, e ao que parece as conexões desse plugin e da implementação que citei acima estão diferentes, porém não estou conseguindo testar isso e resolver o problema. Muito obrigado pela ajuda, vlw ...

--

Clairton

unread,
Nov 11, 2015, 7:11:21 AM11/11/15
to caelum-...@googlegroups.com
Bom Dia Marcelo, exatamente, os dados estão todos em uma mesma base de dados e esquema.


Atenciosamente
Clairton Rodrigo Heinzen

Marcelo Venancio

unread,
Nov 18, 2015, 5:07:59 AM11/18/15
to caelum-vraptor
Bom dia,

Consegui implementar o Multy Tenancy por banco de dados, removendo a implementação citada acima e sobrescrevendo a classe SessionCreator do plugin do vraptor-hibernate. O detalhe é que se eu sobrescrever a classe SessionFactoryCreator do plugin, a implementação também funciona e minha dúvida é qual das duas eu devo sobrescrever, ou se não tem diferença entre as duas. Eu optei por sobrescrever a SessionCreator porque o método getInstance() dessa classe é RequestScoped e eu preciso trocar de SessionFactory no login porque o email do usuário e o tenant (nome do banco) que vou conectar estão em um banco separado, acessível a todos os usuários. Não sei se esse é o melhor jeito de se fazer, mas funcionou. Como é a primeira vez que estou implementando esse recurso, creio que com o tempo eu vou dando uma ajeitada. Se alguém tiver alguma sugestão para melhorar ....

@ApplicationScoped
public class TenantSessionCreator {
   
private Map<String, SessionFactory> connections = new HashMap<String, SessionFactory>();
   
private ServiceRegistry serviceRegistry;

   
public Session create(Tenant tenant) {
       
try {
           
SessionFactory sessionFactory = this.connections.get(tenant.getTenantName());
           
if(sessionFactory == null) {

               
Configuration cfg = new Configuration();
                cfg
.configure("hibernate.cfg.xml");

                cfg
.setProperty("hibernate.connection.url", "jdbc:postgresql://localhost:5432/" + tenant.getTenantName());
               
                serviceRegistry
= new StandardServiceRegistryBuilder().applySettings(cfg.getProperties()).build();
                sessionFactory
= cfg.buildSessionFactory(serviceRegistry);
               
this.connections.put(tenant.getTenantName(), sessionFactory);
           
}

           
return sessionFactory.openSession();
       
}catch(Exception err) {

           
System.err.println("Initial SessionFactory creation failed.");

            err
.printStackTrace();
           
throw new ExceptionInInitializerError(err);
       
}
   
}
}


public class Tenant {
   
private Sessao sessao;

   
@Inject
   
public Tenant(Sessao sessao) {
       
this.sessao = sessao;
   
}
   
   
public String getTenantName() {
       
if(this.sessao.getTenantName() == null) {
           
return new String("sisec_connection");
       
}else {
           
return this.sessao.getTenantName();
       
}
   
}
}


@Specializes
public class CustomSessionCreator extends SessionCreator {
   
private static final Logger LOGGER = LoggerFactory.getLogger(CustomSessionCreator.class);
   
private TenantSessionCreator tenantSessionCreator;
   
private Tenant tenant;

   
/**
     * CDI eyes only
     * @deprecated
     */

   
public CustomSessionCreator() {}

   
@Inject
   
public CustomSessionCreator(SessionFactory factory, TenantSessionCreator tenantSessionCreator, Tenant tenant) {
       
super(factory);
       
this.tenantSessionCreator = tenantSessionCreator;
       
this.tenant = tenant;
   
}

   
@Produces
   
@RequestScoped
   
public Session getInstance() {
       
Session session = this.tenantSessionCreator.create(this.tenant);
        LOGGER
.debug("opening a session {}", session);
       
return session;
   
}
   
   
public void destroy(@Disposes Session session) {
        LOGGER
.debug("closing session {}", session);
        session
.close();
   
}
}


Obrigado ...

Marcelo Venancio

unread,
Nov 18, 2015, 7:41:24 AM11/18/15
to caelum-vraptor
Percebi que após repetir alguns logins (logar e deslogar várias vezes) a aplicação trava. Talvez haja alguma falha na criação da session dessa implementação, não pode ser RequestScoped será?

Rafael Viana

unread,
Nov 18, 2015, 8:00:14 AM11/18/15
to caelum-vraptor
Fiz uma alteração no plugin vraptor-hibernate para adicionar suporte multi-tenancy... Falta terminar pequenos detalhes... vou terminar agora meio-dia e subo o pull-request, pode ser que lhe ajude, ok?

Marcelo Venancio

unread,
Nov 18, 2015, 10:19:36 AM11/18/15
to caelum-vraptor
Ok Rafael, eu agradeço muito, estou quebrando a cabeça com isso já faz tempo. Vou aguardar, muito obrigado ...

Rafael Viana

unread,
Nov 18, 2015, 1:24:15 PM11/18/15
to caelum-vraptor
Marcelo,

Ainda não consegui colocar diretamente no plugin (estou com probleminha no @Specializes do CDI)

Mas dá uma olhada no último commit que fiz no repositório.
https://github.com/RafaelRViana/vraptor-hibernate

Copiando as classes no projeto, está funcionando.
Pego o tenant id da URL, ex: teste.nomesistema.com.br
Tinha tentando colocar na sessão, mas antes do login não tenho...e o session é criado no inicio da requisção... então, foi a maneira que achei de resolver este problema, e, fica "elegante".

Vou tentar liberar isto no plugin até o fim da semana.... se não conseguir, faço um post explicando como inserir as classes no projeto.

Marcelo Venancio

unread,
Nov 18, 2015, 1:32:08 PM11/18/15
to caelum-vraptor
Certo Rafael, vou tentar implementar aqui e te dou um retorno assim que conseguir ... no meu caso, eu tenho um banco somente com uma tabela (email, tenantId) onde a partir do email do usuário eu recupero o nome do banco ao qual eu vou conectar (tenantId), então eu crio a nova session e troco de banco. Porém achei a sua solução interessante e mais elegante mesmo, vou tentar implementar .... muito obrigado.

Rafael Viana

unread,
Nov 19, 2015, 7:08:41 AM11/19/15
to caelum-vraptor
Você está criando um SessionFactory para cada banco? Isso vai te consumir bastante memória... Mantém um SessionFactory só e crio ConnectionProviders para os bancos.

Marcelo Venancio

unread,
Nov 19, 2015, 10:42:11 AM11/19/15
to caelum-vraptor
Então Rafael, como eu faria para criar apenas um sessionFactory e usá-lo em todos os bancos? É possível fazer isso? Porque conforme meu código abaixo eu crio a factory a medida que o tenantId é alterado e seto num mapa, ou seja, a medida que o cliente muda, visto que para cada cliente eu possuo um banco (modelo SAAS) ... Esse código funciona bem, mas ele trava a medida que vou usando, deve ser memória mesmo ...



@ApplicationScoped
public class TenantSessionCreator {
   
private Map<String, SessionFactory> connections = new HashMap<String, SessionFactory>();
   
private ServiceRegistry serviceRegistry;

   
public Session create(Tenant tenant) {
       
try {
           
SessionFactory sessionFactory = this.connections.get(tenant.getTenantName());
           
if(sessionFactory == null) {

               
Configuration cfg = new Configuration();
                cfg
.configure("hibernate.cfg.xml");

                cfg
.setProperty("hibernate.connection.url", "jdbc:postgresql://localhost:5432/" + tenant.getTenantName());
               
                serviceRegistry
= new StandardServiceRegistryBuilder().applySettings(cfg.getProperties()).build();
                sessionFactory
= cfg.buildSessionFactory(serviceRegistry);
               
this.connections.put(tenant.getTenantName(), sessionFactory);
           
}

           
return sessionFactory.openSession();
       
}catch(Exception err) {

           
System.err.println("Initial SessionFactory creation failed.");

            err
.printStackTrace();
           
throw new ExceptionInInitializerError(err);
       
}
   
}
}

(OBS: ainda estou tentando implementar a solução que vc me sugeriu)

Rafael Viana

unread,
Nov 20, 2015, 8:41:40 AM11/20/15
to caelum-vraptor
Eu fazia um SessionFactory por tenant antes do Hibernate 4 suportar isto. E, frequentemente tinha que fazer upgrade de memória no servidor porque o SessionFactory é um objeto bem pesado.

Você tem que trabalhar com o MultiTenantConnectionProvider

Aqui tem a documentação oficial do Hibernate:
https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html

Nos meus commits você pode ver a implementação em: MultiTenantConnectionProviderDefault.java

Criado este objeto, você vai registrar ele no ServiceRegistry: (Exemplo na classe: ServiceRegistryCreator.java)
serviceRegistryBuilder.addService(MultiTenantConnectionProvider.class, getMultiTenantConnectionProvider());

protected MultiTenantConnectionProvider getMultiTenantConnectionProvider() {
return new MultiTenantConnectionProviderDefault(cfg, multiTenancyConfiguration) {
private static final long serialVersionUID = -3482116169215468727L;
@Override
protected ConnectionProvider buildConnectionProvider() {
C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
connectionProvider.injectServices((ServiceRegistryImplementor) getInstance());
connectionProvider.configure(cfg.getProperties());
return connectionProvider;
}
};
}

Já que não estou conseguindo inserir isso "default" no plugin vraptor-hibernate, vou tentar criar amanhã um post explicando como inserir estas classes no projeto :)

Abraços!

Marcelo Venancio

unread,
Nov 23, 2015, 7:47:43 AM11/23/15
to caelum-vraptor
blza Rafael, to quebrando a cabeça aqui tb ... Obrigado!
Reply all
Reply to author
Forward
0 new messages