Spring: A Developers Notebook

7.3. Transactions on Multiple Databases

If you have to refactor a simple application to use multiple resources, Spring's pluggable transaction strategies can save you a whole lot of effort. In this example, you're going to use JTA transactions to span multiple databases. For this example to work, your application must be running in a JTA-aware J2EE container.

If you're using some kind of agile programming method, you'll likely run into this kind of scenario. Agile methods suggest that you take simple approaches to a problem, and implement more complex features only when they're needed. Dependency injection makes it much easier to take slightly different versions of application resources and plug them into a given application without major upheaval.

7.3.1. How do I do that?

In this case, you're going to maintain monetary transaction information in a separate database, rentaBikeAccounts. Whenever users make a reservation, they have to provide a down payment, and you'll transactionally add this amount to the monetaryTransactions table in the new database. Example 7-5 is the script for setting up the new database and table.

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;

Don't forget that you'll need to set up a user account in MySQL with privileges on the new database:

GRANT ALL PRIVILEGES ON rentaBikeAccounts.* TO 'rentaBikeAccounts'@'localhost' WITH GRANT OPTION;

We just created a user named rentaBikeAccounts, with complete access to the new database.

Next, Example 7-6 creates a persistent 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); } }

Map the schema to the class, as in Example 7-7.

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 ></generator> </id> <property name="resId" column="resid" type="int"/> <property name="amount" column="amount" type="double"/> </class> </hibernate-mapping>

To access this new database, you have to configure a second data source and SessionFactory in your application configuration. Session factories handle access for to a single database, so you need a new SessionFactory for the second one (Example 7-8).

Example 7-8. RentABikeApp-Servlet.xml

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

Unfortunately, you can't use this new SessionFactory directly within the HibRentABike implementation. That's because HibRentABike extends the Spring-provided HibernateDaoSupport class, which uses a single SessionFactory as the backing behind getHibernateTemplate( ). As such, you need a second façade class, this one to model access to this new database through the new SessionFactory (Examples Example 7-9 and Example 7-10).

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"); } }

You need to configure this façade in your application configuration (Example 7-11).

Example 7-11. RentABike-servlet.xml

<bean > <property name="sessionFactory"> <ref local="sessionFactoryForAccounts"/> </property> </bean> <bean > <property name="proxyInterfaces"> <value>com.springbook.Accounts</value> </property> <property name="interceptorNames"> <value>transactionInterceptor,rentaBikeAccountsTarget</value> </property> </bean>

Finally, use this new functionality within the RentABike façade, which means you'll need to add a setter to RentABike and wire them together in the application context. Example 7-12 shows the additions to HibRentABike.

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 shows the additions to the configuration file.

Example 7-13. App-Servlet.xml

<bean > <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 > </bean> <bean name="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>

7.3.2. What just happened?

After adding a second data source, you were able to quickly add XA transactional functionality to the application. Now, whenever the HibRentABike.addReservation( ) method is called, the application transactionally adds a reservation to the original database and a MonetaryTransaction to the new database. You added a few new classes to the domain model, but mostly this was accomplished through configuration settings. Spring's Hibernate and JTA support handle most of the heavy lifting, and now the application handles multiple physical data stores inside a single distributed transaction.

    Категории