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