2015-04-10 10 views
6

Ho creato una semplice applicazione java SOAP (lato server) e sto usando Glassfish4,JPA/EclipseLink,EJB. Ho impostato le connessioni db (risorse/pool) in Glassfish. Si prega di suggerire alcuni modelli/conoscenze progettuali per utilizzare più database da una singola applicazione. La creazione di unità di persistenza multiple è una buona idea per l'accesso multiplo? O c'è qualche altra soluzione ottimizzata? Ho una classe generica di accesso al database.Accesso a più database da un'applicazione Web Java utilizzando JPA/EclipseLink/EJB

public class GenericDAO<T> { 

/* 
* private static final EntityManagerFactory emf = 
* Persistence.createEntityManagerFactory("icanPU"); private EntityManager 
* em; 
*/ 
/* 
* Persistence context is injected with following @PersistenceContext 
* annotation. This uses all persistence configurations as specified in the 
* persistence.xml. 
* 
* Note this kind of injection can only be done for JTA data sources. 
*/ 
@PersistenceContext(unitName = "SavingBalanceDemoServer_PU") 
private EntityManager em; 
private Class<T> entityClass; 

public EntityManager getEntityManager() { 
return this.em; 
} 

public void joinTransaction() { 
/* em = emf.createEntityManager(); */ 
em.joinTransaction(); 
} 

public GenericDAO(Class<T> entityClass) { 
this.entityClass = entityClass; 
} 

public void save(T entity) { 
em.persist(entity); 
} 

// Added by Sudeep for bulk Insert of List object. 
public void saveList(List<T> objList) { 
for (Iterator<T> iterator = objList.iterator(); iterator.hasNext();) { 
T t = (T) iterator.next(); 
em.persist(t); 
} 
} 

public void delete(Object id, Class<T> classe) { 
T entityToBeRemoved = em.getReference(classe, id); 

em.remove(entityToBeRemoved); 
} 

public T update(T entity) { 
return em.merge(entity); 
} 

public int truncateUsingNative(String tableName) { 
Query qry = em.createNativeQuery("TRUNCATE TABLE " + tableName); 

return qry.executeUpdate(); 
} 

// Added by Sudeep for bulk Update of List object. 
public void updateList(List<T> entity) { 
for (Iterator<T> iterator = entity.iterator(); iterator.hasNext();) { 
T t = (T) iterator.next(); 
em.merge(t); 
} 
} 

public T find(int entityID) { 
// em.getEntityManagerFactory().getCache().evict(entityClass, entityID); 
return em.find(entityClass, entityID); 
} 

public T find(long entityID) { 
// em.getEntityManagerFactory().getCache().evict(entityClass, entityID); 
return em.find(entityClass, entityID); 
} 

public T find(Object compositePkObject) { 
// em.getEntityManagerFactory().getCache().evict(entityClass, entityID); 
return em.find(entityClass, compositePkObject); 
} 

public T findReferenceOnly(int entityID) { 
return em.getReference(entityClass, entityID); 
} 

// Using the unchecked because JPA does not have a 
// em.getCriteriaBuilder().createQuery()<T> method 
@SuppressWarnings({ "unchecked", "rawtypes" }) 
public List<T> findAll() { 
CriteriaQuery cq = null; 
if (isDbAccessible()) { 
try { 
cq = em.getCriteriaBuilder().createQuery(); 
cq.select(cq.from(entityClass)); 
return em.createQuery(cq).getResultList(); 
} catch (org.eclipse.persistence.exceptions.DatabaseException ex) { 
System.out.println("The zzz error is :" + ex.toString()); 
/*JSFMessageUtil jsfMessageUtil = new JSFMessageUtil(); 
jsfMessageUtil 
.sendErrorMessageToUser("Database Server is unavailable or not accessible! Please, contact your system admin!");*/ 
return null; 
} 
} 
return null; 
} 

private boolean isDbAccessible() { 
return em.isOpen(); 
} 

@SuppressWarnings("unchecked") 
public List<T> findAllWithGivenCondition(String namedQuery, 
Map<String, Object> parameters) { 
List<T> result = null; 
Query query = em.createNamedQuery(namedQuery); 

if (parameters != null && !parameters.isEmpty()) { 
populateQueryParameters(query, parameters); 
} 

result = (List<T>) query.getResultList(); 

return result; 
} 

@SuppressWarnings("unchecked") 
public List<T> findAllWithGivenConditionLazyLoading(String namedQuery, 
Map<String, Object> parameters,int startingAt, int maxPerPage) { 
List<T> result = null; 
Query query = em.createNamedQuery(namedQuery); 

if (parameters != null && !parameters.isEmpty()) { 
populateQueryParameters(query, parameters); 
} 
query.setFirstResult(startingAt); 
query.setMaxResults(maxPerPage); 

result = (List<T>) query.getResultList(); 

return result; 

} 

@SuppressWarnings("unchecked") 
public List<T> findAllWithGivenConditionJpql(String jpql, 
Map<String, Object> parameters) { 
List<T> result = null; 
Query query = em.createQuery(jpql); 

if (parameters != null && !parameters.isEmpty()) { 
populateQueryParameters(query, parameters); 
} 

result = (List<T>) query.getResultList(); 

return result; 
} 

@SuppressWarnings("unchecked") 
public T findOneWithGivenConditionJpql(String jpql, 
Map<String, Object> parameters) { 
Query query = em.createQuery(jpql); 

if (parameters != null && !parameters.isEmpty()) { 
populateQueryParameters(query, parameters); 
} 
return (T) query.getSingleResult(); 
} 

// Using the unchecked because JPA does not have a 
// query.getSingleResult()<T> method 
@SuppressWarnings("unchecked") 
protected T findOneResult(String namedQuery, Map<String, Object> parameters) { 
T result = null; 

try { 
if (!em.isOpen()) { 
/*JSFMessageUtil jsfMessageUtil = new JSFMessageUtil(); 
jsfMessageUtil 
.sendErrorMessageToUser("Database Server is unavailable or not accessible! Please, contact your system admin!");*/ 
} else { 
Query query = em.createNamedQuery(namedQuery); 

// Method that will populate parameters if they are passed not 
// null and empty 
if (parameters != null && !parameters.isEmpty()) { 
populateQueryParameters(query, parameters); 
} 

result = (T) query.getSingleResult(); 
} 

} catch (NoResultException e) { 
// JSFMessageUtil jsfMessageUtil = new JSFMessageUtil(); 
// jsfMessageUtil.sendErrorMessageToUser("No Information Found...!"); 

// e.printStackTrace(); 
return null; 
} catch (org.eclipse.persistence.exceptions.DatabaseException e) { 
/*JSFMessageUtil jsfMessageUtil = new JSFMessageUtil(); 
jsfMessageUtil 
.sendErrorMessageToUser("Database Server is unavailable or not accessible!");*/ 
e.printStackTrace(); 
} 

return result; 
} 

private void populateQueryParameters(Query query, 
Map<String, Object> parameters) { 
for (Entry<String, Object> entry : parameters.entrySet()) { 
query.setParameter(entry.getKey(), entry.getValue()); 
} 
} 

/** 
* @param startingAt 
* @param maxPerPage 
* @param t 
* @return list of persisted entities which belong to this class t 
*/ 
@SuppressWarnings("unchecked") 
public List<T> getAllLazyEntities(int startingAt, int maxPerPage, Class<T> t) { 
// regular query that will search for players in the db 
Query query = getEntityManager().createQuery(
"select p from " + t.getName() + " p"); 
query.setFirstResult(startingAt); 
query.setMaxResults(maxPerPage); 

return query.getResultList(); 
} 

/** 
* @param clazz 
* @return count of existing entity rows from backend 
*/ 
public int countTotalRows(Class<T> clazz) { 
Query query = getEntityManager().createQuery(
"select COUNT(p) from " + clazz.getName() + " p"); 

Number result = (Number) query.getSingleResult(); 

return result.intValue(); 
} 

/** 
* @return count of existing entity rows from backend acccording to given 
*   condition 
*/ 
public int countTotalRowsWithCond(Class<T> clazz, String Cond) { 
Query query = getEntityManager() 
.createQuery(
"select COUNT(p) from " + clazz.getName() + " p " 
     + Cond + " "); 

Number result = (Number) query.getSingleResult(); 

return result.intValue(); 
} 
} 

è dinamicamente modificando unitName in @PersistenceContext(unitName = "SavingBalanceDemoServer_PU") una buona idea? Per favore suggeriscimi

mio persistence.xml è:

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.1" 
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> 
<persistence-unit name="SavingBalanceDemoServer_PU" 
transaction-type="JTA"> 
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> 
<jta-data-source>jdbc/simfin</jta-data-source> 
<class>org.demo.model.MemRegMcgEntity</class> 
<class>org.demo.model.SavAccHolderMcgEntity</class> 
<class>org.demo.model.SavAccMcgEntity</class> 
<class>org.demo.model.SavTransactionEntity</class> 
</persistence-unit> 
</persistence> 

Si prega di suggerire un po 'di ottimizzazione/modifiche in questo file.

E ho utilizzato EJB per utilizzare la classe generica. esempio:

@Stateless 
public class MemberEJB extends GenericDAO<MemRegMcgEntity> { 
/** 
* @see GenericDAO#GenericDAO(Class<T>) 
*/ 
public MemberEJB() { 
super(MemRegMcgEntity.class); 
// TODO Auto-generated constructor stub 
} 

public List<MemRegMcgEntity> getListOfMemberByName(String name){ 
Map<String, Object> parameters = new HashMap<String, Object>(); 
parameters.put("memName", name+'%'); 

return super.findAllWithGivenCondition("Mem.getMemberByName", parameters); 
} 

} 

L'applicazione client fornisce il nome del database da utilizzare e tutti i database ha stessa struttura. Ho solo bisogno di accedere a più database in base alla richiesta del cliente.

+0

Avete intenzione di utilizzare più database con lo stesso object-relational mapping come definito nel persistence.xml? In altre parole: lo stesso insieme di entità sarà usato per tutti i database? – wypieprz

+0

Sì. Hai ragione ... – SudeepShakya

+0

http://forum.spring.io/forum/spring-projects/data/41122-how-to-configure-and-use-multiple-databases-in-spring e http: // stackoverflow.com/questions/10674051/using-spring-jpa-with-hibernate-to-access-multiple-databases-datasources-config potrebbe aiutare .. – Lucky

risposta

2

Quando si ha a che fare con un'app e più DB EclipseLink offre due soluzioni. Qual è la migliore adatto per voi dipende dal vostro caso d'uso, se

Gli utenti devono mappare esporre più unità di persistenza come un unico contesto di persistenza all'interno di un'applicazione.

Date un'occhiata a Using Multiple Databases with a Composite Persistence Unit

Se il vostro caso è che

più client di applicazioni devono condividere le fonti di dati, con accesso privato ai propri dati ambiente.

di dare un'occhiata a Tenant Isolation Using EclipseLink

In alternativa, questo blog post descrive un modo di progettare un multi-tenancy, senza legarsi al fornitore funzionalità specifiche

UPDATE rispetto al commento

Non penso che il tipo di routing di origine dati dinamico che stai cercando esista come un costrutto ready-made di glassfish. Ma non dovrebbe essere troppo difficile da implementare neanche. Dovresti dare un'occhiata allo TomEE's dynamic datasource api e all'implementazione di riferimento che hanno fornito. Dovresti essere in grado di scrivere il tuo router basato su di esso senza troppi problemi

+0

Grazie a U per la risposta. Il mio requisito è che il client richieda l'applicazione con il nome del database e io dovrei colpire il database richiesto in fase di esecuzione e reply.i.e. la connessione al database deve essere impostata dinamicamente per ogni richiesta del client. Tutti i database sono gli stessi solo il nome del database è diverso e il client può avere più richieste contemporaneamente, il webservice dovrebbe rispondere di conseguenza. – SudeepShakya

0

Di sicuro si può fare in modo più sofisticato, ma c'è anche una soluzione diretta che mi viene in mente. Cosa succede se si distribuiscono tante applicazioni quanti più database si hanno e si progetta una piccola applicazione di instradamento delle richieste che inoltrerà tutte le richieste dei clienti all'app corrispondente da "databaseId" fornita nella richiesta. Questa soluzione funzionerà perfettamente in un ambiente distribuito.

+0

Ci stavo anche pensando come hai fatto tu, ma ci dovrebbe essere un modo più efficiente. Il client invia non solo "databaseId" ma anche altri parametri. – SudeepShakya

1

La mia soluzione sarebbe quella di aggiungere una seconda unità di persistenza per il secondo database, quindi rifattorizzare GenericDAO in modo che EntityManager non sia un attributo della classe, ma passato in ogni metodo. Creo quindi gli oggetti facciata per ciascuno dei tuoi database che ottengono il GenericDAO e il pertinente EntityManager iniettato in essi. Se volessi davvero, potresti avere un'interfaccia comune per mantenere l'API uguale. Potrebbe assomigliare a questo:

persistence.xml

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.1" 
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> 

    <persistence-unit name="SavingBalanceDemoServer_PU" transaction-type="JTA"> 
     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> 
     <jta-data-source>jdbc/simfin</jta-data-source> 
     <class>org.demo.model.MemRegMcgEntity</class> 
     <class>org.demo.model.SavAccHolderMcgEntity</class> 
     <class>org.demo.model.SavAccMcgEntity</class> 
     <class>org.demo.model.SavTransactionEntity</class> 
    </persistence-unit> 

    <persistence-unit name="MySecondPersistenceUnit_PU" transaction-type="JTA"> 
     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> 
     <jta-data-source>jdbc/other-jta-datasource</jta-data-source> 
     <class>org.demo.model.OtherEntityOne</class> 
     <class>org.demo.model.OtherEntityTwo</class> 
     <class>org.demo.model.OtherEntityThree</class> 
     <class>org.demo.model.OtherEntityFour</class> 
    </persistence-unit> 

</persistence> 

DAO generico:

public class GenericDAO<T> { 

public void <T extends IEntity> save(EntityManager em, T entity) { 
    em.persist(entity); 
} 

Entity Interfaccia:

public Interface IEntity { 
    .... 
} 

Entity Classe:

public class SomeEntity implements IEntity { 
    .... 
} 
Database

DAO Facciata One:

public class GenericFacadeOne { 

@PersistenceContext(unitName = "SavingBalanceDemoServer_PU") 
private EntityManager em; 
@Autowired 
private GenericDao dao; 

@Transactional(propogation=Propogation.REQUIRED) 
public void saveSomeEntity(SomeEntity entity) { 
    getDao().save(getEm(), entity); 
} 

public void setEm(EntityManager em) { 
    this.em = em; 
} 

public EntityManager getEntityManager() { 
    return this.em; 
} 

public void setDao(GenericDao dao) { 
    this.em = em; 
} 

public GenericDao getDao() { 
    return this.dao; 
} 
} 

DAO Facciata Database Due:

public class GenericFacadeTwo { 

@PersistenceContext(unitName = "MySecondPersistenceUnit_PU") 
private EntityManager em; 
@Autowired 
private GenericDao dao; 

@Transactional(propogation=Propogation.REQUIRED) 
public void saveSomeEntity(SomeEntity entity) { 
    getDao().save(getEm(), entity); 
} 

public void setEm(EntityManager em) { 
    this.em = em; 
} 

public EntityManager getEntityManager() { 
    return this.em; 
} 

public void setDao(GenericDao dao) { 
    this.em = em; 
} 

public GenericDao getDao() { 
    return this.dao; 
} 
} 

Speriamo che abbia un senso, fatemi sapere se avete bisogno di qualsiasi chiarimento!

+0

Ammiro la tua soluzione ma l'implementazione ha bisogno di DAO Facade per ogni singolo database. Potresti per favore elaborare Entity Interface 'IEntity interfaccia pubblica'? Anche la soluzione di @Virgi è buona. – SudeepShakya

+0

Sono d'accordo @ Virgi ha dato una soluzione semplice ed efficace. Immagino che dipenda dalle tue esigenze - mi piace avere il mio generico dao con tutte le operazioni CRUD al suo interno, quindi avere una facciata per incapsulare la mia logica di persistenza in una singola transazione (e fornire una bella API leggibile). L'altra opzione è avere una singola unità di persistenza e aggiornare le proprietà in esso dinamicamente, dare un'occhiata a questa risposta qui http://stackoverflow.com/questions/18583881/changing-persistence-unit-dynamically-jpa – ConMan

4

Abbiamo affrontato lo stesso caso di utilizzo e abbiamo finito per creare unità di persistenza multiple e costruire un factory manager di entità che restituisce il gestore di entità corretto in base a un parametro inviato dal client (come enum nel nostro caso, Environment). Quindi, invece di iniettare il contesto di persistenza nei client, inseriamo questa fabbrica e chiamiamo getEntityManager(environment).

@Stateless 
public class EntityManagerFactory { 

    @PersistenceContext(unitName = "first_PU") 
    EntityManager firstEm; 

    @PersistenceContext(unitName = "second_PU") 
    EntityManager secondEm; 

    public EntityManager getEntityManager(Environment env) { 
     switch (env) { 
     case THIS: 
      return firstEm; 
     case THAT: 
      return secondEm; 
     default: 
      return null; 
     } 
    } 
} 

Esempio enum:

public enum Environment{ 
    DEV, PROD 
} 

Nel tuo caso, il GenericDAO verrebbe riscritta in questo modo:

public class GenericDAO<T> { 

    @EJB 
    private EntityManagerFactory entityManagerFactory; 

    public void save(T entity, Environment env) { 
     entityManagerFactory.getEntityManager(env).persist(entity); 
    } 

} 

E poi il vostro client chiamerebbe con dao.save(someEntity, Environment.DEV).

vostri persistence.xml sarebbero finiti in questo modo:

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.1" 
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> 

    <persistence-unit name="first_PU" transaction-type="JTA"> 
     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> 
     <jta-data-source>jdbc/simfin_1</jta-data-source> 
     <class>org.demo.model.MemRegMcgEntity</class> 
     <class>org.demo.model.SavAccHolderMcgEntity</class> 
     <class>org.demo.model.SavAccMcgEntity</class> 
     <class>org.demo.model.SavTransactionEntity</class> 
    </persistence-unit> 

    <persistence-unit name="second_PU" transaction-type="JTA"> 
     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> 
     <jta-data-source>jdbc/simfin_2</jta-data-source> 
     <class>org.demo.model.MemRegMcgEntity</class> 
     <class>org.demo.model.SavAccHolderMcgEntity</class> 
     <class>org.demo.model.SavAccMcgEntity</class> 
     <class>org.demo.model.SavTransactionEntity</class> 
    </persistence-unit> 

</persistence> 
+0

Potresti per favore elaborare ?? – SudeepShakya

+0

Beh, è ​​piuttosto banale, ma ho aggiunto comunque alcuni dettagli. – Virginie

0

Un'altra soluzione è la creazione di un contesto persistente a livello di codice.

Definire un persistent.xml senza connessione. Simile a:

persistent.xml

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.1" ... > 
    <persistence-unit name="UserInfo" transaction-type="JTA"> 
     <class>mx.saaskun.model.UserInfo</class> 
    </persistence-unit> 
</persistence> 

Creare una fabbrica per la connessione personalizzata:

Il metodo riceve due parametri, il nome dell'unità personalizzato e la JNDI per la connessione.

DynamicResource.java

@Stateless 
@LocalBean 
public class DynamicResource implements Serializable{ 
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 
    public EntityManagerFactory getEntityManager(unitName, jndiConnection){ 
     Map properties = new HashMap(); 
     properties.put("javax.persistence.jtaDataSource", jndiConnection); 
     return Persistence.createEntityManagerFactory(unitName, properties); 
    } 
} 

quindi si utilizza come:

public class UserService{ 
    @EJB 
    DynamicResource radResources; 

    public List<UserInfo> listAll(){ 
      List<UserInfo allUsers = new ArrayList<>(); 
      String[] databases = new String[]{"jndi/simfin","jndi/simfin2"}; 
      for(String db:databases){ 
       List results = listServerUsers("simfin", db); 
       allUsers.addAll(results); 
      } 
      return allUsers; 
    } 

    protected List<UserInfo> listServerUsers(String unitName, String jndi){ 
     EntityManager em= radResources.getEntityManager(unitName,jndi); 
     try { 
      Query q = em.createNamedQuery("UserInfo.findAll"); 
      return (List<UserInfo>) q.getResultList(); 
     } finally { 
      em.close(); 
     } 
    } 
}