Hibernate + Esquemas

8 respostas
Rafael.bnc

Estou pensando em trabalhar com um banco de dados (Postgresql) em um
sistema da seguinte forma:

No esquema public (default do postgresql) eu tenho só uma tabela de
usuários com os campos login, senha e esquema.

Quando o usuário efetuar login o sistema verifica o esquema e faz o
hibernate trabalhar em cima do esquema desse usuário.

Todos os esquemas tem as mesmas tabelas exemplo:
Schema: User01; Tabelas: usuario, endereço, dependentes, produtos…
Schema: User02; Tabelas: usuario, endereço, dependentes, produtos…

O sistema é online e pode ter mais de um usuário logado ao mesmo
tempo.

Alguém já trabalho dessa forma e sabe como fazer a troca dinâmica de
esquemas com o Hibernate?

É realmente vantajoso trabalhar desta forma, tendo em vista que, nesse
sistema, os dados de cada usuário só são pertinentes a ele mesmo?


Rafael de Paula Souza

Sublimus - Desenvolvimento de Software e Soluções Web.
www.sublimus.com.br

8 Respostas

GraveDigger

Boa Noite,

Cara, sem querer te desanimar, mas vc está em apuros :lol:

Atualmente estou trabalhando numa arquitetura que é próxima a essa(na verdade, ainda pior) mas tem essa característica de 1 schema por cliente.

Vc não vai “trocar” de esquema de um usuário para o outro, vc precisa ter um SessionFactory distinto POR schema.

Se vc tiver 100 clientes, terá que ter 100 sessionFactories.

Essa arquitetura vem ganhando certo espaço mas infelizmente não há nada que te ajude a lidar com isso atualmente.

Consegui desenvolver uma solução bem legal mas ela usa o framework Seam.

Se vc estiver usando ele tb, posso te ajudar(acredito que com outro framework, como o Spring, o tipo de configuração que eu fiz não funcionaria, mas conheço muito pouco do Spring para afirmar isso com certeza)

Se vc estiver ainda na hora da modelagem, prefira o modelo convencional, caso contrário, terá que fazer o que eu falei.

Não se aventure em tentar mudar o schema de uma determinada Session(ou mesmo sessionFactory) em tempo de execução, será um grande desperdício de tempo.

Abs,

Pedro Sena

Rafael.bnc

Obrigado GraveDigger,

A minha sorte é que estou utilizando Seam e vou te pedir para me enviar a tua solução.
Mas é uma pena que o Hibernate não tenha um suporte fácil a essa arquitetura, você já deu uma olhada se alguém já submeteu esse feature para o pessoal do Hibernate?


Rafael de Paula Souza

Sublimus - Desenvolvimento de Software e Soluções Web.
www.sublimus.com.br

GraveDigger

Oi,

Que garoto de sorte vc por estar usando o Seam :lol:

Bom, existe um projeto do hibernate chamado Shards, mas ainda está muito cru, inviável para um ambiente de produção.

Ele visa trabalhar com arquiteturas como a nossa e oferece algumas coisas mais complexas(que eu precisei, mas vc não vai) como um “join cross-schema”

Bom, eu ia bloggar sobre essa solução se eu tivesse saco e tempo pra manter um blog, como não é o caso, vou tentar ser sucinto aqui indicando os passos que fiz, caso vc encontre dificuldade em algum , vou te ajudando com código na medida que eu puder, pq a solução completa é grande e não posso expor os fontes por completo.

  1. Crie um hibernate.cfg.xml e coloque suas propriedades específicas de banco lá(Cuidado para não colocar um name na parte session-factory se não ele vai atribuir ao JNDI). Não coloque atributos específicos de algum esquema, como connection.url, user_name ou password(mto menos dataSource)

  2. Crie uma classe que possa montar sua SessionFactory a partir desse arquivo, o típico HibernateUtil, mas coloque ele como um componente Seam, deixe-o Stateless. Lembre-se, o método deve retornar a SessionFactory, e não armazená-la em alguma variável. Esse método vai receber como parâmetro um Usuario de seu sistema, procure deixar na tabela do seu usuário o nome do schema ao qual ele vai se conectar, importante essa info lá.
    Aqui vc vai informar, programaticamente, as configurações que vc não passou no passo 1, para que seja tudo relativo ao usuário que entrou como parâmetro do método.

  3. Por motivos de performance, é melhor vc iniciar todas suas SessionFactories em tempo de deploy, para isso vamos criar um SessionFactoryPreloader. Essa classe vai ler do banco ‘global’(aquele que não é específico de cada usuário) todos os usuário cadastrados para que vc possa montar suas respectivas SessionFactories, mais abaixo mostro como vc vai conectar nesse banco. Ainda nessa classe, sempre que vc criar uma SessionFactory(vc vai fazer um loop para cada cliente seu) vc vai jogar ela em escopo de aplicação usando como alias aquele atributo que especifiquei no item 2, eu estou usando o nome do schema do usuario em questão, que é um atributo que está na minha classe Usuário.

for ( Customer customer : allCustomers ) { SessionFactory sessionFactory = this.hibernateUtil.createSessionFactory(customer); Contexts.getApplicationContext().set(customer.getMysqlDatabase(), sessionFactory); }

Edit: Para que sua classe suba em tempo de deploy, não se esqueça de anotá-la com @Startup, essa classe deve ser ScopeType.APPLICATION tb

  1. Vamos agora criar uma classe que possa nos devolver a sessionFactory correta dado um usuario, isso será usado no item seguinte.
    Algo bem simples como:
@In(value="#{customer}")
	private customer customer;
	
	@Factory(scope=ScopeType.SESSION, value="correctSessionFactory", autoCreate = true)
	public SessionFactory getCorrectSessionFactory() {
		SessionFactory sessionFactory = 
			(SessionFactory) Contexts.getApplicationContext().get(customer.getMysqlDatabase());
		
		if ( sessionFactory == null )
			throw new InfraRuntimeException("Erro ao obter SessionFactory para " + customer.getMysqlDatabase());
		
		return sessionFactory;
	}

Como você pode ver, devemos ter em algum escopo(de preferencia session) a variável customer, que é o usuário dono do schema ao qual queremos conectar

  1. Agora vem a mágica do seam, aqui que você vê de fato a diferença dele :slight_smile:

No seu components.xml, coloque o seguinte:

<persistence:managed-hibernate-session name="hibernateUserSession" auto-create="false"
				 session-factory="#{correctSessionFactory}" />

Como você pode ver, estamos usando #{correctSessionFactory} como a sessionFactory correta, ai que está o truque, ele só vai dar um evaluate nessa expressão quando vc solicitar a hibernateUserSession, ou seja, em tempo de execução, e quando isso acontecer, ela terá um valor, desde que você tenha um usuário em sessão.

Basicamente a idéia é essa, o acesso ao seu banco ‘global’ pode ser configurado da forma convencional, a partir dele vc obtem o usuário correto e joga-o no escopo Session do Seam, daí pra frente, caso você tenha feito todos esses passos que te disse, sempre que vc precisar da sessão do usuário você pode injetá-la via #{hibernateUserSession} que o Seam se encarrega de jogar lá justamente a session que você precisa.

Qualquer dúvida posta ai que eu procuro ajudar,

Abraço,

Pedro Sena

J

Olá,

Ressuscitando o post…

Tenho o mesmo problema, cada cliente possui um schema.
Só que não estou usando Seam, mas sim Spring + JPA + JSF.

Alguém sabe como posso fazer algo semelhante?

Obrigado.

GraveDigger

Oi Amigo,

Você testou o que postei no meu comentário anterior?

Att

J

Olá Pedro.

Mas não estou usando Seam… estou usando Spring.

Nesse post dá pra ver como está minha configuração.

http://www.guj.com.br/java/246469-spring-jpa-e-varios-schemas-#1277521

Aqui vc disse:

Então seria mais indicado criar vários EntityManagers? Isso não tornaria a aplicação mais pesada?

Obrigado!

GraveDigger

Olá,

Mesmo com Spring a idéia é a mesma.

Quando fiz o meu post estávamos usando Seam agora já migramos para o Spring também, fizemos um refactoring mas a idéia em si é a mesma, só tivermos que adequar as especificidades do Spring.

Infelizmente não posso disponibilizar o código por pertencer a empresa, mas o caminho que seguimos é justamente o que postei anteriormente.

Att

J

Ok, obrigado Pedro.

Vou tentar adaptar ao Spring, qq dúvida eu posto aqui :smiley:

Criado 30 de maio de 2009
Ultima resposta 7 de jul. de 2011
Respostas 8
Participantes 3