Using EclipseLink on the SpringSource Application Platform

Rob Harrop






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.

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.

To build the EclipseLink version of Petclinic, simply open a terminal window in the Petclinic root directory and run:

cd org.springframework.petclinic.eclipselink
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/.

To run the Petclinic EclipseLink application, copy all the provided bundles to the S2AP repository directory:

cp target/par-provided/bundles/*.jar $PLATFORM_HOME/repository/bundles/usr

And then copy the PAR file to the S2AP pickup directory:

cp target/artifacts/org.springframework.petclinic.eclipselink.par $PLATFORM_HOME/pickup

Once that is done, you can start the HSQLDB database needed by the Petclinic application:

cd $PETCLINIC_ROOT
cd db/hsqldb
chmod +x server.sh
./server.sh

Now start the S2AP instance:

$PLATFORM_HOME/bin/startup.sh

When this is complete you should see log messages like this:

[2008-07-11 10:03:24.560] fs-watcher Creating web application '/petclinic'.
[2008-07-11 10:03:27.419] async-delivery-thread-1 Starting web application '/petclinic'.
[2008-07-11 10:03:27.904] fs-watcher Deployment of 'org.springframework.petclinic.eclipselink' version '1.2.0.BUILD-20080711085448' completed.

To try the application out, simply hit http://localhost:8080/petclinic:

Petclinic Home Page

The Petclinic EclipseLink application is made up of six bundles:

Module Layout

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:

<reference id="clinic" interface="org.springframework.petclinic.repository.Clinic"/>

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:

@Repository
@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:

<bean id="clinic" class="org.springframework.petclinic.repository.jpa.EntityManagerClinic"/>

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:

<!– JPA EntityManagerFactory –>
<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:

<context:annotation-config/>

Transactions are enabled using the JpaTransactionManager and annotation-driven configuration:

<!– Transaction manager for a single JPA EntityManagerFactory (alternative to JTA) –>
<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:

<context:load-time-weaver aspectj-weaving="on"/>

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:

<service id="osgiClinic"
         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.

The configuration for the JpaVendorAdapter is contained in the repository.jpa.eclipselink bundle:

<context:property-placeholder location="classpath:META-INF/spring/jpa.properties,classpath:org/springframework/petclinic/infrastructure/db.properties" />

<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:

<service ref="jpaVendorAdapter" interface="org.springframework.orm.jpa.JpaVendorAdapter" />;

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:

Manifest-Version: 1.0
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:

Manifest-Version: 1.0
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.


 

5 responses


  1. Many thanks Rob for this helpful entry … funny thing is that I was going to test EclipseLink with S2AP before stumbling into this article :D
    Cheers,
    Jawher.


  2. Hi 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.


  3. It 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.


  4. Matt,

    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


  5. Hi 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.

Leave a Reply