Integrating Spring 3.1 and JPA 2.0

The purpose of this article is to provide some insights into Spring and JPA integration, how it is implemented within Lexaden products portfolio.

Spring is the most popular application development framework for enterprise Java™. Millions of developers use Spring to create high performing, easily testable, reusable code without any lock-in. So does Lexaden!

Feel free to get acquainted with pros and cons of Spring @Configuration annotation in a separate topic

We have incorporated dynamic proxies in spring configuration - and now JPA query interfaces can be used as ordinary Spring beans. They can be injected (or autowired) into any other beans the same way. Let's have a deeper look at how Spring and JPA could be used in java application:

Spring configuration with dynamic proxies

Dynamic Proxies

Dynamic proxies are used to create dynamic interface implementations at runtime. It can be done by means of class java.lang.reflect.Proxy that's why they are called dynamic proxies.

Dynamic proxies can be used for many different purposes, e.g. database connection and transaction management, dynamic mock objects for unit testing, and other AOP-like method intercepting purposes.

In our case we use dynamic proxies as a nice way to create and execute JPA queries at runtime. See code snippet of OrganisationQueries interface for more details.

Creating Proxies

You create dynamic proxies using the  Proxy.newProxyInstance() method.

The newProxyInstance() methods takes 3 parameters:

  • The ClassLoader that is to "load" the dynamic proxy class.
  • An array of interfaces to implement.
  • An InvocationHandler to forward all methods calls on the proxy to.

After Spring initializes bean organisationQueries it creates proxy implementation for OrganisationQueries interface. All calls to the proxy will be forwarded to the handler implementation of the general InvocationHandler interface. 

InvocationHandler

All method calls to the dynamic proxy are forwarded to this InvocationHandler implementation(see in the next section). Here is how the InvocationHandler interface looks like:

public interface InvocationHandler{
      Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable;
    

}

The proxy parameter passed to the invoke() method is the dynamic proxy object implementing the interface. 

The Method object passed into the invoke() method represents the method called on the interface the dynamic proxy implements. From the Method object you can get the method name, parameter types, return type, etc. 

The Object[] args array contains the parameter values passed to the proxy when the method in the interface implemented was called. 

As you can see in DomainQueriesContext class we incorporated dynamic proxies in spring configuration - and now JPA query interfaces can be used as ordinary Spring beans. They can be injected (or autowired) into any other beans the same way.


Code snippets from Lexaden Admistration

Organisation Queries Interface

public interface OrganisationQueries {     /**       * Finds organisation by role id.       */     @Query(named = "find.organisation.by.role.id")     public Organisation findOrganisation(Long roleId);     /**       * Finds all organisations.       */     @Query(named = "find.all.organisations")     public List<Organisation> findAllOrganisations(); } 

 Organisation Queries

  <entity-mappings ... version="1.0">

   	<named-query name="find.organisation.by.role.id">
        	<query><![CDATA[
                    select ar.organisation from Role ar
                     where ar.id =?1
            	]]></query>
    	</named-query>
    	<named-query name="find.all.organisations">
        	<query><![CDATA[
              	select el from Organisation el
                 left join fetch el.organisationType
            	]]></query>
    	</named-query>
	</entity-mappings>

Spring Configuration

	@Configuration
	public class DomainQueriesContext {
    	@Bean
    	public QueryInvocationHandler queryInvocationHandler() {
        	return new QueryInvocationHandler();
    	}
    	@Bean
    	public OrganisationQueries organisationQueries() {
        	return getProxy(OrganisationQueries.class);
    	}
    	private <T> T getProxy(Class<T> clazz) {
          return (T) Proxy.newProxyInstance(
           clazz.getClassLoader(), 
	   new Class[]{clazz}, queryInvocationHandler());
    	}}

  

QueryInvocationHandler

/**
 * Invocation handler to create proxies for interfaces to be able to execute JPA queries on interface method calls.
 */
public class QueryInvocationHandler implements InvocationHandler {

    @PersistenceContext
    private EntityManager em;

    /**
     * Invoked when appropriate interface @Query annotated method is called
     *
     * @param proxy  - object to proxy
     * @param method - annotated method
     * @param args   - query parameters
     * @return proxy object
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method,
                         Object[] args) throws Throwable {

        if (method.getName().equals("toString")) {
            return "QueryInvocationHandler for " + proxy.getClass().getName();
        }
        //trying to find annotated method
        Query annotation = method.getAnnotation(Query.class);
        if (annotation == null) {
            throw new QueryAnnotationException("Cannot find @Query for the method " + method.getName());
        }
        //get named query and create it
        javax.persistence.Query jpaQuery = em.createNamedQuery(annotation.named());

        //set arguments taken from annotated method
        if (args != null) {
            for (int i = 1, argsLength = args.length; i <= argsLength; i++) {
                Object arg = args[i - 1];
                if (arg instanceof Collection && ((Collection) arg).isEmpty()) {
                    jpaQuery.setParameter(i, null);
                } else {
                    jpaQuery.setParameter(i, arg);
                }
            }
        }

        final List resultList = jpaQuery.getResultList();

        final Class<?> returnType = method.getReturnType();
        //result set can contain one or several elements, check for appropriate return type of the method
        if (returnType.equals(List.class)) {
            return resultList;
        } else {
            if (CollectionUtils.isNotEmpty(resultList)) {
                if (resultList.size() != 1) {
                    //throw exception if type is not java.util.List since result set size is more than 1
                    throw new IllegalReturnTypeException("java.util.List expected as a return type");
                } else {
                    final Object result = resultList.get(0);
                    if (result == null) {
                        return null;
                    }
                    //throw ClassCastException if type in result set is incompatible with annotated method
                    if (!returnType.isAssignableFrom(result.getClass())) {
                        throw new ClassCastException("Incompatible types : '" +
                                returnType.getName() + " " +
                                method.getName() + "' and query result type: " +
                                result.getClass().getName());
                    }

                    return result;
                }
            } else {
                return null;
            }
        }
    }
}

 

Configuration

<beans xmlns="...">
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
        <property name="locations">
            <list>
                <value>classpath*:*.properties</value>
            </list>
        </property>
    </bean>
    <!-- Turn on AspectJ @Configurable support -->
    <context:spring-configured/>

    <context:annotation-config />

    <context:component-scan base-package="com.lexaden.platform"/>

    <!-- Turn on @Autowired, @PostConstruct etc support -->
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>

    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <property name="driverClass" value="${database.driverClassName}"/>
        <property name="jdbcUrl" value="${database.url}"/>
        <property name="user" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
        <property name="minPoolSize"><value>${c3p0.minPoolSize}</value></property>
        <property name="maxPoolSize"><value>${c3p0.maxPoolSize}</value></property>
        <property name="checkoutTimeout"><value>20000</value></property><!-- Give up waiting for a connection after this many milliseconds -->
        <property name="maxIdleTime"><value>${c3p0.maxIdleTime}</value></property>
        <property name="idleConnectionTestPeriod"><value>${c3p0.idleConnectionTestPeriod}</value></property>
        <property name="automaticTestTable"><value>${c3p0.automaticTestTable}</value></property>
    </bean>

    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="get*" read-only="true" propagation="SUPPORTS"/>

            <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="serviceFacade" expression="execution(* *..service.*Service*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="serviceFacade"/>
    </aop:config>

    <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.showsql}</prop>
                <prop key="hibernate.jdbc.use_scrollable_resultset">${hibernate.scrollresult}</prop>
                <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
                <prop key="hibernate.generate_statistics">${hibernate.generate.statistics}</prop>
                <prop key="hibernate.max_fetch_depth">${hibernate.fetchdepth}</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
                <prop key="hibernate.cache.use_query_cache">false</prop>

                <prop key="hibernate.jdbc.batch_size">20</prop>
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
            </props>
        </property>
        <property name="jpaVendorAdapter">
            <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true"/>
                <property name="showSql" value="true"/>
            </bean>
        </property>
    </bean>
</beans>

Lexaden
Easy to keep focus on business needs