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中可以使用 --> <bean class="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 @GeneratedValue public 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注入SessionFactory public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public void updateBalance(Account account, double money) { //只能用getCurrentSession()方法获取Session,拿到上下文的Session Session 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 { @Test public 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); } @Test public 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,声明式事务管理在配置上的成本极大的降低了。