Last updated by mingfai 5 years ago
Provide standalone JPA functions without GORM, optional Groovy-style JPA APIs, Validator (JSR 303 draft) and JPA Unit Test supports. It is different from the official JPA support as follows:
  • your application depends on (i.e. is coded against) JPA interface rather than GORM
  • the JPA classes could be put in /src or /grails-app/domains
    • it's unsure if the default GORM plugin will do anything to do the entity classes under /domain. in future version, this plugin may try to disable the default GORM plugin
    • it's uncertain if the entity class will work against Grails data binding, scaffolding and any features that works for GORM. Please test and let me know
  • you probably could package your entity classes for Java
others:

Changes

  • <0.1.9> All demo classes are disabled or removed. Please reference to the demo entity and controller in the svn for reference.

Features

  • Simplified development with more flexible configuration
    • automatic generation of persistence.xml
    • if you don't need to use multiple persistence unit and/or data source, and you don't mind to have your EntityManagerFactory called "entityManagerFactory", u could skip all configurations. By default, the plugin scan the classpath for any entity class and add to a default persistence unit named 'defaultPersistenceUnit'
    • you could use wildcard character to define which entity classes shall belong to which persistence unit
  • <0.1.1> validator api
    • included the latest snapshot library of JSR 303 Bean Validation API
    • base on Agimatec Validation
  • <0.1.2> Proposed Groovy JPA API
    • The Proposed Groovy JPA API is included in the Grails JPA Plugin, but could work independently in any Groovy application
    • The API provides two Groovy Category, one for stateless operation and one for thread-scope stateful operations rely on the JPAContextHolder
    • The API also provides a JPA instance that as similar as the Groovy JMS instance. (but improved over it)
    • Good test coverage, see the test cases for how it works
  • <0.1.4> Provider a JPATestHelper.createTempEntityManagerFactory() method
    • It scan the classpath according to your criteria, prepare a temp persistence.xml and create a EntityManagerFactory
    • See the examples in the test cases in this project.
  • others
    • Support any JPA implementation
    • support Grails data source
    • utilize Spring LocalContainerEntityManagerFactoryBean

Installation and Configuration

  • Installation
    grails install-plugin jpa
  • Notice: Grails by default will install the Hibernate plugin in any project. This plugin also bundle with Hibernate libraries but is not compatible with the Hibernate Plugin. Please ensure the Hibernate plugin is uninstalled
    • check the application.property to ensure the line of hibernate plugin is removed
    • check your USER_HOME/.grails/${grailsVersion}/projects/${projectName}/plugins/hibernate.* are removed
  • Configuration - Check the JPA Plugin Configuration page

JPA Usage

  • This is a JPA plugin and you are expected to learn about JPA. This plugin base on Spring JPA support and by default provide Hibernate JPA implementation
  • Simple scenario
    • Sample Entity Class
      package demo.jpa
      
      import javax.persistence.*
      
      @Entity @Table (name = "DEMO_PERSON")
      public class Person {
        @Id @GeneratedValue (strategy = GenerationType.AUTO) Long id;
        @Basic String name;
        @Temporal (TemporalType.TIMESTAMP) Date lastUpdated;
        @Version int version;
        //List<Address>
      }
      
  • Basic JPA Operation.
    • Please google some JPA Tutorial
    • Data insertion in a controller
      import javax.persistence.* 
      
      class JpaController{
        def entityManagerFactory;
      
        def insert = {
         def em = inmemoryEMF.createEntityManager()
         em.transaction.begin()
         em.persist( new Person(name:params.'name') )
         em.transaction.commit()
         render "new person is inserted to database"
        }
      }
      
    • Check the demo controller for example about query and update

Validator Usage

  • <0.1.1> Latest JSR 303 snapshot with agimatec-validation implementation
  • Usage
    • Sample Entity
      @Entity
      public class Person {
        @Basic @Min (value = 13L, message = "{Person.age.min}") Integer age;
      }
    • perform validation
      • by default, a validate() method is added to any entity class. perform validation is as simple as:
        List<javax.validation.ConstraintViolation> violations = new Person(age:10).validate()
      • alternative, you may use the ValidatorUtil
        use(ValidatorUtil){
           def violations = new Person(age:10).validate()
           if (violations?.size()>0) violations.each{ println it.interpolatedMessage }
         }
        
      • and for sure, you could use JSR303 API directly. You may obtain a validator either via the standard API, or with ValidatorContextHolder.getValidator()
    • See the Config.groovy and grails-app/i18n/validators*.properties for an example of how to configure the validator.

Proposed Groovy JPA API

  • <0.1.2> See the test cases to get to know how it works
  • The "Groovy JPA API" usage is optional, and it provides some extra API to use JPA.
  • There are three ways/level of usage
    • Use the stateless Category
      use(JPACoreCategory) {
            assertEquals 3, em.getEntities('query': 'select p from Person p')?.size()
            assertEquals 'person1', em.getEntities('query': 'select p from Person p')[0].'name'
            assertEquals 3, em.getEntities('sql': 'SELECT * FROM DEMO_PERSON p', 'mapBy': Person.class)?.size()
            assertEquals 'person1', em.getEntities('sql': 'SELECT * FROM DEMO_PERSON p', 'mapBy': Person.class)[0].'name'
          }
      
    • Use the Stateful(ThreadLocal scope) Category
      • JPA.run static import
        import static groovy.jpa.JPA.run
        
        JPAContextHolder.setJPA(new JPA(em:em))
        run{
         new Person(name:'elegant JPA').persist()
        }
        
      • static JPA.run
        JPA.run{
          new Person(name:'elegant JPA').persist()
        }
        
      • explicity Category usage
        JPAContextHolder.setJPA(new JPA(em:em))
        use(JPACategory) { 
          new Person(name: 'no arg').persist() 
        }
        
        // Note: in future version, all entities registered in Grails will have the JPACategory methods added to EMC, so the use() syntax can be omitted 
        
        JPAContextHolder.clear() // remove the static threadlocal variable
        
    • Use a JPA instance to manage transaction
      new JPA(em: injectedEntityManager) {
            def trialPerson = new Person(name: 'person4', age: 10)
            trialPerson.persist()
            trialPerson.'name' = 'person4.2'
            trialPerson.flush()
            trialPerson.remove()
       }
      
  • The full parameters are not documented anywhere. You should lookup the method comment first and if it is not stated, read the source code. Each method is pretty clear and almost all methods are under 20 line-of-code

JPATestHelper usage

  • <0.1.4> setUp, tearDown
    • it wraps createTempEntityManagerFactory and new JPA() creation.
    • sample:
      void setUp() {setUpResult = JPATestHelper.setUp(includes: 'demo.jpa.*')}
      void setUp() {setUpResult = JPATestHelper.setUp(null, em: injectedEM)}
      void tearDown() { JPATestHelper.tearDown() }
      
    • see comment at source code for other variety of usage
  • other
    EntityManagerFactory emf = JPATestHelper.createTempEntityManagerFactory(includes: 'demo.jpa.*') 
    EntityManagerFactory emf = JPATestHelper.createTempEntityManagerFactory(includes: ['demo.jpa.*','demo.**.entity.*'])
    EntityManagerFactory emf = JPATestHelper.createTempEntityManagerFactory(includes: ['demo.jpa.*'], excludes:'demo.jpa.Address')
    
    • for both 'includes' and 'excludes', they may take either a String or a Collection of String, and accept '*' and '**' wildcard characers

Other docs

  • EntityBeanScanner parameters - indirectly used by the first argument of JPAHelper.setUp(entityBeanScannerCfg), and JPAHelper.createTempEntityManagerFactory(entityBeanScannerCfg)

Demo

<0.1.9> All demo classes are disabled or removed. Please reference to the demo entity and controller in the svn for reference.
  • pre-requisite: JDK and Grails 1.1 (tested with beta 3)
  • To create a project called demo with JPA plugin,
    • create project and install plugin
      grails create-app demo
      cd demo
      grails uninstall-plugin hibernate # see remarks
      grails install-plugin jpa
      grails run-app
      
    • remarks: The Hibernate plugin is sometimes installed by default. Run the grails uninstall-plugin hibernate command may uninstall it. You see error, following the procedure at the first comment of GRAILS-4005
    • if it is successful, you should see:
      Server running. Browse to http://localhost:8080/demo
  • access the demo controller
    • Access http://localhost:8080/demo (and click on the jpaDemoController link) or directly at http://localhost:8080/demo/jpaDemo
    • by default, the database has no data. click on the 'add' link at the top to add a record
    • after added a record, there will be one row in the table. the last column provides two links that will rename the 'name' of the record.
  • Next, you may want to read the source of the JpaDemoController
    • hopefully these points are not changed by the time you read it
    • add - use Groovy JPA, you'll see it saves data without any begin and end of transaction
    • update - plain JPA usage with Groovy syntax, you could see JPA syntax like createQuery, getResultList, persist, commit etc.
    • validate - show how to use the validator api. this method is not clickable on screen. you need to type in the URL to access it.
  • by default, the demo entity classes are included in the classpath and will be added to your entityManagerFactory. You may disable them by using the following configuration in Config.groovy:
    plugins {
      jpa {
        'demo' {
          disable = true
        }
    }
    
    • The parameter does not disable the demo classes from loading to your application. If you definitely don't want the few classes be loaded your production application, you could manually remove them under $user_home/.grails/$grailsVersion/projects/$projectName/plugins/jpa*/

Limitations and Issues

  • This plugin doesn't support @PersistenceContext injection of EntityManager by default. However you could add additional beans to the Spring context to get it work. See the relevant discussion thread
  • @MappedSuperclass doesn't work in Groovy. i.e. you cannot make a hierarchy of entity classes as same as in Java.
    • Possible workarounds are:
      • use Java for the entity class (notice that the parent class could be in Groovy)
      • not to use parent entity class

Change History and Outstanding Tasks

arthor: Mingfai Ma