Using EclipseLink on the SpringSource Application Platform

This week the EclipseLink team announced the release of EclipseLink 1.0. I've been using EclipseLink on S2AP for a while now; in fact, I used EclipseLink when developing our JPA load-time-weaving support.
We've yet to upgrade our internal usage to 1.0 - our beta9 was tagged just before the announcement - but I wanted to demonstrate how effectively the pairing works in an OSGi environment.
EclipseLink in Petclinic
In the 1.2.0 version of the S2AP Petclinic sample, we released the EclipseLink implementation of the Clinic back-end. The back-end is a drop-in replacement for the JDBC back-end that was previously the only option.
Building Petclinic EclipseLink
To build the EclipseLink version of Petclinic, simply open a terminal window in the Petclinic root directory and run:
ant collect-provided jar
This will create the Petclinic EclipseLink PAR file in org.springframework.petclinic.eclipselink/target/artifacts/org.springframework.petclinic.eclipselink.par and will put all the required bundles in org.springframework.petclinic.eclipselink/target/par-provided/bundles/.
Running Petclinic EclipseLink
To run the Petclinic EclipseLink application, copy all the provided bundles to the S2AP repository directory:
And then copy the PAR file to the S2AP pickup directory:
Once that is done, you can start the HSQLDB database needed by the Petclinic application:
cd db/hsqldb
chmod +x server.sh
./server.sh
Now start the S2AP instance:
When this is complete you should see log messages like this:
[2008-07-11 10:03:27.419] async-delivery-thread-1
[2008-07-11 10:03:27.904] fs-watcher
Testing Petclinic EclipseLink
To try the application out, simply hit http://localhost:8080/petclinic:
Modularity in Petclinic EclipseLink
The Petclinic EclipseLink application is made up of six bundles:
The domain, web, repository and infrastructure bundles are common across all versions of the application. These bundles have the following roles:
- domain - contains the persistent types such as Pet and Vet
- repository - contains the main repository interface Clinic
- web - contains all the web front-end classes and configuration
- infrastructure - defines the DataSource to connect to HSQLDB and exports this as a service
Note that the web bundle only depends on the domain and repository bundles at the module level. This means that web only needs types from those two bundles. In the case of Clinic which is an interface, the web bundle gets its implementation from OSGi:
Basic JPA Implementation and Configuration
The implementation of the Clinic interface is provided by the repository.jpa bundle. It is worthwhile looking at the implementation and configuration of this bundle in some detail. The EntityManagerClinic class implements Clinic using JPA:
@Transactional
public class EntityManagerClinic implements Clinic {
@PersistenceContext
private EntityManager em;
@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
public Collection<Vet> getVets() {
return this.em.createQuery("SELECT vet FROM Vet vet ORDER BY vet.lastName, vet.firstName").getResultList();
}
@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
public Collection<PetType> getPetTypes() {
return this.em.createQuery("SELECT ptype FROM PetType ptype ORDER BY ptype.name").getResultList();
}
@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
public Collection<Owner> findOwners(String lastName) {
Query query = this.em.createQuery("SELECT owner FROM Owner owner WHERE owner.lastName LIKE :lastName");
query.setParameter("lastName", lastName + "%");
return query.getResultList();
}
@Transactional(readOnly = true)
public Owner loadOwner(int id) {
return this.em.find(Owner.class, id);
}
@Transactional(readOnly = true)
public Pet loadPet(int id) {
return this.em.find(Pet.class, id);
}
public void storeOwner(Owner owner) {
// Consider returning the persistent object here, for exposing
// a newly assigned id using any persistence provider…
Owner merged = this.em.merge(owner);
this.em.flush();
owner.setId(merged.getId());
}
public void storePet(Pet pet) {
// Consider returning the persistent object here, for exposing
// a newly assigned id using any persistence provider…
Pet merged = this.em.merge(pet);
this.em.flush();
pet.setId(merged.getId());
}
public void storeVisit(Visit visit) {
// Consider returning the persistent object here, for exposing
// a newly assigned id using any persistence provider…
Visit merged = this.em.merge(visit);
this.em.flush();
visit.setId(merged.getId());
}
}
This class depends on a standard JPA EntityManager which is annotated for injection using @PersistenceContext. The transaction configuration is defined using Spring's @Transactional annotation. The configuration for the EntityManagerClinic is contained in module-context.xml:
The EntityManagerClinic needs to be injected with an EntityManager. Spring's JPA support provides the LocalContainerEntityManagerFactoryBean which provides access to a shared EntityManager:
To enable Spring transaction management, the configuration defines a JpaTransactionManager:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource">
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
</bean>
The JpaVendorAdapter defines which provider you are using, along with the provider-specific configuration. Rather than define the JpaVendorAdapter in-line, Petclinic uses an OSGi service to get the JpaVendorAdapter from another bundle. This allows for different JPA providers to be added without having to change the core repository.jpa bundle.
Enabling Spring's annotation-driven configuration will allow for the configured EntityManager to be injected into the EntityManagerClinic:
Transactions are enabled using the JpaTransactionManager and annotation-driven configuration:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
<tx:annotation-driven mode="aspectj"/>
Notice that the configuration defines the transaction mode as aspectj. In this mode, AspectJ load-time weaving is used to apply the transaction behaviour. This requires that Spring's load-time weaving support be enabled and configured for AspectJ weaving:
The LoadTimeWeaver is also used to support the weaving needed by EclipseLink when it transforms the persistent types. No special configuration is needed to use the S2AP LoadTimeWeaver; this will be added when the application is deployed.
The osgi-context.xml configuration pulls in the necessary services and exports the EntityManagerClinic:
ref="clinic"
interface="org.springframework.petclinic.repository.Clinic" />
<reference id="dataSource" interface="javax.sql.DataSource"/>
<reference id="jpaVendorAdapter" interface="org.springframework.orm.jpa.JpaVendorAdapter"/>
Notice that the JpaVendorAdapter is being referenced as an OSGi service - this allows the repository.jpa.eclipselink bundle to provide the exact JpaVendorAdapter needed to run with EclipseLink.
EclipseLink-specific configuration
The configuration for the JpaVendorAdapter is contained in the repository.jpa.eclipselink bundle:
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"
p:databasePlatform="${jpa.eclipselink.databasePlatform}" p:showSql="${jpa.showSql}"/>;
The jpaVendorAdapter bean is then exported as an OSGi service:
Import Scopes
To enable the Clinic implementation to be switched without having to make changes to the four core bundles, it is important that any changes are contained within the implementation bundles only. Unfortunately, this is not possible in standard OSGi.
When EclipseLink weaves a type, it adds dependencies on its own types to that type. For instance, weaving Vet will cause Vet and thus the domain bundle to depend on EclipseLink. One way of solving this is simply to add the appropriate Import-Package or Import-Bundle statements to the domain bundle. This is undesirable because it breaks the modularity - the domain bundle shouldn't care about how it is persisted.
This problem exists wherever bytecode transformation is used, and to a lesser degree where runtime class generation is used. For example, when using Hibernate, CGLIB is used to generate subclasses of persistent types. For the most part this is fine, except when some other code starts to reflect on objects of this dynamically generated type. This is quite common in web code, where reflection is used for data binding - all of a sudden the web bundle finds it has a dependency on Hibernate!
To solve this, S2AP supports the notion of Import Scopes. When using Import-Bundle inside an application, you can define that the scope of the import is the application. This will cause the import to be added to all the other modules in the application dynamically. This allows you to get the dependencies you need to make your application run, but without having to make changes to your application that might limit its flexibility.
Configuring Import Scope
The repository.jpa bundle has no dependency on EclipseLink:
Bundle-Version: 1.0.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.springframework.petclinic.repository.jpa
Bundle-Name: PetClinic JPA-based Repository Implementation
Bundle-Vendor: SpringSource Inc.
Import-Library: org.springframework.spring;version="[2.5,2.6)"
Import-Bundle: com.springsource.javax.persistence;version="[1.0.0,1.0.0]",
com.springsource.org.aspectj.runtime;version="1.6.0",
com.springsource.org.aspectj.weaver;version="1.6.0"
Import-Package: javax.sql,
org.apache.commons.logging,
org.springframework.petclinic.domain,
org.springframework.petclinic.repository
Export-Package: org.springframework.petclinic.repository.jpa
The repository.jpa.eclipselink bundle introduces the EclipseLink dependency and defines that all the application modules should see EclipseLink:
Bundle-ManifestVersion: 2
Bundle-Version: 1.0.0
Bundle-SymbolicName: org.springframework.petclinic.repository.eclipselink
Bundle-Name: PetClinic EclipseLink-based Repository Implementation
Bundle-Vendor: SpringSource Inc.
Import-Library: org.springframework.spring;version="[2.5,2.6)"
Import-Bundle: com.springsource.org.eclipse.persistence;version="1.0.0.m5";import-scope:=application
Import-Package: org.apache.commons.logging,
org.springframework.petclinic.infrastructure
Export-Package: org.springframework.petclinic.repository.eclipselink
Summary
Using EclipseLink and JPA in the SpringSource Application Platform requires very little extra effort on your part. The Import Scope feature allows you to keep your non-EclipseLink modules completely isolated from your usage of EclipseLink.
Jawher says:
Added on July 21st, 2008 at 4:14 amMany thanks Rob for this helpful entry … funny thing is that I was going to test EclipseLink with S2AP before stumbling into this article
Cheers,
Jawher.
Matt says:
Added on August 12th, 2008 at 10:31 amHi Rob,
Thanks for the article. I'm new to spring, having an ejb background.
I've attempted to re-port your code over to maven/jetty (j2se). Although I've had no luck.
I start maven jetty:run with the following set:
set MAVEN_OPTS=-javaagent:"C:\Documents and Settings\Administrator\.m2\repository\org\springframework\spring-agent\2.5.4\spring-agent-2.5.4.jar"
However, it bombs out with exception due to the following line:
It fails stating that
2008-08-13 01:11:52.111:/:INFO: Initializing Spring root WebApplicationContext
(16 ms) [main] ERROR: org.springframework.web.context.ContextLoader#initWebApplicationContext : Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.weaving.AspectJWeavingEnabler#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loadTimeWeaver': Initialization of bean failed; nested exception is java.lang.IllegalStateException: ClassLoader [org.mortbay.jetty.webapp.WebAppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-agent.jar
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:478)
…
If i change the line to be
It gives the error
java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.
If i comment the line out entirely… I get the error
[EL Config]: 2008.08.13 01:17:38.796–ServerSession(15703940)–Thread(Thread[main,5,main])–The alias name for the entity class [class org.springframework.petclinic.domain.PetType] is being defaulted to: PetType.
[EL Config]: 2008.08.13 01:17:38.843–ServerSession(15703940)–Thread(Thread[main,5,main])–The column name for element
[private java.lang.String org.springframework.petclinic.domain.NamedEntity.name] is being defaulted to: NAME.
[EL Config]: 2008.08.13 01:17:38.843–ServerSession(15703940)–Thread(Thread[main,5,main])–The column name for element
[private java.lang.Integer org.springframework.petclinic.domain.BaseEntity.id] is being defaulted to: ID.
(15 ms) [main] ERROR: org.springframework.web.context.ContextLoader#initWebApplicationContext : Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0' defined in ServletContext resource [/WEB-INF/applicationContext-persistence.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/applicationContext-persistence.xml]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Exception
[EclipseLink-28018] (Eclipse Persistence Services - 1.0 (Build 1.0 - 20080707)): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Predeployment of PersistenceUnit [PetClinic] failed.
Internal Exception: Exception [EclipseLink-7161] (Eclipse Persistence Services - 1.0 (Build 1.0 - 20080707)): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Entity class [class org.springframework.petclinic.domain.PetType] has no primary key specified. It
should define either an @Id, @EmbeddedId or an @IdClass. If you have defined PK using any of these annotations then please make sure that you do not have mixed access-type (both fields and properties annotated) in your entity class hierarchy.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:478)
Thus I'm stumped!
In your particular 1.2.0 petclinic code sample, how is aspectj actually leveraged?
Is some type of transformation supposed to take place so that the @MappedSuperclass
public class BaseEntity / NamedEntity.java properties automatically get pushed to the subclass entities?
Is there any reason your code should not run from a standard jvm (via jetty)?
At this stage, all i'm trying to do is get the domain and repository objects to function.
The only change I have made is to BaseEntity to leverage a sequence generator (seeing that I'm running on an Oracle database):
@Id
@GeneratedValue(generator="IdSeq")
@SequenceGenerator(name="IdSeq",sequenceName="ID_SEQ", initialValue=1000)
My applicationContext-persistence.xml looks like …
–>
Oracle
none
FINE
true
true
true
true
INFO
Oracle-JDBC
My persistence.xml looks like ….
org.springframework.petclinic.domain.BaseEntity
org.springframework.petclinic.domain.NamedEntity
org.springframework.petclinic.domain.Owner
org.springframework.petclinic.domain.Person
org.springframework.petclinic.domain.Pet
org.springframework.petclinic.domain.PetType
org.springframework.petclinic.domain.Specialty
org.springframework.petclinic.domain.Vet
org.springframework.petclinic.domain.Visit
true
My persistence.properties file looks like …
jpa.eclipselink.databasePlatform=org.eclipse.persistence.platform.database.oracle.OraclePlatform
jpa.showSql=true
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=petclinic
jdbc.password=petclinic
cheers
Matt.
Matt says:
Added on August 12th, 2008 at 11:30 amIt looks like the xml from above got garbled.
I've upload my petclinic cutdown version based on Jetty to the following location
http://rapidshare.com/files/136814768/petclinic_oracle.zip
rapidshare.com/files/136814768/petclinic_oracle.zip
It's only 33k.
Rob/anyone skilled with spring/eclipselink - any chance you could take a quick look.
All it requires is maven2, all dependencies will load automatically.
there is a readme file as well inside zip.
I would be very greatful for any help.
cheers
Matt.
Rob Harrop says:
Added on August 13th, 2008 at 3:57 amMatt,
You look to be doing the right with respect to trying to configure the Java agent. From the error message though it looks pretty clear that Spring can't detect the agent. One issue here could be classloading - make sure that you don't have two versions of the agent classes on the classpath - they should be on the boot class path only, if they are packaged in your WAR that might cause problems.
Rob
Matt says:
Added on August 13th, 2008 at 11:11 amHi Rob,
You're an absolute champion!
I had bundled the spring-agent-2.5.4.jar with the WAR - and were also starting the maven/jetty pointing to a copy of the same jar.
I removed the agent from the WAR, and now everything works perfectly!
thankyou so much. One thing I did find with your sample, is that it won't work with spring 2.5.5, but does on 2.5.4. This is due to an error introduced with a fix for http://jira.springframework.org/browse/SPR-4524
cheers
Matt.