How to inject persistence context to different data source programmatically

前端 未结 4 1078
时光说笑
时光说笑 2020-12-03 06:01

In standard EJB 3, when injecting entity manager, persistence unit (which refers to datasource) is hardcoded into annotation: (or alternatively xml file)

@Pe         


        
相关标签:
4条回答
  • 2020-12-03 06:33

    It is possible! I've done it and it works under JBoss AS and WebSphere.

    I use a custom persistence provider which extends org.hibernate.ejb.HibernatePersistence (you need to modify a private static final field to set your persistence provider name into org.hibernate.ejb3.Ejb3Configuration.IMPLEMENTATION_NAME: this is a kind of black magic but it works). Make sure your persistence.xml's persistence units have the custom provider set in the <provider> tag and your custom provider is registered in META-INF/services/javax.persistence.spi.PersistenceProvider.

    My provider overrides the createContainerEntityManagerFactory(PersistenceUnitInfo,Map) method called the Java EE container as such (for JTA datasource but it would be easy to do it also for non JTA datasource):

    @Override
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
    
        // load the DataSource
        String newDataSourceName = ...; // any name you want
        DataSource ds = (DataSource)(new InitialContext().lookup(newDataSourceName));
    
        // modify the datasource
        try {
            try {
                // JBoss implementation (any maybe other Java EE vendors except IBM WebSphere)
                Method m = info.getClass().getDeclaredMethod("setJtaDataSource", DataSource.class);
                m.setAccessible(true);
                m.invoke(info, ds);
    
            } catch (NoSuchMethodException e) {
                // method does not exist (WebSphere?) => try the WebSphere way
    
                // set the datasource name
                Method m = info.getClass().getDeclaredMethod("setJtaDataSource", String.class);
                m.setAccessible(true);
                m.invoke(info, newDataSourceName);
    
                // do the lookup
                Method m2 = info.getClass().getDeclaredMethod("lookupJtaDataSource", String.class);
                m2.setAccessible(true);
                m2.invoke(info);
            }
        } catch (Throwable e) {
            throw new RuntimeException("could not change DataSource for "+info.getClass().getName());
        }
    
        // delegate the EMF creation
        return new HibernatePersistence().createContainerEntityManaferFactory(info, map);
    }
    

    The createEntityManagerFactory(String,Map) also overriden but is much simpler:

    @Override
    public EntityManagerFactory createEntityManagerFactory(String persistenceUnitInfo, Map map) {
    
        // change the datasource name
        String newDataSourceName = ...; // any name you want
        if (map==null) map = new HashMap();  
        map.put(HibernatePersistence.JTA_DATASOURCE, newDataSourceName);  
    
        // delegate the EMF creation
        return new HibernatePersistence().createEntityManaferFactory(persistenceUnitInfo, map);
    }
    

    Note that I only wrote here the core code. In fact, my persistence provider has a lot of other functionalities:

    • check that the DataSource is up and running
    • set the transaction manager for JBoss or WebSphere
    • cache the EMF for lower memory usage
    • reconfigure the Hibernate query plan cache for smaller memory usage
    • register JMX bean (to allow more than one EAR to get the same persistence unit name)
    0 讨论(0)
  • 2020-12-03 06:33

    I want to indicate that the usage of

    Persistence.createEntityManagerFactory(persistenceUnitName)
    

    recommended in the answer of Nayan is classified by the JPA Specification (JSR 317) as follows (footnote in "9.2 Bootstrapping in Java SE Environments"):

    "Use of these Java SE bootstrapping APIs may be supported in Java EE containers; however, support for such use is not required."

    So this isn't a standard solution for EJB. Anyway, I can confirm that this is working in EclipseLink.

    Note: I'm not yet allowed to post this as a comment.

    0 讨论(0)
  • 2020-12-03 06:38
    • Configure required data-sources & persistent-units in persistence.xml.
    <persistence-unit name="UNIT_NAME" transaction-type="JTA">
          <provider>PERSISTENCE_PROVIDER</provider>
              <jta-data-source>java:DATA_SOURCE_NAME</jta-data-source>
    </persistence-unit>
    
     -- other units  
    

    Now at runtime you can build entity-manager for the required persistence-unit. Create separate persistence-units for each data-source.

    //---
    EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
    EntityManager em = emf.createEntityManager();
    //---
    
    • Else you can also build factory by providing a map of properties like db-url, userName etc.
    createEntityManagerFactory(persistenceUnitName,propertiesMap);
    

    This will create and return an EntityManagerFactory for the named persistence unit using the given properties. Therefore you can change the properties at runtime accordingly.

    0 讨论(0)
  • 2020-12-03 06:47

    Using EclipseLink, You can set a DataSource configured in your app server.

    import org.eclipse.persistence.config.PersistenceUnitProperties;
    ...
    
    
    ....
    Map props = new HashMap();  
    props.put(PersistenceUnitProperties.JTA_DATASOURCE, "dataSource");  
    EntityManagerFactory  emf = Persistence.createEntityManagerFactory("UNIT_NAME", props);
    EntityManager em = emf.createEntityManager();
    

    PU_NAME refers to the name used in the file persistence.xml
    dataSource refers name used in the app server for the jdbc Resource as "jdbc/sample"

    0 讨论(0)
提交回复
热议问题