Last updated by kgeis 1 year ago

JPA Plugin Requirements Spec

We need to properly support JPA in order to use GORM over other persistence technologies (including AppEngine).

To do this we need to create a JPA plugin and a plugin for each provider. The main plugin is gorm-jpa . In order to use GORM JPA you would have have installed or configured a JPA provider. We should provide some out of the box providers so you can do:

grails install-plugin hibernate-jpa
grails install-plugin gorm-jpa

Configured Beans

The JPA provider plugin will need to take responsibility for configuring the EntityManagerFactory, ReflectiveLoadTimeWeaver, JpaTransactionManager beans named entityManagerFactory , reflectiveLoadTimeWeaver and transactionManager respectively. The JPA provider plugins will need to depend on the DataSource plugin in order to obtain an appropriate dataSource bean.

The JPA provider plugin should also setup the necessary JpaInterceptor to avoid lazy load problems:

if (manager?.hasGrailsPlugin("controllers")) {
    openSessionInViewInterceptor(JpaInterceptor) {
       // etc.
    }
 if(getSpringConfig().containsBean("grailsUrlHandlerMapping")){                    
   grailsUrlHandlerMapping.interceptors << openSessionInViewInterceptor
    }
}

Generation of persistence.xml

We need to generate persistence.xml using MarkupBuilder to avoid the unnecessary boilerplate this provides and also integrate with Grails' configuration model.

For example a typical persistence.xml configuration file with Hibernate looks like:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
   version="1.0">
   <persistence-unit name="manager1" transaction-type="JTA">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <mapping-file>ormap.xml</mapping-file>
      <jar-file>MyApp.jar</jar-file>
      <class>org.acme.Employee</class>
      <class>org.acme.Person</class>
      <class>org.acme.Address</class>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

All of this can be inferred without forcing users into configuration. First the <jta-data-source> is unnecessary as Spring can configure that.

The hibernate-jpa plugin already knows the provider so it can configure the <provider>.

The <class> elements can be obtained using entity scanning (see below). And finally the <properties> can be obtained from DataSource.groovy where we already have:

hibernate {
    cache.use_second_level_cache=true
    cache.use_query_cache=true
    cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}

Which is used to configure Hibernate. In order to do this automatic generation of persistence.xml. The gorm-jpa plugin should provide a scripts/_Events.groovy file that hooks into the generateWebXml event:

eventGenerateWebXmlStart = {
     new File("${projectWorkDir}/resources/persistence.xml").withWriter { w ->
     new MarkupBuilder(w).persistence(xmlns:"http://java.sun.com/xml/ns/persistence") {
         // etc.
     }
}
}

The Hibernate JPA provider plugin should dynamically configure itself using this persistence.xml file depending whether you are in production or not:

def doWithSpring = { // bean builder dsl code entityManagerFactory(LocalEntityManagerFactoryBean) {

if(!application.warDeployed) { persistenceXmlLocation = "${BuildSettingsHolder.settings.projectWorkDir}/resources/persistence.xml" } } }

For production WAR deployment we'll need an event in _Events.groovy that will package the persistence.xml into the default location:

eventCreateWarStart = { warLocation, stagingDir ->
    ant.copy(file:"${projectWorkDir}/resources/persistence.xml", todir:"${stagingDir}/WEB-INF/classes/META-INF"
}

Entity Scanning

The GORM-JPA plugin will need to setup a ComponentScanBeanDefinitionParser bean using the <context:component-scan/> tag to scan for all the classes marked with the java.persistence.Entity annotation.

With a list of persistent entities in hand these will all need to be added to the GrailsApplication object. In order to correctly support data binding and validation within GORM the JPA plugin will need to create an instance of the GrailsDomainClass and add it to each GrailsApplication object. The implementation, let's say JpaGrailsDomainClass will need to know how persistent associations relate, how to evaluate the identity etc.

Dynamic Methods

Once this is done the GormJpaGrailsPlugin will need to implement the doWithDynamicMethods block in order to add the necessary persistent methods. For example:

def doWithDynamicMethods = { appCtx ->
    def jpaTemplate = new JpaTemplate(appCtx.getBean("entityManagerFactory"))
    for(domainClass in application.domainClasses) {
       def theClass = domainClass.getClazz()
       domainClass.metaClass.get { Serializable id ->
           jpaTemplate.find(theClass, id)
       }
       … // etc.
    }
}

As many of the GORM methods should be implemented as possible. In an idea while you should be able to move application between GORM and GORM-JPA but that may be a lofty goal as we would need to implement support for criteria.