Spring, Hibernate, and XFire

Spring, Hibernate, and XFire

(Reposted from my original article on http://www.yawf.com)

 

 

Introduction

A bit of back story:

I had to tie in web services to a hibernate-based backend for the upcoming release of FireBright's SaaS on Demand platform. The problem: Doing it in such a way that:

  • doesn't tie me down to a particular platform.
  • Offers me maximum flexibility in terms of features
  • Doesn't bog down performance.

Web services are quite interesting beasts. Not something to be taken lightly, if you consider the implementation that Axis2 provides. That being said, there is another way, privided by the friendly folks at XFire (which, incidentally, is where I found out about FishEye?). But, there seems to be a bit of a disconnect.. I've got objects persisted by Hibernate, and those same objects need to be rendered as XML Data, and with over 50 entity types and relationships, I really don't want to have to write populators for everything.

Again, introduce XFire. Why? Because it makes this stuff simple. WSDL Generation, auto-typing using aegis or JAXB (PERFECT!), and it even uses jws annotations, so it limits the amount of implementation specific code in my own code. So how this is done.

 

Configuring XFire

 

Adding XFireServlet to web.xml

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-service.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>XFireServlet</servlet-name>
        <display-name>XFire Servlet</display-name>
        <servlet-class>org.codehaus.xfire.spring.XFireSpringServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>XFireServlet</servlet-name>
        <url-pattern>/servlet/XFireServlet/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>XFireServlet</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>
</web-app>

 

Configuring spring-service.xml to include basic XFire properties

Next, let's start building out the XFire side of the spring config. This allows XFire to talk to service beans configured through spring, and allows it to read Jsr181 annotations (javax.jws).

 

<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <import resource="classpath:org/codehaus/xfire/spring/xfire.xml"/>
    <bean id="jaxbTypeMappingRegistry" class="org.codehaus.xfire.jaxb2.JaxbTypeRegistry" 
              init-method="createDefaultMappings" singleton="true"/>
    <bean id="webAnnotations" class="org.codehaus.xfire.annotations.jsr181.Jsr181WebAnnotations"/>
    <bean id="handlerMapping" class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping">
        <property name="typeMappingRegistry">
            <ref bean="jaxbTypeMappingRegistry"/>
        </property>
        <property name="xfire">
            <ref bean="xfire"/>
        </property>
        <property name="webAnnotations">
            <ref bean="webAnnotations"/>
        </property>
    </bean>
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="urlMap">
            <map>
                <entry key="/">
                    <ref bean="handlerMapping"/>
                </entry>
            </map>
        </property>
    </bean>
</beans>

 

Configuring service beans.

Next, we add a spring bean for each of our services. In this case, I have an AOP proxy that checks authentication before the method gets executed, so I will wrap each service bean with a ProxyFactoryBean:

    <bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target">
            <bean class="com.firebright.service.impl.EchoDatabaseTestServiceImpl"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>authSessionAdvisor</value>
            </list>
        </property>
    </bean>

 

Java code for EchoDatabaseTestServiceImpl

Looking at EchoDatabaseTestServiceImpl:

 

package com.firebright.service.impl;

import com.firebright.common.persistence.manager.IdentifiableManager;
import com.firebright.service.EchoDatabaseTestService;
import com.firebright.service.support.entity.Foo;
import javax.jws.WebService;

@WebService(name="EchoDatabaseTestService", endpointInterface = "com.firebright.service.EchoDatabaseTestService")
public class EchoDatabaseTestServiceImpl implements EchoDatabaseTestService
{
    private IdentifiableManager manager = null;
    public void setManager(IdentifiableManager manager)
    {
        this.manager = manager;
    }
    public Foo getFoo(Long id)
    {
        return (Foo) manager.getOne(id);
    }
}

 

What's happening in java code?

Now, what's happening is this:
  • The @WebService annotation is a jws annotation.
  • The 'name' attribute of this annotation becomes the last part of the URL when making the request, e.g. http://service.url.com/services/EchoDatabaseTestService .
  • The endpointInterface attribute specifies the java interface to look for, and uses that to generate the soap endpoint WSDL.

 

Configuring the Interface for EchoDatabaseTestServiceImpl

The interface is where we will put all of our annotations to describe what is actually available in our SOAP API:

 

package com.firebright.service;

import com.firebright.service.support.entity.Foo;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService(name="EchoDatabaseTestService", targetNamespace = "http://service.firebright.com")
public interface UserService
{
    @WebMethod
    @WebResult(name="Foo")
    public Foo getFoo(@WebParam(name="id") Long id);
}

And that's it for the XFire side of things. XFire will automatically enumerate each of your beans, grab their annotations, and generate a wsdl and complex types for, in this case, our "Foo" object (which we'll get to later in this article).

However, we will have an issue, since when we make the request, the Foo object shows up in the namespace "entity.support.service.firebright.com" which is a bit silly, so let's set it to the proper namespace:

 

package com.firebright.service.support.entity;

import org.codehaus.xfire.aegis.type.java5.XmlType;

@Entity
@XmlType(name="Foo", namespace="http://service.firebright.com")
public class Foo {

   ...
}

 

Setting up Hibernate

Next, let's set up hibernate. First we'll add a filter to web.xml to bind the current hibernate session to our services:

 

    <filter>
        <filter-name>sessionFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>sessionFilter</filter-name>
        <url-pattern>/services/*</url-pattern>
    </filter-mapping>

 

Setting up spring-config.xml hibernate entries

Next, set up our spring-context.xml to include required hibernate elements, as well as configuring it to look at EJB3 annotated entity beans.

 

<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource">
            <ref bean="postgresDataSource"/>
        </property>
        <property name="hibernateProperties">
            <ref bean="hibernateProperties"/>
        </property>
        <property name="configurationClass">
            <value>org.hibernate.cfg.AnnotationConfiguration</value>
        </property>
        <property name="configLocation">
            <value>/WEB-INF/classes/hibernate.cfg.xml</value>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
    </bean>

    <bean id="transactionProxyTemplate" abstract="true"
          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="transactionManager"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
    </bean>

    <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor">
       <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
    </bean>

    <bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="properties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
                <prop key="hibernate.query.substitutions">true 'T', false 'F'</prop>
                <prop key="hibernate.show_sql">@@hibernate.show_sql@@</prop>
                <prop key="hibernate.c3p0.minPoolSize">5</prop>
                <prop key="hibernate.c3p0.maxPoolSize">20</prop>
                <prop key="hibernate.c3p0.timeout">600</prop>
                <prop key="hibernate.c3p0.max_statement">50</prop>
                <prop key="hibernate.c3p0.testConnectionOnCheckout">false</prop>
            </props>
        </property>
    </bean>

    <bean id="postgresDataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>
        </property>
        <property name="url">
            <value>@@db.connection.url@@</value>
        </property>
        <property name="username">
            <value>@@db.connection.username@@</value>
        </property>
        <property name="password">
            <value>@@db.connection.password@@</value>
        </property>
    </bean>
</beans>

 

The DAO pattern

Each of our managers will use a DAO that is configured to talk to hibernate through the hibernate session.

The DAO interface looks like this. We'll get into what Identifiable is later on.

 

package com.firebright.common.persistence.dao;

import com.firebright.common.entity.Identifiable;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import java.util.List;

public interface IdentifiableDao
{
    public void refresh(Identifiable identifiable);
    public void save(Identifiable identifiable);
    public void delete(Identifiable identifiable);
    public List<Identifiable> getAll(Class clazz);
    public List<Identifiable> getAll(Class clazz, List<Criterion> criterion);
    public List<Identifiable> getAll(Class clazz, List<Criterion> criterion, Order order);
    public Identifiable getOne(Class clazz, Long id);
    public int getCount(Class clazz, List<Criterion> criterion);
    int getCount(Class managedClass, List<Criterion> criteria, Order order);
}

And our hibernate specific dao:

 

ackage com.firebright.common.persistence.dao.impl;

import com.firebright.common.entity.Identifiable;
import com.firebright.common.persistence.dao.IdentifiableDao;
import com.firebright.common.persistence.util.PersistentObjectNotFoundException;
import org.hibernate.criterion.*;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import java.util.List;

public class HibernateIdentifiableDaoImpl extends HibernateDaoSupport implements IdentifiableDao
{
    public void refresh(Identifiable identifiable) {
        getHibernateTemplate().refresh(identifiable);
    }

    public void save(Identifiable identifiable)
    {
        getHibernateTemplate().saveOrUpdate(identifiable);
    }

    public void delete(Identifiable identifiable)
    {
        getHibernateTemplate().delete(identifiable);
    }

    public Identifiable getOne(Class clazz, Long id)
    {
        return getOne(clazz, id, Order.asc("id"));
    }

    public Identifiable getOne(Class clazz, Long id, Order order)
    {
        return (Identifiable) getHibernateTemplate().get(clazz, id);
    }

    public List<Identifiable> getAll(Class clazz)
    {
        List list = getHibernateTemplate().loadAll(clazz);
        return list;
    }

    public List<Identifiable> getAll(Class clazz, List<Criterion> criterion)
    {
        return getAll(clazz, criterion, Order.asc("id"));
    }

The spring config for the DAO:

 

    <bean id="identifiableDao" class="com.firebright.common.persistence.dao.impl.HibernateIdentifiableDaoImpl">
        <property name="hibernateTemplate">
            <ref bean="hibernateTemplate"/>
        </property>
        <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
    </bean>

 

The Identifiable interface.

This is pretty simple. Note that it, too, has the namespace defined on it.

 

package com.firebright.common.entity;

import org.codehaus.xfire.aegis.type.java5.XmlType;

@XmlType(name = "Identifiable", namespace = "http://service.firebright.com")
public interface Identifiable
{
    public Long getId();

    public void setId(Long id);
}

 

Wrapping up

So there are a few things left.
  • Swap out your connection settings (in our example, we used postgres 8.x, but this will work for mysql, etc.)
  • generate a hibernate.cfg.xml containing the names of all your annotated entity classes.
  • build the app.
  • deploy the app.

 

Coming soon

We will be releasing a "starting point" project which includes ant scripts/tasks to build hibernate.cfg.xml from annotated classes, and a basic "Hello World" application that can retrieve data from the database. Stay tuned!  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值