前言:正如我在blog: Why we are using Spring framework?里面提到的那样,Spring framework通过其aop框架为我们提供了一个容器事务处理的实现,通过使用该容器事务处理,我们可以获得一个轻型的容器事务处理,下面我们通过例子我们来说明这些实现。

模型:
       现在我们来创建一个网上购物系统,在该系统中用户购物与发送数据单,同时系统还可以记录用户的行为。现在为了简化这个系统,我们只需要三个商业模型,LineItem,OrderList和AuditObject,如下:
     
     这里,这三个模型对应着两个数据库,一个数据库appfuse1用于管理OrderList和LineItem等用户订单数据,而数据库appfuse2则用于管理AuditObject数据,用于记录用户执行的动作。
     现在我们为了测试Spring的事务处理,我们假设一个订单最多只能有两种物品,当数目超出2的时候,会提示需要重新申请一个订单,并回滚,但是相应的记录信息在AuditObject并不回滚,也记录这些信息。
     我们的实现是使用容器来管理事务,通过xml文件配置,以下是类之间的相关关系:


具体例子:
     (1)开发测试环境:eclipse3.1(支持jdk1.5和JUnit),hibernate3(支持annotation),spring2.0m4,hsql,具体的这些配置请参考用户手册等。
     (2)相关知识:
         ejb3,hibernate3,spring,hsql
     (3)数据库服务器配置:
          根据我在blog: 流行开源数据库hsql第四点里面所说的那样,以下是server.properties的配置:
                server.database.0=db/appfuse1
                server.dbname.0=appfuse1
                server.database.1=db/appfuse2
                server.dbname.1=appfuse2
     (4)具体实现:
1.商业model
模型LineItem.java:
// $Id: LineItem.java 2006-5-6 2:01:17 $
package springh3.transaction.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
@Entity
@Table(name = "app_lineitems")
@NamedQuery(
        name = "springh3.transaction.model.lineItemByOrder",
        query = "from LineItem as item where item.orderList.id=?")
public class LineItem implements Serializable {
    static final long serialVersionUID = -4103866707817178530L;

    private Long id;

    private String productId;

    private int numberOfItems;

    private OrderList orderList;

    @Id
    @Column(name="lineitem_id")
    @GeneratedValue(generator = "sys-increment")
    @GenericGenerator(name = "sys-increment", strategy = "increment")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getNumberOfItems() {
        return numberOfItems;
    }

    public void setNumberOfItems(int numberOfItems) {
        this.numberOfItems = numberOfItems;
    }

    @ManyToOne
    @JoinColumn(name = "order_id")
    public OrderList getOrderList() {
        return orderList;
    }

    public void setOrderList(OrderList orderList) {
        this.orderList = orderList;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    @Override
    public String toString() {
        return this.productId+"("+this.numberOfItems+")";
    }
}
模型OrderList.java:
// $Id: OrderList.java 2006-5-6 2:15:09 $
package springh3.transaction.model;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
@Entity
@Table(name="app_orderlist")
@NamedQuery(
        name="springh3.transaction.model.orderByOrderId",
        query="from OrderList")
public class OrderList implements Serializable{
    static final long serialVersionUID = -2442553988169224982L;
   
    private Long id;
    private Set<LineItem> lineItems;
   
    @Id
    @Column(name="order_id")
    @GeneratedValue(generator="sys-increment")
    @GenericGenerator(name="sys-increment",strategy="increment")
    public Long getId() {
        return id;
    }
   
    public void setId(Long id) {
        this.id = id;
    }
   
    @OneToMany(cascade=CascadeType.ALL,mappedBy="orderList")
    public Set<LineItem> getLineItems() {
        return lineItems;
    }
   
    public void setLineItems(Set<LineItem> lineItems) {
        this.lineItems = lineItems;
    }
   
   
}
模型AuditObject.java:
// $Id: AuditObject.java 2006-5-6 2:24:57 $
package springh3.transaction.model;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
@Entity
@Table(name="app_auditobject")
public class AuditObject implements Serializable{
    static final long serialVersionUID = -3766298732085358604L;
   
    private Long id;
    private String who;
    private Date when;
    private String resource;
    private String action;
   
    public AuditObject(){
    }
   
    public AuditObject(String resource,String action){
        this.resource=resource;
        this.action=action;
        this.when=new Date();
        this.who=System.getProperties().getProperty("user.name");
    }
   
    public String getAction() {
        return action;
    }
    public void setAction(String action) {
        this.action = action;
    }
   
    @Id
    @Column(name="audit_id")
    @GeneratedValue(generator="sys-increment")
    @GenericGenerator(name="sys-increment",strategy="increment")
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getResource() {
        return resource;
    }
    public void setResource(String resource) {
        this.resource = resource;
    }
    public Date getWhen() {
        return when;
    }
    public void setWhen(Date when) {
        this.when = when;
    }
    public String getWho() {
        return who;
    }
    public void setWho(String who) {
        this.who = who;
    }
   
}
2.DAO接口,(具体实现请下载源代码,连接在最后):
OrderListDAO.java接口:
// $Id: OrderListDAO.java 2006-5-6 3:22:53 $
package springh3.transaction.dao;

import java.util.List;

import springh3.transaction.model.LineItem;
import springh3.transaction.model.OrderList;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
public interface OrderListDAO {
    List getAllOrderList();
    OrderList getOrderList(Long id);
    Long createOrderList(OrderList orderList);
   
    Long addLineItem(Long orderId,LineItem lineItem);
    List getAllLineItems(Long orderId);
    int queryNumberOfLineItems(Long orderId);
}
AuditObjectDAO接口:
// $Id: AuditObjectDAO.java 2006-5-6 3:29:52 $
package springh3.transaction.dao;

import springh3.transaction.model.AuditObject;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
public interface AuditObjectDAO {
    void log(AuditObject auditObject);
}
3.Transaction Manager接口(具体实现在源代码中):
OrderListManager.java接口:
// $Id: OrderListManager.java 2006-5-6 3:13:28 $
package springh3.transaction.service;

import java.util.List;

import springh3.transaction.exception.FacadeException;
import springh3.transaction.model.LineItem;
import springh3.transaction.model.OrderList;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
public interface OrderListManager {
    List getAllOrderList();
    OrderList getOrderList(Long id);
    Long createOrderList(OrderList orderList);
   
    void addLineItem(Long orderId,LineItem lineItem) throws FacadeException;
    List getAllLineItems(Long orderId);
    int queryNumberOfLineItems(Long orderId);
}
AuditManager.java接口:
// $Id: AuditManager.java 2006-5-6 3:20:47 $
package springh3.transaction.service;

import springh3.transaction.model.AuditObject;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
public interface AuditManager {
    void log(AuditObject auditObject);
}

4.spring的applicationContext.xml文件:
<?xml version="1.0"?>
<!DOCTYPE beans
    PUBLIC "-//SPRING//DTD BEAN//EN"
        "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <!-- JDBC Data Source -->
    <bean id="dataSource1"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url"
            value="jdbc:hsqldb:hsql://localhost/appfuse1" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

    <bean id="dataSource2"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url"
            value="jdbc:hsqldb:hsql://localhost/appfuse2" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

    <!-- Hibernate SessionFactory -->
    <bean id="sessionFactory1"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource1" />
        <property name="annotatedClasses">
            <list>
                <value>springh3.transaction.model.LineItem</value>
                <value>springh3.transaction.model.OrderList</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.HSQLDialect
                </prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
            </props>
        </property>
    </bean>

    <bean id="sessionFactory2"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource2" />
        <property name="annotatedClasses">
            <list>
                <value>springh3.transaction.model.AuditObject</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.HSQLDialect
                </prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
            </props>
        </property>
    </bean>

    <!--  Transaction manager for a single Hibernate SessionFactory (alternative to JTA)-->
    <bean id="transactionManager1"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory1" />
    </bean>

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

    <!-- DAO Beans -->
    <bean id="auditObjectDAO"
        class="springh3.transaction.dao.hibernate.AuditObjectDAOHibernate">
        <property name="sessionFactory" ref="sessionFactory2" />
    </bean>

    <bean id="orderListDAO"
        class="springh3.transaction.dao.hibernate.OrderListDAOHibernate">
        <property name="sessionFactory" ref="sessionFactory1" />
    </bean>

    <!-- Manager -->
    <bean id="auditManagerTarget"
        class="springh3.transaction.service.impl.AuditManagerImpl">
        <property name="auditObjectDAO" ref="auditObjectDAO" />
    </bean>

    <bean id="auditManager"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="transactionManager2" />
        <property name="target" ref="auditManagerTarget" />
        <property name="transactionAttributes">
            <props>
                <prop key="log">PROPAGATION_REQUIRES_NEW</prop>
            </props>
        </property>
    </bean>

    <bean id="orderListManagerTarget"
        class="springh3.transaction.service.impl.OrderListManagerImpl">
        <property name="orderListDAO" ref="orderListDAO" />
        <property name="auditManager" ref="auditManager" />
    </bean>

    <bean id="orderListManager"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="transactionManager1" />
        <property name="target" ref="orderListManagerTarget" />
        <property name="transactionAttributes">
            <props>
                <prop key="addLineItem">
                    PROPAGATION_REQUIRED,-springh3.transaction.exception.FacadeException
                </prop>
                <prop key="createOrderList">PROPAGATION_REQUIRED</prop>
                <prop key="getAllLineItems">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="getAllOrderList">PROPAGATION_REQUIRED</prop>
                <prop key="getOrderList">PROPAGATION_REQUIRED</prop>
                <prop key="queryNumberOfLineItems">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>
</beans>
5.测试代码OrderListManagerTest.java:
// $Id: OrderListManagerTest.java 2006-5-6 12:46:13 $
package springh3.transaction.test;

import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import springh3.transaction.exception.FacadeException;
import springh3.transaction.model.LineItem;
import springh3.transaction.model.OrderList;
import springh3.transaction.service.OrderListManager;

import junit.framework.TestCase;

/**
 * @author Sidney J.Yellow(Sidney.J.Yellow@gmail.com)
 */
public class OrderListManagerTest extends TestCase {
    private static Log log = LogFactory.getLog(OrderListManagerTest.class);

    private ApplicationContext ctx;

    private OrderListManager orderListManager;

    @Override
    protected void setUp() throws Exception {
        String[] paths = { "springh3/transaction/test/applicationContext.xml" };
        ctx = new ClassPathXmlApplicationContext(paths);
        orderListManager = (OrderListManager) ctx.getBean("orderListManager");
    }

    @Override
    protected void tearDown() throws Exception {
        orderListManager = null;
    }

    public void testCreateOrder() throws Exception {
        LineItem lineItem1 = new LineItem();
        lineItem1.setProductId("Professional Hibernate");
        lineItem1.setNumberOfItems(1);

        LineItem lineItem2 = new LineItem();
        lineItem2.setProductId("Spring Live");
        lineItem2.setNumberOfItems(3);

        OrderList orderList1 = new OrderList();
        Long orderId1 = orderListManager.createOrderList(orderList1);
        log("Created OrderList with id '" + orderId1 + "'...");

        orderListManager.addLineItem(orderId1, lineItem1);
        orderListManager.addLineItem(orderId1, lineItem2);

        int numberOfLineItems1 = orderListManager
                .queryNumberOfLineItems(orderId1);
        log("Number of LineItems for OrderList with id '" + orderId1 + "' is: "
                + numberOfLineItems1);
        List lineItemList1 = orderListManager.getAllLineItems(orderId1);

        LineItem lineItemFound1 = null;
        if ((lineItemList1 != null) && (lineItemList1.size() != 0)) {
            log("/nLine items for Order " + orderId1 + " are:");
        }

        for (Iterator iter = lineItemList1.iterator(); iter.hasNext();) {
            lineItemFound1 = (LineItem) iter.next();
            log(lineItemFound1);
        }

//         Test addLineItem(),
        // As we know, when numberOfLineItems>2, we will have an exception, but
        // still want to proceed
        LineItem lineItem3 = new LineItem();
        lineItem3.setProductId("Professional .NET");
        lineItem3.setNumberOfItems(3);

        LineItem lineItem4 = new LineItem();
        lineItem4.setProductId("Architecture Bootcamp");
        lineItem4.setNumberOfItems(2);

        LineItem lineItem5 = new LineItem();
        lineItem5.setProductId(".NET Remoting");
        lineItem5.setNumberOfItems(4);

        OrderList orderList2 = new OrderList();
        Long orderId2 = orderListManager.createOrderList(orderList2);
        log("Created OrderList with id '" + orderId2 + "'...");

        orderListManager.addLineItem(orderId2, lineItem3);
        orderListManager.addLineItem(orderId2, lineItem4);
       
        //
        try{
            orderListManager.addLineItem(orderId2,lineItem5);
        }catch(FacadeException fe){
            log("ERROR: "+fe.getMessage());
        }
       
        int numberOfLineItems2=orderListManager.queryNumberOfLineItems(orderId2);
        log("Number of LineItems for OrderList with id '"+orderId2+"' is: "+numberOfLineItems2);
        List lineItemList2 = orderListManager.getAllLineItems(orderId2);

        LineItem lineItemFound2 = null;
        if ((lineItemList2 != null) && (lineItemList2.size() != 0)) {
            log("/nLine items for Order " + orderId2 + " are:");
        }

        for (Iterator iter = lineItemList2.iterator(); iter.hasNext();) {
            lineItemFound2 = (LineItem) iter.next();
            log(lineItemFound2);
        }

    }

    private static final void log(Object message) {
        System.out.println(message.toString());
    }
}
    (5)测试:
     运行数据服务器,然后在eclipse中通过JUnit运行测试代码,测试成功:
     ...
     2006-5-6 14:47:51 springh3.transaction.service.impl.OrderListManagerImpl addLineItem
     信息: Added LineItem 5 to Order 2;But rolling back ***!
     ...

小结:
   
从上面可以看出,通过spring,我们可以轻松的进行编码实现我们的数据库事务处理,同时还不用部署在web服务器中就可以完成测试,如此轻松简单的功能确实是我们选择spring作为轻型底层框架的原因。

资源下载:
    (1) 本文的源代码(附带hsql数据库)
    (2) spring官方网址
    (3) hibernate官方网址
    (4) eclipse官方网址



感谢你的阅读!
对这篇文章有什么疑问的话,请联系作者。作者联系地址: Sidney.J.Yellow@gmail.com
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐