Accessing multiple database from a Java web application using JPA/EclipseLink/EJB

前端 未结 5 434
猫巷女王i
猫巷女王i 2020-12-29 15:21

I have built a simple SOAP java application(server side) and I am using Glassfish4,JPA/EclipseLink,EJB. I have set the db connections(resources/poo

相关标签:
5条回答
  • 2020-12-29 15:44

    For sure it can be done more sophisticated way, but there is also a straight forward solution which comes into my mind. What if you deploy as many applications as many databases you have and design a small request routing application which will forward all your clientrequests to the corresponding app by 'databaseId' provided in the request. This solution will work great in a distributed environment.

    0 讨论(0)
  • 2020-12-29 15:52

    My solution would be to add a second persistence unit for the second database, then refactor your GenericDAO so that the EntityManager is not an attribute of the class, but passed into each method. I would then create facade objects for each of your databases which get the GenericDAO and the relevent EntityManager injected into them. If you really wanted you could have a common interface to keep the api the same. It might look like this:

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

    Entity Interface:

    public Interface IEntity {
        ....
    }
    

    Entity Class:

    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 Database Two:

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

    Hopefully that makes sense, let me know if you need any clarification!

    0 讨论(0)
  • 2020-12-29 16:02

    When dealing with one app and multiple DBs EclipseLink provides two solutions. Which one is better suited for you depends on your use-case, if

    Users need to map expose multiple persistence units as a single persistence context within an application.

    Take a look at Using Multiple Databases with a Composite Persistence Unit

    If your case is that

    Multiple application clients must share data sources, with private access to their data environment.

    than take a look at Tenant Isolation Using EclipseLink

    Alternatively, this blog post describes a way of designing a multi-tenancy, without binding to vendor specific functionality

    UPDATE with respect to the comment

    I don't think that the type of dynamic data source routing that you're after exists as a ready made construct of glassfish. But it should not be too hard to implement it either. You should take a look at the TomEE's dynamic datasource api and the reference implementation they provided. You should be able to write your own router based on it without too much issues

    0 讨论(0)
  • 2020-12-29 16:08

    We faced the same use case and ended up creating multiple persistence-unit and building an entity manager factory which returns the correct entity manager according to an parameter sent by the client (as an enum in our case, Environment). Then, instead of injecting the persistence context in the clients, we inject this factory and call 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;
            }
        }
    }
    

    Example enum:

    public enum Environment{
        DEV, PROD
    }
    

    In your case, the GenericDAO would be refactored this way:

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

    And then your client would call with dao.save(someEntity, Environment.DEV).

    Your persistence.xml would end up like this:

    <?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 讨论(0)
  • 2020-12-29 16:08

    Another solution is creating the persistent context programmatically.

    Define an persistent.xml without the connection. Similar to:

    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>
    

    Create a factory for the custom connection:

    The method receives two parameters, the custom unit name and the JNDI for the connection.

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

    then you use 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();
             }
         }
     }
    
    0 讨论(0)
提交回复
热议问题