2015-04-10 10 views
6

Ich habe eine einfache SOAP Java-Anwendung (Server-Seite) gebaut und ich verwende Glassfish4,JPA/EclipseLink,EJB. Ich habe die Db-Verbindungen (Ressourcen/Pools) in Glassfish festgelegt. Bitte schlagen Sie ein Designmuster/Wissen vor, um mehrere Datenbanken aus einer einzigen Anwendung zu verwenden. Ist das Erstellen mehrerer Persistenzeinheiten eine gute Idee für den Mehrfachzugriff? Oder gibt es eine andere optimierte Lösung? Ich habe eine generische Klasse von Datenbankzugriff.Zugriff auf mehrere Datenbank von einer Java-Webanwendung mit 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(); 
} 
} 

modifizierenden dynamisch unitName in @PersistenceContext(unitName = "SavingBalanceDemoServer_PU") eine gute Idee? Bitte schlage mich vor.

Mein persistence.xml ist:

<?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> 

vorschlagen Bitte einige Optimierung/Änderungen in dieser Datei.

Und ich habe EJB verwendet, um die generische Klasse zu verwenden. zB:

@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); 
} 

} 

Die Client-Anwendung stellt die Datenbank-Namen zu verwenden und jede Datenbank gleiche Struktur hat. Ich muss nur auf mehrere Datenbanken zugreifen, je nach Kundenwunsch.

+0

Wollen Sie mehrere Datenbanken mit dem gleichen objektrelationalen Mapping verwenden, wie in der persistence.xml definiert? Mit anderen Worten: Wird für alle Datenbanken die gleiche Menge von Entitäten verwendet? – wypieprz

+0

Ja. Du hast sehr viel Recht .. – SudeepShakya

+0

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

Antwort

2

Wenn es um eine App und mehrere DBs geht, bietet EclipseLink zwei Lösungen. Welche ist besser geeignet für die auf dem Anwendungsfall abhängt, wenn

Benutzer innerhalb einer Anwendung mehrere Persistenzeinheiten als einzelne Persistenzkontext aussetzen Karte benötigen.

Werfen Sie einen Blick auf Using Multiple Databases with a Composite Persistence Unit

Wenn Ihr Fall, dass die Kunden teilen müssen Datenquellen mit eigenem Zugriff auf ihre Daten Umgebung

Multiple-Anwendung ist.

als Alternative bei Tenant Isolation Using EclipseLink

einen Blick darauf werfen, beschreibt diese blog post eine Möglichkeit, eine Multi-Tenancy entwerfen, ohne spezifische Funktionalität Hersteller Bindung

UPDATE in Bezug auf den Kommentar

Ich denke nicht, dass die Art des dynamischen Datenquellen-Routings, nach der Sie suchen, als ein fertiges Konstrukt von Glassfish existiert. Aber es sollte auch nicht zu schwer sein, es umzusetzen. Sie sollten sich die TomEE's dynamic datasource api und die von ihnen zur Verfügung gestellte Referenzimplementierung ansehen. Sie sollten Ihren eigenen Router basierend darauf ohne zu viele Probleme schreiben können

+0

Vielen Dank für die Antwort. Meine Anforderung ist, dass der Client die Anwendung mit dem Datenbanknamen anfordert und ich die benötigte Datenbank zur Laufzeit und reply.i.e treffen sollte. Die Datenbankverbindung sollte für jede Clientanforderung dynamisch festgelegt werden. Alle Datenbanken sind gleich, nur der Datenbankname ist unterschiedlich und der Client kann mehrere Anfragen gleichzeitig haben, der Webservice sollte entsprechend antworten. – SudeepShakya

0

Sicherlich kann es ausgefeilter Weise getan werden, aber es gibt auch eine einfache Lösung, die mir in den Sinn kommt. Was ist, wenn Sie so viele Anwendungen wie viele Datenbanken bereitstellen und eine kleine Anforderungsweiterleitungsanwendung entwerfen, die alle Ihre Clientanfragen an die entsprechende App über die in der Anforderung angegebene Datenbank-ID weiterleitet. Diese Lösung wird in einer verteilten Umgebung funktionieren.

+0

Ich habe auch darüber nachgedacht wie du, aber es sollte einen effizienteren Weg geben. Der Client sendet nicht nur 'databaseId', sondern auch andere Parameter. – SudeepShakya

1

Meine Lösung wäre, eine zweite Persistenzeinheit für die zweite Datenbank hinzuzufügen, dann dein GenericDAO so zu refactorieren, dass der EntityManager kein Attribut der Klasse ist, sondern an jede Methode übergeben wird. Ich würde dann für jede Ihrer Datenbanken Fassadenobjekte erstellen, in die das GenericDAO und der relevante EntityManager eingefügt werden. Wenn Sie wirklich wollten, könnten Sie eine gemeinsame Schnittstelle haben, um die API gleich zu halten. Es könnte wie folgt aussehen:

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> 

Generisches DAO:

public class GenericDAO<T> { 

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

Entity-Schnittstelle:

public Interface IEntity { 
    .... 
} 

Entity Klasse:

public class SomeEntity implements IEntity { 
    .... 
} 

DAO Fassade Datenbank 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 Fassade Datenbank Zwei:

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; 
} 
} 

Hoffentlich, die Sinn macht, lassen Sie mich wissen, wenn Sie Klärung bedürfen!

+0

Ich bewundere Ihre Lösung, aber die Implementierung benötigt DAO Facade für jede einzelne Datenbank. Würden Sie bitte Entity Interface 'public Interface IEntity' ausarbeiten? Lösung geben by @ Virgi ist auch eine gute. – SudeepShakya

+0

Ich stimme zu @Virgi gab eine einfache und effektive Lösung. Ich schätze, es hängt von Ihren Anforderungen ab - ich mag meinen generischen Dao mit all den CRUD-Operationen, dann habe ich eine Fassade, um meine Persistenzlogik in eine einzige Transaktion zu kapseln (und eine gut lesbare API bereitzustellen). Die andere Möglichkeit besteht darin, eine einzelne Persistenzeinheit zu haben und die Eigenschaften dynamisch zu aktualisieren. Sehen Sie sich diese Antwort hier an: http://stackoverflow.com/questions/18583881/changing-persistence-unit-dynamically-jpa – ConMan

4

Wir hatten denselben Anwendungsfall und erstellten mehrere Persistenzeinheiten und bauten einen Entity Manager Factory, der den korrekten Entity Manager gemäß einem vom Client gesendeten Parameter zurückgibt (in unserem Fall Environment). Anstatt den Persistenzkontext in die Clients zu injizieren, injizieren wir dann diese Factory und rufen getEntityManager(environment) auf.

@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; 
     } 
    } 
} 

Beispiel Enum:

public enum Environment{ 
    DEV, PROD 
} 

In Ihrem Fall würde die GenericDAO auf diese Weise umgestaltet werden:

public class GenericDAO<T> { 

    @EJB 
    private EntityManagerFactory entityManagerFactory; 

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

} 

Und dann würde Ihr Kunde mit dao.save(someEntity, Environment.DEV) nennen.

Ihre persistence.xml würde bis am Ende wie folgt:

<?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

Würden Sie das bitte näher ausführen? ?? – SudeepShakya

+0

Nun, es ist eher trivial, aber ich habe trotzdem einige Details hinzugefügt. – Virginie

0

Eine andere Lösung programmatisch den persistenten Kontext erstellt.

Definieren Sie eine persistent.xml ohne die Verbindung. Ähnlich wie:

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> 

eine Fabrik für die Verbindung benutzerdefinierten erstellen:

Die Methode erhält zwei Parameter, die benutzerdefinierten Gerätenamen und die JNDI für die Verbindung.

DynamischeRessource.

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); 
    } 
} 

dann verwenden Sie als:

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(); 
     } 
    } 
} 
Verwandte Themen