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,声明式事务管理在配置上的成本极大的降低了。