JPA entity scanning with Spring
If you've used JPA, does this look familiar?
<persistence>
<persistence-unit>
<class>my.entities.package.Entity</class>
<class>my.entities.package.Another</class>
<class>my.entities.package.More</class>
<class>my.entities.package.EvenMore</class>
<class>my.entities.package.AndMore</class>
<class>my.entities.package.AndEvenMore</class>
</persistence-unit>
</persistence>
Writing JPA entity class names into persistence.xml can be a pain. How could a lazy wise programmer make it easier?
By implementing entity scanning of course!
JPA has its own class scanning system, but it has some fundamental limitations.
As far as I know, it only scans the JAR where persistence.xml resides so it cannot be used to include other entities into the same persistence context. Spring supports PersistenceUnitPostProcessors that can modify a persistence context and add new entities into it!
A post processor could for example scan a base package for entities and add all entities which have proper JPA annotations.
Here's a post processor written in Scala:
import javax.persistence.{Entity, Embeddable, MappedSuperclass}
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
import org.springframework.core.`type`.filter.AnnotationTypeFilter
import org.springframework.orm.jpa.persistenceunit.{MutablePersistenceUnitInfo, PersistenceUnitPostProcessor}
import scala.collection.JavaConversions._
class EntityScanningPersistenceUnitPostProcessor(basePackage: String)
extends ClassPathScanningCandidateComponentProvider(false)
with PersistenceUnitPostProcessor {
addIncludeFilter(new AnnotationTypeFilter(classOf[Entity]))
addIncludeFilter(new AnnotationTypeFilter(classOf[Embeddable]))
addIncludeFilter(new AnnotationTypeFilter(classOf[MappedSuperclass]))
private val log = LoggerFactory.getLogger(this.getClass)
override def postProcessPersistenceUnitInfo(info: MutablePersistenceUnitInfo) {
val count =
findCandidateComponents(basePackage).foldLeft(0L) { (count, beanDefinition) =>
info.addManagedClassName(beanDefinition.getBeanClassName)
count + 1
}
log.info("Registered {} entities from base package {}", count, basePackage)
}
}
Here's how to use it with Spring JavaConfig:
@Bean
def entityManagerFactory = {
val bean = new org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
// ... normal configuration
bean.setPersistenceUnitPostProcessors(Array(new EntityScanningPersistenceUnitPostProcessor("my.entities.package")))
bean
}
The major downside in this approach is that some JPA tooling assumes that persistence.xml contains the entities that will be used.
They will fail because they at compile-time the persistence context seems empty!