使用JPA / EclipseLink / EJB从Java Web应用程序访问多个数据库

我构建了一个简单的SOAP java应用程序(服务器端),我正在使用Glassfish4,JPA/EclipseLink,EJB 。 我在Glassfish中设置了数据库连接(资源/池)。 请建议一些设计模式/知识,以便从单个应用程序中利用多个数据库。 创建多个持久性单元是多重访问的好主意吗? 或者还有其他优化的解决方案吗? 我有一个通用的数据库访问类。

 public class GenericDAO { /* * 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 entityClass; public EntityManager getEntityManager() { return this.em; } public void joinTransaction() { /* em = emf.createEntityManager(); */ em.joinTransaction(); } public GenericDAO(Class 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 objList) { for (Iterator iterator = objList.iterator(); iterator.hasNext();) { T t = (T) iterator.next(); em.persist(t); } } public void delete(Object id, Class 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 entity) { for (Iterator 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() method @SuppressWarnings({ "unchecked", "rawtypes" }) public List 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 findAllWithGivenCondition(String namedQuery, Map parameters) { List result = null; Query query = em.createNamedQuery(namedQuery); if (parameters != null && !parameters.isEmpty()) { populateQueryParameters(query, parameters); } result = (List) query.getResultList(); return result; } @SuppressWarnings("unchecked") public List findAllWithGivenConditionLazyLoading(String namedQuery, Map parameters,int startingAt, int maxPerPage) { List result = null; Query query = em.createNamedQuery(namedQuery); if (parameters != null && !parameters.isEmpty()) { populateQueryParameters(query, parameters); } query.setFirstResult(startingAt); query.setMaxResults(maxPerPage); result = (List) query.getResultList(); return result; } @SuppressWarnings("unchecked") public List findAllWithGivenConditionJpql(String jpql, Map parameters) { List result = null; Query query = em.createQuery(jpql); if (parameters != null && !parameters.isEmpty()) { populateQueryParameters(query, parameters); } result = (List) query.getResultList(); return result; } @SuppressWarnings("unchecked") public T findOneWithGivenConditionJpql(String jpql, Map 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() method @SuppressWarnings("unchecked") protected T findOneResult(String namedQuery, Map 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 parameters) { for (Entry 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 getAllLazyEntities(int startingAt, int maxPerPage, Class 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 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 clazz, String Cond) { Query query = getEntityManager() .createQuery( "select COUNT(p) from " + clazz.getName() + " p " + Cond + " "); Number result = (Number) query.getSingleResult(); return result.intValue(); } } 

动态修改@PersistenceContext(unitName = "SavingBalanceDemoServer_PU")@PersistenceContext(unitName = "SavingBalanceDemoServer_PU")是个好主意吗? 请建议我。

我的persistence.xml是:

    org.eclipse.persistence.jpa.PersistenceProvider jdbc/simfin org.demo.model.MemRegMcgEntity org.demo.model.SavAccHolderMcgEntity org.demo.model.SavAccMcgEntity org.demo.model.SavTransactionEntity   

请在此文件中建议一些优化/更改。

我一直在使用EJB来使用Generic类。 例如:

 @Stateless public class MemberEJB extends GenericDAO { /** * @see GenericDAO#GenericDAO(Class) */ public MemberEJB() { super(MemRegMcgEntity.class); // TODO Auto-generated constructor stub } public List getListOfMemberByName(String name){ Map parameters = new HashMap(); parameters.put("memName", name+'%'); return super.findAllWithGivenCondition("Mem.getMemberByName", parameters); } } 

客户端应用程序提供要使用的数据库名称,每个数据库具有相同的结构。 我只需要根据客户的要求访问多个数据库。

我们遇到了相同的用例,最终创建了多个持久性单元并构建了一个实体管理器工厂,它根据客户端发送的参数返回正确的实体管理器(在我们的例子中为Environment )。 然后,我们不是在客户端中注入持久化上下文,而是注入此工厂并调用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; } } } 

枚举示例:

 public enum Environment{ DEV, PROD } 

在您的情况下,GenericDAO将以这种方式重构:

 public class GenericDAO { @EJB private EntityManagerFactory entityManagerFactory; public void save(T entity, Environment env) { entityManagerFactory.getEntityManager(env).persist(entity); } } 

然后你的客户端会调用dao.save(someEntity, Environment.DEV)

您的persistence.xml最终会像这样:

    org.eclipse.persistence.jpa.PersistenceProvider jdbc/simfin_1 org.demo.model.MemRegMcgEntity org.demo.model.SavAccHolderMcgEntity org.demo.model.SavAccMcgEntity org.demo.model.SavTransactionEntity   org.eclipse.persistence.jpa.PersistenceProvider jdbc/simfin_2 org.demo.model.MemRegMcgEntity org.demo.model.SavAccHolderMcgEntity org.demo.model.SavAccMcgEntity org.demo.model.SavTransactionEntity   

在处理一个应用程序和多个DB时,EclipseLink提供了两种解决方案。 哪一个更适合你,取决于你的用例,如果

用户需要将多个持久性单元映射为应用程序中的单个持久性上下文。

看一下使用复合持久性单元使用多个数据库

如果是这样的话

多个应用程序客户端必须共享数据源,并且可以私有访问其数据环境。

而不是看看使用EclipseLink的租户隔离

或者,此博客文章描述了一种设计多租户的方法,而不绑定供应商特定的function

关于评论的更新

我不认为你所追求的动态数据源路由类型是作为现成的glassfish构造而存在的。 但要实现它也不应该太难。 您应该看一下TomEE的动态数据源api及其提供的参考实现。 您应该能够基于它编写自己的路由器而不会出现太多问题

我的解决方案是为第二个数据库添加第二个持久性单元,然后重构您的GenericDAO,以便EntityManager不是该类的属性,而是传递给每个方法。 然后,我会为每个数据库创建外观对象,这些对象将GenericDAO和相关的EntityManager注入其中。 如果你真的想要你可以有一个通用接口来保持api相同。 它可能看起来像这样:

persistence.xml中

    org.eclipse.persistence.jpa.PersistenceProvider jdbc/simfin org.demo.model.MemRegMcgEntity org.demo.model.SavAccHolderMcgEntity org.demo.model.SavAccMcgEntity org.demo.model.SavTransactionEntity   org.eclipse.persistence.jpa.PersistenceProvider jdbc/other-jta-datasource org.demo.model.OtherEntityOne org.demo.model.OtherEntityTwo org.demo.model.OtherEntityThree org.demo.model.OtherEntityFour   

通用DAO:

 public class GenericDAO { public void  save(EntityManager em, T entity) { em.persist(entity); } 

实体接口:

 public Interface IEntity { .... } 

实体类:

 public class SomeEntity implements IEntity { .... } 

DAO门面数据库一:

 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门面数据库二:

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

希望这是有道理的,如果您需要任何澄清,请告诉我!

当然,它可以以更复杂的方式完成,但也有一个直接的解决方案,我想到了。 如果您部署尽可能多的应用程序,并设计一个小型请求路由应用程序,该应用程序将通过请求中提供的“databaseId”将所有客户端请求转发到相应的应用程序,该怎么办? 此解决方案在分布式环境中运行良好。

另一种解决方案是以编程方式创建持久上下文

在没有连接的情况下定义persistent.xml。 相近:

persistent.xml

    mx.saaskun.model.UserInfo   

为自定义连接创建工厂:

该方法接收两个参数,自定义单元名称和连接的JNDI。

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

那你用作:

  public class UserService{ @EJB DynamicResource radResources; public List listAll(){ List(); String[] databases = new String[]{"jndi/simfin","jndi/simfin2"}; for(String db:databases){ List results = listServerUsers("simfin", db); allUsers.addAll(results); } return allUsers; } protected List listServerUsers(String unitName, String jndi){ EntityManager em= radResources.getEntityManager(unitName,jndi); try { Query q = em.createNamedQuery("UserInfo.findAll"); return (List) q.getResultList(); } finally { em.close(); } } }