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


 

作者:ysjian_pingcx 发表于2013-2-22 15:16:05 原文链接
阅读:29 评论:0 查看评论

你可能感兴趣的:(spring_aop_)