24

With Hibernate you can load your Entity classes as:

sessionFactory = new AnnotationConfiguration()
                    .addPackage("test.animals")
                    .addAnnotatedClass(Flight.class)
                    .addAnnotatedClass(Sky.class)
                    .addAnnotatedClass(Person.class)
                    .addAnnotatedClass(Dog.class);

Is there a way to do the same thing - programmatically loading your Entity classes - in a JPA 2.0 compliant way?

The reason for this question is because I'd like to dynamically load my Entity classes, thus not necessarily programmatically.

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
wen
  • 3,782
  • 9
  • 34
  • 54

3 Answers3

29

With the help of Spring I did this in a JPA compliant way.

My "persistence.xml" looks empty, with no entities listed within the <persistence-unit> element.

I then wrote a class that implemented PersistenceUnitPostProcessor like so:

import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import org.reflections.Reflections;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;

public class ReflectionsPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {

    private String reflectionsRoot;
    private Logger log = LoggerFactory.getLogger(ReflectionsPersistenceUnitPostProcessor.class);

    @Override
    public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
            Reflections r = new Reflections(this.reflectionsRoot, new TypeAnnotationsScanner());
            Set<String> entityClasses = r.getStore().getTypesAnnotatedWith(Entity.class.getName());
            Set<String> mappedSuperClasses = r.getStore().getTypesAnnotatedWith(MappedSuperclass.class.getName());

            for (String clzz : mappedSuperClasses)
            {
                    pui.addManagedClassName(clzz);
            }


            for (String clzz : entityClasses)
            {
                    pui.addManagedClassName(clzz);
            }

    }

    public String getReflectionsRoot() {
            return reflectionsRoot;
    }

    public void setReflectionsRoot(String reflectionsRoot) {
            this.reflectionsRoot = reflectionsRoot;
    }
}

Then I adjusted my spring context xml like this:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="jpaVendorAdapter">
                    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                            <property name="showSql" value="false" />
                            <property name="generateDdl" value="true" />
                            <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
                    </bean>
            </property>
            <property name="persistenceUnitName" value="GenericPersistenceUnit"/>
            <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
            <property name="persistenceUnitPostProcessors">
                    <list>
                            <bean class="com.austinmichael.core.repository.ReflectionsPersistenceUnitPostProcessor">
                                    <property name="reflectionsRoot" value="com.austinmichael"/>
                            </bean>
                    </list>
            </property>
    </bean>

Note the registration of the ReflectionsPersistenceUnitPostProcessor in the persistenceUnitPostProcessors setting.

And that's it. Every class with a JPA Entity or MappedSuperclass annotation on the classpath is added to the classpath. I had to give reflections the prefix of a package name to scan through which is why com.austinmichael is there at all. You could register a second ReflectionsPersistenceUnitPostProcessor with a different package name prefix if you want if your entities don't share a common package name prefix.

But, this is now JPAVendor agnostic.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
Carl Horton
  • 306
  • 3
  • 2
  • Is it possible to use this method to programmatically set the databaseName and other properties in persistence.xml? – Jared Hooper Oct 22 '15 at 16:41
5

Is there a way to do the same thing - programmatically loading your Entity classes - in a JPA 2.0 compliant way?

No, this is not supported by JPA so you'll have to do this in a provider specific way. James Sutherland described the process for EclipseLink in this thread like this:

You can access the EclipseLink ServerSession from the EntityManagerFactoryImpl (getServerSession()), and use its' addDescriptor(ClassDescriptor) or addDescriptors() API to add EclipseLink ClassDescriptor. You will need to build the ClassDescriptor meta-data objects directly yourself (or use the Mapping Workbench to create them), as loading from JPA annotations or orm.xml would be more difficult.

Also have a look at this more recent thread for more code sample (the API looks like a bit verbose).

References

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • Doesn't this defeat the whole purpose of JPA? It gives you a lot of control, but at the cost of having to do *absolutely everything* manually. Or am I seeing this the wrong way? – wen May 23 '10 at 17:42
  • @Dennetik Somehow, yes. But I don't think EclipseLink has something as succinct as Hibernate. And I'm not even sure where to put the weaving step if you want lazy ToOne. In other words, I'm not totally sure of what you'd like to do but I have the feeling that JPA might not be appropriate. – Pascal Thivent May 23 '10 at 18:03
  • Is there something for Hibernate? – Ondra Žižka Jun 17 '11 at 03:13
3

I don't think there is such option - JPA supports scanning the classpath for entities or explicitly listing the entity classes in the persistence.xml. Since you're using hibernate as the persistence provider however you can always resort to hibernate code. Have a look at the HibernateEntityManager and HibernateEntityManagerFactory classes. You can cast entity manager factories and entity managers to them and do the usual hibernate stuff.

Bozhidar Batsov
  • 55,802
  • 13
  • 100
  • 117
  • Well, to be honest I'm actually using EclipseLink-2.0.2. I'll look into the classpath scanning, as it sounds promising. Do you have any idea whether or not that would work with bytecode modifications (before creating the EntityManager) with libraries such as Javassist? – wen May 15 '10 at 20:50