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

时间:2015-04-10 09:32:44

标签: java java-ee jpa glassfish eclipselink

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

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

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

我的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>

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

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

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

}

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

5 个答案:

答案 0 :(得分:4)

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

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

public enum Environment{
    DEV, PROD
}

然后您的客户将拨打public class GenericDAO<T> { @EJB private EntityManagerFactory entityManagerFactory; public void save(T entity, Environment env) { entityManagerFactory.getEntityManager(env).persist(entity); } }

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

dao.save(someEntity, Environment.DEV)

答案 1 :(得分:2)

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

  

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

查看Using Multiple Databases with a Composite Persistence Unit

如果你的情况是那样

  

多个应用程序客户端必须与私有共享数据源   访问他们的数据   环境。

而不是看Tenant Isolation Using EclipseLink

或者,此blog post描述了一种设计多租户的方法,而不绑定供应商特定的功能

关于评论的更新

我认为您所追求的动态数据源路由类型不是作为现成的glassfish构造存在的。但要实现它也不应该太难。您应该查看TomEE's dynamic datasource api及其提供的参考实现。您应该能够在没有太多问题的情况下编写自己的路由器

答案 2 :(得分:1)

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

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

Generic DAO:

public class GenericDAO<T> {

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

实体界面:

public Interface IEntity {
    ....
}

实体类:

public class SomeEntity implements IEntity {
    ....
}

DAO Facade Database 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 Facade数据库二:

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

希望这是有道理的,请告诉我你是否需要澄清!

答案 3 :(得分:0)

可以肯定的是,它可以采用更复杂的方式,但我也会想到一个直接的解决方案。如果您部署的应用程序数量与您拥有的数据库数量相同,并设计一个小型请求路由应用程序,该应用程序会将所有客户请求转发到相应的应用程序,该数据库由数据库提供。在请求中提供。该解决方案在分布式环境中非常有用。

答案 4 :(得分:0)

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

定义没有连接的persistent.xml。类似于:

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>

为自定义连接创建工厂:

该方法接收两个参数,自定义单元名称和连接的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);
    }
}

然后你使用as:

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