É possível mudar em tempo de execução os dados de conexão na JPA?

8 respostas
L

Bom dia galera,

alguém já conseguiu fazer, ou sabe se é possível, mudar em tempo de execução os dados da conexão com o banco de dados usando a JPA?
Obtenho a conexão através de de um recurso do servidor (a aplicação está publicada no glassfish). Esta conexão tem um usuário padrão, descrito no sun-resources.xml.
O caso é que, para cada usuário do sistema, existe um usuário paralelo no banco de dados, e após o processo de login o usuário de banco de dados referente ao usuário do sistema passa a ser usado para obter as conexões.
Isso acontece pois as permissões são controladas via triggers, baseadas no usuário da conexão.
Para quem se perguntou o motivo de estar sendo feito assim, explico: é uma migração… e precisamos manter esse esquema.

Abraços!!

8 Respostas

J

tem como sim

/*1. Coloca o "path de conexão", ou URL, em um arquivo properties. Por exemplo:
view plaincopy to clipboardprint?*/

  url=jdbc:oracle:thin:@servidor:1521:instancia  

  url=jdbc:oracle:thin:@servidor:1521:instancia

/*2. Recupera essa informação usando o ResourceBundle. Se você colocar o arquivo na raiz do projeto/JAR, fica mais ou menos assim:
view plaincopy to clipboardprint?*/

    // Recuperando o arquivo de propriedades, nomeado DBConfig  
 ResourceBundle bundle = ResourceBundle.getBundle("DBConfig", locale);  
 // Recuperando a propriedade associada a chave url  
 String urlBanco = bundle.getString("url");  

// Recuperando o arquivo de propriedades, nomeado DBConfig ResourceBundle bundle = ResourceBundle.getBundle("DBConfig", locale); // Recuperando a propriedade associada a chave url String urlBanco = bundle.getString("url");


/*3. Redefinir a propriedade no EntityManagerFactory:
view plaincopy to clipboardprint?*/

 /* Aqui vamos sobrescrever o que foi definido no persistence.xml 
    * Voce também poderá colocar por aqui o tratamento para saber se o arquivo DBConfig está presente ou preenchido corretamente, 
    * e só sobrescrever se ele existir 
    */  
   Map configOverrides = new HashMap();  
   configOverrides.put("hibernate.connection.url", urlBanco);  
   EntityManagerFactory programmaticEmf = Persistence.createEntityManagerFactory("persistenceUnit", configOverrides);  

/* Aqui vamos sobrescrever o que foi definido no persistence.xml * Voce também poderá colocar por aqui o tratamento para saber se o arquivo DBConfig está presente ou preenchido corretamente, * e só sobrescrever se ele existir  Map*/ configOverrides = new HashMap(); configOverrides.put("hibernate.connection.url", urlBanco); EntityManagerFactory programmaticEmf = Persistence.createEntityManagerFactory("persistenceUnit", configOverrides);


/*Aparentemente isso poderia resolver o problema para você.


Caso esteja usando o Toplink, ou não queira fixar quem é o Provedor de Persistência , poderia ler o nome da propriedade, a ser mudada no persistence.xml, do arquivo de propriedades:
view plaincopy to clipboardprint?*/

    url=jdbc:oracle:thin:@servidor:1521:instancia  
    urlPropertyName=hibernate.connection.url  

     url=jdbc:oracle:thin:@servidor:1521:instancia urlPropertyName=hibernate.connection.url
/*
E usar assim:
view plaincopy to clipboardprint?
*/
    // Recuperando a propriedade associada a chave url  
    String urlBanco = bundle.getString("url");  
    // Recuperando o nome da propriedade a ser redefinida  
    String propUrlBanco = bundle.getString("urlPropertyName");  
     
    Map configOverrides = new HashMap();  
    configOverrides.put(propUrlBanco, urlBanco);  

// Recuperando a propriedade associada a chave url 
String urlBanco = bundle.getString("url"); // Recuperando o nome da propriedade a ser redefinida 
String propUrlBanco = bundle.getString("urlPropertyName"); Map configOverrides = new HashMap(); 
configOverrides.put(propUrlBanco, urlBanco);
L

Opa… fiz os testes aqui e deu certo.
A questão agora é que tenho que abrir mão da injeção automática de EntityManagers, certo?
O problema que isso me causa é na propagação de transação.
Tenho a seguinte situação (vou ignorar os detalhes irrelevantes no exemplo que vou apresentar, pra passar só a idéia principal):

@Stateful
MeuStatefulBean{

   @EJB
   MeuStatelessBeanX beanX;
   @EJB
   MeuStatelessBeanY beanY;

   metodoA(){
      transacaoBegin();
      ...
      beanX.metodoX1();
      beanY.metodoY2();
      ...
      transacaoCommit();
   }

   metodoB(){
      transacaoBegin();
      ...
      beanX.metodoX2();
      beanY.metodoY1();
      ...
      transacaoCommit();
   }

}


@Stateless
MeuStatelessBeanX{

   metodoX1(){
      ...
      executaOperacaoTransacional();
      ...
   }

   metodoX2(){
      ...
      executaOperacaoTransacional();
      ...
   }

}


@Stateful
MeuStatelessBeanY{

   metodoY1(){
      ...
      executaOperacaoTransacional();
      ...
   }

   metodoY2(){
      ...
      executaOperacaoTransacional();
      ...
   }

}

no caso, transacaoBegin() seria um entityManager.getTransaction().begin().
preciso estender esta transação para os demais beans sem estado… e pelas anotações creio que não é possível.

Alguma sugestão galera?

E

Senhores ressucitando o tópico, com a finalidade de duplicidades.

Luiz_Gustavo na época como você resolveu este problema com a possivel solucação apresentada? Estou com a mesma situação, preciso realizar conexões dinâmicas atreladas a um datasource, sem ter que gerênciar as trasações manualmente, quero que isso continue sendo feito pelo container. Sabem me dizer se JPA 2 tem algo nativo que disponibiliza tal ação?

Desde já obrigado.

L

ederfreitas,

neste projeto em que trabalhei, em um determinado momento, deixamos de usar EJB, e passamos a usar somente JPA, controlando as transações manualmente.

Para a questão de mudar dinamicamente os dados de conexão havíamos adotado a estratégia de criar instâncias de EntityManagerFactory com as configurações necessárias, mas isto nos levou a um grande problema de performance no final.

Quando se trata de EntityManagerFactory o ideal é que se tenha apenas uma instância no sistema, pois cada instância costuma ser muito cara em termos de recursos, ocupando muito espaço em memória (isso varia de acordo com o tamanho do banco).

Como criávamos um EntityMaganerFactory para cada usuário do sistema (a cada vez que um usuário realizava o login criávamos a instância personalizada e mantínhamos em um cache) em pouco tempo a memória estava cheia, com os objetos de metadados do banco.

Tentamos personalizar os dados de conexão passando propriedades no momento da criação das instâncias de EntityManager, mas não tivemos sucesso.

Um consultor que tentou nos ajudar, na ocasião, disse que havia uma forma de configurar o JBoss para que o mesmo realizasse a autenticação dos usuários, e com as mesmas credenciais fornecer as conexões ao EntityManager, de acordo com o usuário logado. Bom, a consultoria nunca conseguiu nos mostrar isso, e eu também não consegui encontrar este recurso. (A autenticação dos usuários é beleza, mas me refiro à passagem de conexões personalizadas ao EM).

Na configuração de datasource do JBoss há uma opção que permite especificar para a fábrica de conexões qual usuário e senha utilizar para recuperar conexões, mas não testei ainda para ver como isso poderia ser utilizado com o JPA:

application-managed-security: Se true, o login e senha para conexão serão fornecidos pelo código Java que requisita a conexão ao servidor de aplicações, em vez de pela definição do datasource;

:roll:

E

Entendo, mas a idéia permanece a de criar apenas um EntityManagerFactory, a única coisa que vai mudar mesmo é a database, pense assim, tenho um banco de dados que conterá databases diversificadas para cada cliente, cliente x acessa a database x, cliente y acessa a database ya única coisa que preciso é espeficar isso em runtime uma vez que não posso manter isso constante em um persistence.xml.

L

Entendi =)

Neste caso me parece mais fácil.

Supondo que você está usando um datasource gerenciado pelo seu servidor de aplicações para obter as conexões (que é o recomendado), você terá que criar vários datasources no seu servidor, um para cada banco, dando a cada um o seu devido nome JNDI.

No seu arquivo persistence.xml você pode criar várias unidades de persistência, cada uma com um nome e referenciando um datasource diferente.

Depois você pode criar as instâncias de EntityManagerFactory, uma para cada unidade de persistência, armazenar estas instâncias em um cache, e recuperar cada uma no momento com base nos dados de login.

E

Aí que está o problema não é tão fácil assim, “revirei” o Goole em busca de informações. Posso ter uma nova database a cada dia e não poderia ficar recompilando a aplicação toda vez que eu precisasse de um novo datasource, tinha que ser genérico, algo que o JPA não colaborou com nós programadores nos deixando presos a um banco de dados.

Tudo que eu preciso resume-se nisso https://community.jboss.org/message/356284, porém é bem antigo e não há nenhuma solução apresentada.

E

´

Criado 13 de dezembro de 2007
Ultima resposta 8 de abr. de 2011
Respostas 8
Participantes 3