I managed to get EclipseLink 2.3.0 JPA provider to work with domain classes that are not inside a package in the following way.
In the following I will show how to setup a grails application that is using EclipseLink as a JPA provider to persist domain classes and run the application in a Tomcat 6 web container.
Start by
Hint: Do not put domain classes in packages. EclipseLink will fail when processing e.g. "model.Person" because of the dot in the name, but will do fine with e.g. "Person" (compare to above post).
Edit "grails-app\domain\Person.groovy" as follows:
import javax.persistence.*;
@Entity
class Person {
@Id
@GeneratedValue
long id;
@Basic
long version;
@Basic
String firstName
@Basic
String lastName
static constraints = {
firstName(nullable:true)
lastName(nullable:true)
}
}
Generate a controller and a view for the defined entity:
grails generate-controller Person
grails generate-view Person
Now we need to modify the generated controller slightly, so that all actions modifying the entity (save, edit, update, delete) are encapsulated in a transaction (compare to bug GPGORMJPA-6). This is done by encapsulating the whole action into a "Person.withTransaction" block.
Edit "grails-app\controllers\PersonController.groovy" as follows:
[...]
def save = {
Person.withTransaction {
[...original code stays in this block...]
}
}
[...]
def edit = {
Person.withTransaction {
[...original code stays in this block...] }
}
}
def update = {
Person.withTransaction {
[...original code stays in this block...]
}
}
def delete = {
Person.withTransaction {
[...original code stays in this block...]
}
}
[...]
Now we define a persitence unit "manager" that specifies a set of classes to manage persistence for (class "Person") and which JPA provider to use for these classes (in our case EclipseLink).
Create a file "web-app\WEB-INF\classes\META-INF\persistence.xml" as follows:
<?xml version="1.0" encoding="UTF-8"?>
<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="manager" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>Person</class>
<properties>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
Please note: The file will be deleted if "target" dir of project is cleaned, e.g. when "grails clean" is called explicitly. So it's a good idea to make a backup of the file outside of the "target" dir.
To make EclipseLink available to the grails project, download EclipseLink 2.3.0 installer ZIP and extract "eclipselink.jar" from "eclipselink\jlib\" in ZIP to "lib" folder of grails project:
lib\eclipselink.jar
Now we need to make sure, that an "entityManagerFactory" and "transactionManager" bean are created when the web application is started. The beans will provide the needed access to the JPA provider that will manage all the persistency.
Edit "grails-app\conf\spring\resources.groovy" as follows:
beans = {
entityManagerFactory(org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean) {
dataSource = ref("dataSource")
beanClassLoader = ref("classLoader")
loadTimeWeaver = new org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver()
}
transactionManager(org.springframework.orm.jpa.JpaTransactionManager) {
entityManagerFactory = entityManagerFactory
}
}
Please note that we specified a special loadTimeWeaver in the above declarations, that enables the JPA persistence provider to hook into java bytecode on the fly during runtime. When using EclipseLink this is essential. If just using org.springframework.instrument.classloading.SimpleLoadTimeWeaver for testing purposes, this enables the web app to start, but when accessing JPA entities you will encounter exceptions like "0 is not a known entity type", as the persistence provider is unable to hook into the management of the entity classes at runtime.
The use of InstrumentationLoadTimeWeaver is a bit of a hassle, as it will only work, if
- the java virtual machine executing the web app server is started using "spring-agent" java agent, AND
- our web app is loaded by the Tomcat server using a special classloader named "TomcatInstrumentableClassLoader"
To achieve this, first download spring-agent-2.5.6.SEC02.jar and spring-instrument-tomcat-3.0.5.RELEASE.jar.
Assume that you have installed Tomcat server in directory %CATALINA_HOME%.
- Copy the downloaded jar files "spring-agent-2.5.6.SEC02.jar" and "spring-instrument-tomcat-3.0.5.RELEASE.jar" to %CATALINA_HOME%\lib
Now we modify "%CATALINA_HOME%\bin\catalina.bat" that is used by e.g. the start and stop scripts of Tomcat to make sure the JVM executing Tomcat runs with the "spring-agent" java agent.
Add the following to "%CATALINA_HOME%\bin\catalina.bat" in section "Execute The Requested Command" after all the echos:
if exist "%CATALINA_HOME%\lib\spring-agent-2.5.6.SEC02.jar" set JAVA_OPTS=%JAVA_OPTS% -javaagent:%CATALINA_HOME%\lib\spring-agent-2.5.6.SEC02.jar
When Tomcat is started, the script will now check if "spring-agent-2.5.6.SEC02.jar" is in the lib dir, and if this is the case, it will add it as the java agent to the JAVA_OPTS that are used as command line parameters to the JVM when starting Tomcat.
To enable the "TomcatInstrumentableClassLoader" as class loader for our web application we add the file "web-app\META-INF\context.xml" to our grails project, with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>
Please note that the tags in "context.xml" are case sensitive. So do not try to change <Context> to <context> (or similar things) - this will fail!
Before we package and deploy our web application, we define which datasource to use for persistency. For testing, we just define a HSQLDB in-memeory database.
Edit "grails-app\conf\DataSource.groovy" as follows:
[...]
development {
dataSource {
dbCreate = "create-drop"
url = "jdbc:hsqldb:mem:testDB"
}
}
test {
dataSource {
dbCreate = "create-drop"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "create-drop"
url = "jdbc:hsqldb:mem:testDB"
}
}
[...]
Now we are ready to create a WAR archive of our web app using:
grails war
The WAR will be placed in the "target" directory of our project.
In "%CATALINA_HOME%\webapps"
- create a directory "GrailsJPA"
- extract all contents of the created WAR archive into this directory
Please note: Do not place WAR in "%CATALINA_HOME%\webapps" and hope that it is deployed on startup of Tomcat. This will fail!
Now start the Tomcat server using script
"%CATALINA_HOME%\bin\startup.bat"
After Tomcat is up and running, use your browser to browse to
http://localhost:8080/GrailsJPA
Now click on controller "PersonController" to create, update, list and delete Person entities via JPA using EclipseLink JPA provider.
But using domain classes that are inside of a package will still fail with the exception mentioned in the above post. This issue is still unresolved.
Download links
Grails 1.3.7
http://grails.org/Download
EclipseLink 2.3.0
http://www.eclipse.org/eclipselink/downloads/2.3.0
Tomcat 6
http://tomcat.apache.org/download-60.cgi
spring-instrument-tomcat-3.0.5.RELEASE.jar
http://mvnrepository.com/artifact/org.springframework/spring-instrument-tomcat
spring-agent-2.5.6.SEC02.jar
http://mvnrepository.com/artifact/org.springframework/spring-agent