Adding entity classes dynamically at runtime

前端 未结 2 1471
梦谈多话
梦谈多话 2020-12-02 09:48

I have this requirement to add entity classes to the persistence unit at runtime rather than specifying all of them in the persistence.xml. Can someone help me with the same

2条回答
  •  生来不讨喜
    2020-12-02 10:32

    I am late to the party, but I think this will save some people some headache. I implemented classpath scanning for pure JPA (no spring etc needed) that integrates with e.g. guice-persist if needed as well.

    Here's what you need to do.

    First, change the persistence.xml and add your own implementation, like:

    
    
    
    
        my.custom.package.HibernateDynamicPersistenceProvider
    
        true
    
        
            
            
            
            
        
    
    

    In order for the Providers to be recognised, you will have to make it discoverable. JPA discovers using the service loading mechanism, so we add:

    /src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider
    

    This file has exactly one line:

    my.custom.package.HibernateDynamicPersistenceProvider
    

    Finally add your own provider and base it on the HibernateProvider (I base it on that since I want to use hibernate):

    public class HibernateDynamicPersistenceProvider extends HibernatePersistenceProvider implements PersistenceProvider {
    
        private static final Logger log = Logger.getLogger(HibernateDynamicPersistenceProvider.class);
    
        public static final String CUSTOM_CLASSES = "CUSTOM_CLASSES";
    
        @Override
        protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(
                PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader) {
    
            if(persistenceUnitDescriptor instanceof ParsedPersistenceXmlDescriptor) {
                ParsedPersistenceXmlDescriptor tmp = (ParsedPersistenceXmlDescriptor) persistenceUnitDescriptor;
                Object object = integration.get("CUSTOM_CLASSES");
            }
    
            return super.getEntityManagerFactoryBuilder(persistenceUnitDescriptor, integration, providedClassLoader);
        }
    
        protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties, ClassLoader providedClassLoader) {
            log.debug( String.format("Attempting to obtain correct EntityManagerFactoryBuilder for persistenceUnitName : %s", persistenceUnitName ));
    
            final Map integration = wrap( properties );
            final List units;
            try {
                units = PersistenceXmlParser.locatePersistenceUnits( integration );
            }
            catch (Exception e) {
                log.debug( "Unable to locate persistence units", e );
                throw new PersistenceException( "Unable to locate persistence units", e );
            }
    
            log.debug( String.format("Located and parsed %s persistence units; checking each", units.size() ));
    
            if ( persistenceUnitName == null && units.size() > 1 ) {
                // no persistence-unit name to look for was given and we found multiple persistence-units
                throw new PersistenceException( "No name provided and multiple persistence units found" );
            }
    
            for ( ParsedPersistenceXmlDescriptor persistenceUnit : units ) {
                log.debug( String.format(
                        "Checking persistence-unit [name=%s, explicit-provider=%s] against incoming persistence unit name [%s]",
                        persistenceUnit.getName(),
                        persistenceUnit.getProviderClassName(),
                        persistenceUnitName
                ));
    
                final boolean matches = persistenceUnitName == null || persistenceUnit.getName().equals( persistenceUnitName );
                if ( !matches ) {
                    log.debug( "Excluding from consideration due to name mis-match" );
                    continue;
                }
    
                // See if we (Hibernate) are the persistence provider
    
                String extractRequestedProviderName = ProviderChecker.extractRequestedProviderName(persistenceUnit, integration);
    
                if ( ! ProviderChecker.isProvider( persistenceUnit, properties ) && !(this.getClass().getName().equals(extractRequestedProviderName))) {
                    log.debug( "Excluding from consideration due to provider mis-match" );
                    continue;
                }
    
                return getEntityManagerFactoryBuilder( persistenceUnit, integration, providedClassLoader );
            }
    
            log.debug( "Found no matching persistence units" );
            return null;
        }
    }
    

    I had to overwrite 2 methods, first:

    protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(
                PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader)
    

    This is the intercepting method. I added a custom property "CUSTOM_CLASSES" which should really be called "CUSTOM_PACKAGES" which will list all packages that need to be scanned. At this point I am a bit lazy and I will skip the actual classpath scanning, but you can do it yourself - it's quite straight forward. You can then call

    tmp.addClasses("class1", "class2");
    

    Where the classes are the ones you discovered.

    The second method we are overriding is:

    protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties, ClassLoader providedClassLoader)
    

    This is because the provider we are extending is hardcoded to only allow hibernate classes to create an EMF. Since we have a custom class intercepting the construction, our names don't add up. So I added:

    String extractRequestedProviderName = ProviderChecker.extractRequestedProviderName(persistenceUnit, integration);
    
    if ( ! ProviderChecker.isProvider( persistenceUnit, properties ) && !(this.getClass().getName().equals(extractRequestedProviderName))) {
            log.debug( "Excluding from consideration due to provider mis-match" );
            continue;
    }
    

    This extends the normal hibernate check to also include my custom provider to be valid.

    Wola, we are done, you now have hibernate enabled classpath scanning with JPA.

提交回复
热议问题