读书人

Spring_AOP_宣言式事务

发布时间: 2013-02-24 17:58:56 作者: rapoo

Spring_AOP_声明式事务

Spring_AOP_Transaction

引言:

Spring和Hibernate继承还有一个重点就是事务的管理,通过AOP实现事务的管理的,通常不会将事务管理的切面织入DAO层,而是注入到BIZ业务层,因为我们的业务逻辑中有可能包含多个DAO层的操作,比如银行转账的业务中,A账户给B账户转账,从A账户减1000是要与数据库交互一次既是一次事务,给B账户加1000又是一次数据库交互,有事一次事务,如果这两个事务是独立的话,万一程序运行到刚将A账户减1000,事务提交后,发生了异常,那么B账户的1000元就没法进账,这就导致A白白损失了1000,严重的可能导致两人的争执,这是不合理的。改进一下,因为这两次操作属于同一个业务,将转账的两个事务放到一个事务中,就会避免这样的问题,因为如果A减了1000后,如果发生异常,之后会回滚,此时两个要么同时成功,要么同时失败,这正好满足了事务的原子性和一致性,这种方式重新定义了事务的边界,所以讲事务管理加到BIZ层是合理的设计。

那么模仿上面的例子实现一个Spring的事务管理:

开始搭建框架:

<?xml version="1.0" encoding="UTF-8"?><!-- 这里特别的注意,xsi:schemaLocation中要有上面的信息对应的地址 --><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd           http://www.springframework.org/schema/tx           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"><!-- IOC使用注解的配置前提 --><context:annotation-config /><!-- 指定在com.ssh下所有的包去匹配,需要在特定的bean中配置注解 --><context:component-scan base-package="com.spring_hibernate" /><!-- 用于直指定配置文件的位置信息,在dataSource中可以使用 --><beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><value>classpath:jdbc.properties</value></property></bean><!-- 配置数据源,用了dbcp数据库连接池 --><bean id="dateSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><property name="driverClassName" value="${jdbc.driverClass}" /><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.userName}" /><property name="password" value="${jdbc.password}"></property></bean><!-- 配置SessionFactory --><bean id="sessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"><!-- 配置数据源,用于连接数据库 --><property name="dataSource" ref="dateSource" /><!-- 方式一:使用了注解的实体类,就是关联关系的类,有几个得加几个 --><property name="annotatedClasses"><list><value>com.spring_hibernate.entity.Account</value></list></property><!-- 方式二:配置扫描包 ,这个可以统一加入com.ssh.entity包下的实体,而这选其一即可--><property name="packagesToScan"><list><value>com.spring_hibernate.entity</value></list></property><!-- 配置Hibernate的一些属性 --><property name="hibernateProperties"><props><prop key="hibernate.show_sql">true</prop><prop key="hibernate.hbm2ddl.auto">update</prop><prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop></props></property></bean><!-- 配置Hibernate的声明式事务管理,相当于一个切面 --><bean id="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory"></property></bean><!-- 定义事务通知 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="add*" propagation="REQUIRED" /><tx:method name="remit" propagation="REQUIRED"/><tx:method name="get*" propagation="SUPPORTS" read-only="true"/></tx:attributes></tx:advice><!-- 配置AOP --><aop:config><!-- 定义一个切入点(断言) --><aop:pointcut id="bizMethods"expression="execution(* com.spring_hibernate.biz..*.*(..))" /><!-- 定义通知者 --><aop:advisor advice-ref="txAdvice" pointcut-ref="bizMethods" /></aop:config></beans>


Account::

import java.io.Serializable;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name="tb_account")public class Account implements Serializable {private static final long serialVersionUID = 2945659794041944288L;private int aid;private String aname;private double balance;@Id@GeneratedValuepublic int getAid() {return aid;}public void setAid(int aid) {this.aid = aid;}public String getAname() {return aname;}public void setAname(String aname) {this.aname = aname;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}}

AccountDao:

import java.io.Serializable;import com.spring_hibernate.entity.Account;public interface AccountDao {/** * 添加用户 */public void addAccount(Account account);/** * 根据id获取用户 * @param id * @return */public Account getAccountById(Serializable id);/** * 更新余额 * @param money */public void updateBalance(Account account, double money);}


AccountDaoImpl(这里面是关键的交易代码):

import java.io.Serializable;import javax.annotation.Resource;import org.hibernate.HibernateException;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.springframework.stereotype.Component;import com.spring_hibernate.dao.AccountDao;import com.spring_hibernate.entity.Account;@Component("accountDao")public class AccountDaoImpl implements AccountDao {private SessionFactory sessionFactory;@Resource// 通过Spring注入SessionFactorypublic void setSessionFactory(SessionFactory sessionFactory) {this.sessionFactory = sessionFactory;}public void updateBalance(Account account, double money) {//只能用getCurrentSession()方法获取Session,拿到上下文的SessionSession session = sessionFactory.getCurrentSession();//Transaction tx = session.beginTransaction();不用在这开启事务,交由Spring管理try {String hql = "update Account a set a.balance = a.balance + :money where aname = :name";session.createQuery(hql).setDouble("money", money).setString("name",account.getAname()).executeUpdate();//模拟的时候必须得抛出去,如果是HibernateException的话,//Spring会自动管理,就会中断事务//throw new RuntimeException("交易有误...");} catch (HibernateException e) {//发生HibernateException时打印System.out.println("事务回滚...");e.printStackTrace();}//tx.commit();  不能在这提交了,交给Spring管理。}public void addAccount(Account account) {Session session = sessionFactory.getCurrentSession();session.save(account);}public Account getAccountById(Serializable id) {Session session = sessionFactory.getCurrentSession();return (Account)session.get(Account.class, id);}}

AccountBiz:

import java.io.Serializable;import com.spring_hibernate.entity.Account;public interface AccountBiz {public Account getAccountById(Serializable id);public void addAccount(Account account);public void remit(Account fromAccount,Account toAccount,double money);}


AccountBizImpl:

import java.io.Serializable;import javax.annotation.Resource;import org.springframework.stereotype.Component;import com.spring_hibernate.biz.AccountBiz;import com.spring_hibernate.dao.AccountDao;import com.spring_hibernate.entity.Account;@Component("accountBiz")public class AccountBizImpl implements AccountBiz {private AccountDao accountDao;//程序自己指定public AccountBizImpl(){}//指定资源,默认按byName@Resource(name="accountDao")public void setAccountDao(AccountDao userDao) {this.accountDao = userDao;}public void remit(Account fromAccount1,Account toAccount2,double money) {accountDao.updateBalance(fromAccount1,-money);System.out.println(fromAccount1.getAname()+"的账户减少了"+ money+"元");accountDao.updateBalance(toAccount2,money);System.out.println(toAccount2.getAname()+"的账户增加了"+ money+"元");}public void addAccount(Account account) {accountDao.addAccount(account);}public Account getAccountById(Serializable id) {return accountDao.getAccountById(id);}}


junit测试:

import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.spring_hibernate.biz.AccountBiz;import com.spring_hibernate.entity.Account;public class TestAccount {@Testpublic void testSave() {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");AccountBiz accountBiz = (AccountBiz) ac.getBean("accountBiz");System.out.println(accountBiz);Account account = new Account();account.setAname("yyyyy");account.setBalance(5000);Account account2 = new Account();account2.setAname("jjjjj");account2.setBalance(5000);accountBiz.addAccount(account);accountBiz.addAccount(account2);}@Testpublic void testRemit() {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");AccountBiz accountBiz = (AccountBiz) ac.getBean("accountBiz");System.out.println(accountBiz);Account account1 = accountBiz.getAccountById(1);Account account2 = accountBiz.getAccountById(2);//开始转账accountBiz.remit(account2, account1, 1000);}}


总结,在上面的AccountDaoImpl中模拟了一个异常的放生,就是当任意一个账户转账过程中发生异常时,真个事务都会回滚,已经更改的数据没有提交,一并回到事务之前,这就是要将事务管理的切面放到BIZ业务逻辑层的原因;

抽出配置Hibernate声明式事务的代码:

<!-- 配置Hibernate的声明式事务管理,相当于一个切面 --><bean id="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory"></property></bean><!-- 定义事务通知 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="add*" propagation="REQUIRED" /><tx:method name="remit" propagation="REQUIRED"/><tx:method name="get*" propagation="SUPPORTS" read-only="true"/></tx:attributes></tx:advice><!-- 配置AOP --><aop:config><!-- 定义一个切入点(断言) --><aop:pointcut id="bizMethods"expression="execution(* com.spring_hibernate.biz..*.*(..))" /><!-- 定义通知者 --><aop:advisor advice-ref="txAdvice" pointcut-ref="bizMethods" /></aop:config>


稍作解释:propagation的类型是关于在业务执行时事务开启的情况,是一个枚举类型的常量,有七中情况,最常用的REQUIRED

REQUIRED:使用当前的事务,如果没有,就开启一个。

SUPPORT:使用当前的事务,如果当前没有事务,依然执行。

REQUIRED_NEW:开启一个新的事务,如果当前有事务,就挂起当前的事务,执行自己的事务。

...

read-only="true"可以提高查询的效率。

补充:另外Spring还有一种编程式事务,推荐用声明式事务;

至于两者怎么选择?

1.当你只有很少的事务操作时,编程式事务管理通常比较合适。例如:如果你只有一个Web应用,

其中只有特定的更新操作有事务要求,你可能不愿意使用Spring或其他即使设置事务代理。这种情况下,

使用TransactionTemplate可能是个好方法。只有编程式事务管理才能显示的设置事务名称。

2.如果你的应用中存在大量事务操作,那么声明式事务管理通常是值得的。它将事务管理与业务逻辑分离,

而且在Spring中配置也不难。使用Spring,而不是EJB CMT,声明式事务管理在配置上的成本极大的降低了。


读书人网 >软件架构设计

热点推荐