Spring_AOP_声明式事务

标签: spring_aop_ | 发表时间:2013-02-22 07:16 | 作者:ysjian_pingcx
分享到:
出处:http://blog.csdn.net

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_] 推荐:

Spring AOP详解

- - Java - 编程语言 - ITeye博客
        最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP来解决. 一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容. 例如,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智. 1.对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况.

Spring_AOP_声明式事务

- - CSDN博客架构设计推荐文章
改进一下,因为这两次操作属于同一个业务,将转账的两个事务放到一个事务中,就会避免这样的问题,因为如果A减了1000后,如果发生异常,之后会回滚,此时两个要么同时成功,要么同时失败,这正好满足了事务的原子性和一致性,这种方式重新定义了事务的边界,所以讲事务管理加到BIZ层是合理的设计. 那么模仿上面的例子实现一个Spring的事务管理:.

Spring AOP 代理机制 JDK&CGLIB

- - 开源软件 - ITeye博客
Spring AOP使用JDK动态代理或者CGLIB来为目标对象创建代理. (建议优先使用JDK的动态代理). 如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理. 所有该目标类型实现的接口都将被代理. 若该目标对象没有实现任何接口,则创建一个CGLIB代理. 如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法) 那也可以.

Spring AOP监控SQL执行

- - CSDN博客架构设计推荐文章
         对数据库连接池Proxool比较熟悉的读者,都知道Proxool可以记录SQL执行内容和时间等信息日志. 我们可以将该日志记录专门的SQL日志文件,对于查找执行特别耗时的SQL起了不小的作用. 对于一些其他连接池,没有该特性时,本文介绍Spring AOP切面方法来记录SQL日志.

Spring AOP 实现原理与 CGLIB 应用

- - 博客 - 伯乐在线
来源: IBM Developerworks. 简介: AOP(Aspect Orient Programming),也就是面向方面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等.

使用spring AOP获得session的思路

- - RSS - IT博客云
由于Spring 的AOP面向切面编程,与Servlet容器没有任何关联,所以想要获得Session会话比较麻烦. 当然Struts2同样不依赖Servlet容器,可以在Spring AOP中可以使用 com.opensymphony.xwork2.ActionContext,就可以获得 Session.

Spring AOP + Redis缓存数据库查询

- - 编程语言 - ITeye博客
我们希望能够将数据库查询结果缓存到Redis中,这样在第二次做同样的查询时便可以直接从redis取结果,从而减少数据库读写次数. 必须要做到与业务逻辑代码完全分离. 从缓存中读出的数据必须与数据库中的数据一致. 如何为一个数据库查询结果生成一个唯一的标识. Key),能唯一确定一个查询结果,同一个查询结果,一定能映射到同一个.

Spring AOP动态代理原理与实现方式 (转)

- - 开源软件 - ITeye博客
AOP:面向切面、面向方面、面向接口是一种横切技术. 1.事务管理: (1)数据库事务:(2)编程事务(3)声明事物:Spring AOP-->声明事物   . 3.安全验证: Spring AOP---OOP升级  . 静态代理原理:目标对象:调用业务逻辑    代理对象:日志管理. 表示层调用--->代理对象(日志管理)-->调用目标对象.

基于 Annotation 拦截的 Spring AOP 权限验证方法

- - 企业架构 - ITeye博客
转自: http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilter/index.html. 使用 Annotation 可以非常方便的根据用户的不同角色,分配访问 Java 方法的权限. 在 Java Web 开发中,使用这种方法,可以提高系统的松耦合度,方便维护.

Spring aop 原理及各种应用场景

- - 开源软件 - ITeye博客
AOP是Aspect Oriented Programing的简称,面向切面编程. AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理、缓存、对象池管理以及日志记录. AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中. AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强.