/**
作者:Willpower
来源:Rifoo Technology(http://www.rifoo.com)
时间:2006-04-01
备注:转载请保留以上声明
**/
继上节讨论了简单事务处理后,本节主要讨论在多个数据库上的事务处理。
如果我们要重构一个简单的应用程序,让它使用多个资源,Spring的可插拔事务策略(pluggable transaction strategies)可以帮助我们很好的节省开发时间。在本节的例子里,我们会使用JTA事务来将它扩展到多个数据库源。为了使例子的应用能够正常工作,需要它运行在一个识别JTA的J2EE容器中。
如果我们正在使用敏捷开发的方式来开发程序,那么我们将很可能会经历这样一个场景。敏捷方式建议:将问题简单化,仅仅在我们需要时再实现更复杂的特性。依赖注入让我们可以很容易的改变应用程序的资源并将它们毫不费力插到另一个指定的应用中去。
在本例中,我们将要在一个单独的数据库rentaBikeAccounts中去维护现金业务(monetary transaction)。无论何时当用户进行一次预订操作时,他们将提供预付定金,我们会以事务的形式将这个定金数量加到这个新数据库的monetaryTransactions表中。
建库和建表脚本如下:
Example 7-5. rentabike.sql
create database rentaBikeAccounts;
use rentabikeAccounts;
create table monetaryTransactions (
txId int(11) not null auto_increment,
resId int(11) not null default '0',
amount double not null default '0',
`type` varchar(50) not null,
primary key (txId))
type=InnoDB;
别忘了在MySQL中给这个新数据库建立一个授权的用户账号:
GRANT ALL PRIVILEGES ON rentaBikeAccounts.* TO 'rentaBikeAccounts'@'localhost'
WITH GRANT OPTION;
我们创建了一个叫rentaBikeAccounts的用户,他拥有对这个新数据库的完全权限。
下面,我们创建一个持久的domain class:
Example 7-6. MonetaryTransaction.java
public class MonetaryTransaction {
private int txId;
private int resId;
private double amount;
public int getTxId( ) {
return txId;
}
public void setTxId(int txId) {
this.txId = txId;
}
public int getResId( ) {
return custId;
}
public void setResId(int resId) {
this.resId = resId;
}
public double getAmount( ) {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public MonetaryTransaction(double amount, int resid) {
this.resId = resid;
this.amount = amount;
this.txId = -1;
}
public MonetaryTransaction( ) {
this(0.0, 0);
}
}
对这个类和数据库schema进行映射:
Example 7-7. MonetaryTransaction.hbm.xml
<hibernate-mapping>
<class name="com.springbook.MonetaryTransaction"
table="monetaryTransactions">
<id name="txId" column="txid" type="java.lang.Integer"
unsaved-value="-1">
<generator class="native"></generator>
</id>
<property name="resId" column="resid" type="int"/>
<property name="amount" column="amount" type="double"/>
</class>
</hibernate-mapping>
要访问这个新数据库,我们必须另外再配置一个数据源和SessionFactory 。一个SessionFactory 用来处理针对一个数据库的访问,因此我们需要针对这个新数据库来一个新的SessionFactory。
Example 7-8. RentABikeApp-Servlet.xml
<bean id="dataSourceForAccounts"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/justbikes</value>
</property>
<property name="username"><value>rentaBikeAccounts</value></property>
</bean>
<bean id="sessionFactoryForAccounts"
class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSourceForBikes"/></property>
<property name="mappingResources">
<list>
<value>com/springbook/MonetaryTransaction.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
net.sf.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.show_sql">false</prop>
</props>
</property>
</bean>
但是我们不能直接在HibRentABike接口的实现类中使用这个新的SessionFactory。这是因为HibRentABike扩展了Spring提供的HibernateDaoSupport 类,这个类使用了单一的SessionFactory作为getHibernateTemplate( )的返回。因此,我们需要另一个facade类,这个新的facade类会使用这个新的SessionFactory来访问我们现在这个新建的数据库。
Example 7-9. Accounts.java
public interface Accounts {
void addTx(MonetaryTransaction tx);
MonetaryTransaction getTx(int id);
List getTxs( );
}
Example 7-10. RentABikeAccounts.java
public class RentABikeAccounts
extends HibernateDaoSupport
implements Accounts {
public void addTx(MonetaryTransaction tx) {
getHibernateTemplate( ).saveOrUpdate(tx);
}
public MonetaryTransaction getTx(int id) {
return (MonetaryTransaction)getHibernateTemplate( ).
load(MonetaryTransaction.class, new Integer(id));
}
public List getTxs( ) {
return getHibernateTemplate( ).find("from MonetaryTransaction");
}
}
接着,我们需要在应用中配置这个facade:
Example 7-11. RentABike-servlet.xml
<bean id="rentaBikeAccountsTarget"
class="com.springbook.RentABikeAccounts">
<property name="sessionFactory">
<ref local="sessionFactoryForAccounts"/>
</property>
</bean>
<bean id="rentaBikeAccounts"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.springbook.Accounts</value>
</property>
<property name="interceptorNames">
<value>transactionInterceptor,rentaBikeAccountsTarget</value>
</property>
</bean>
最后,要在RentABike facade中使用这个新功能,我们要给RentABike添加相关setter方法,这样才能让Spring依赖注入进去。下面的代码是RentABike facade需要添加的内容:
Example 7-12. HibRentABike.java
private Accounts accountsFacade;
public Accounts getAccountsFacade( ) {
return accountsFacade;
}
public void setAccountsFacade(Accounts accountsFacade) {
this.accountsFacade = accountsFacade;
}
public void addReservation(Reservation reservation, double amount)
throws AddReservationException {
try {
MonetaryTransaction tx = new MonetaryTransaction(amount,
reservation.getReservationId( ));
getHibernateTemplate( ).saveOrUpdate(reservation);
accountsFacade.addTx(tx);
} catch (Exception ex) {
throw new AddReservationException( );
}
}
然后看看配置文件中需要添加的代码:
Example 7-13. App-Servlet.xml
<bean id="rentaBikeTarget" class="com.springbook.HibRentABike">
<property name="storeName"><value>Bruce's Bikes</value></property>
<property name="sessionFactory"><ref local="sessionFactory"/></property>
<property name="accountsFacade">
<ref local="rentaBikeAccounts"/>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
</bean>
<bean name="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="transactionAttributeSource">
<value>
com.springbook.RentABike.transferReservation=PROPAGATION_REQUIRED,
-ReservationTransferException
com.springbook.RentABike.addReservation=PROPAGATION_REQUIRED,
-AddReservationException
</value>
</property>
</bean>
总结:
在添加了第二个数据源后,我们能够很容易的给应用添加分布式事务功能。现在,无论什么时候HibRentABike.addReservation( )方法被调用时,应用程序会以事务性地将这个预订信息加到原来的数据库,将产生的现金业务加到另一个新的数据库。我们为领域模型添加了一些新的类,但是大部分都是通过配置设置来获取的。Spring的Hibernate和JTA的支持,处理了大部分比较重量级的工作,现在,应用程序在一个单一分布事务中处理多个物理数据存储。
这个系列的笔记和小结就此结束了,其实这本书在Amazon上的评价并不高,但是可以快速上手用,从整体了解一下Spring框架还是有帮助的。