Spring的事务管理

本文深入探讨了Spring的事务管理,包括事务管理API的事务管理器接口和事务定义接口,介绍了如何使用事务代理工厂、注解以及AspectJ的AOP配置管理事务,并提供了具体的程序示例和异常处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring 的事务管理

事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。在 Spring 中通常可以通过以下三种方式来实现对事务的管理:

  1. 使用 Spring 的事务代理工厂管理事务
  2. 使用 Spring 的事务注解管理事务
  3. 使用 AspectJ 的 AOP 配置管理事务

Spring 事务管理API

Spring 的事务管理,主要用到两个事务相关的接口。

1. 事务管理器接口

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。

常用的两个实现类
  • DataSourceTransactionManager:使用 JDBC 或 iBatis 进行持久化数据时使用。
  • HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
Spring 的回滚方式

Spring 事务的默认回滚方式是: 发生运行时异常时回滚,发生受查异常时提交。对于受查异常,也可以手工设置其回滚方式。

错误与异常

{% asset_img dao05.jpg %}

Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时, 才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。

Error 是程序在运行过程中出现的无法处理的错误,比如OutOfMemoryError、ThreadDeath、 NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出)
的, JVM 一般会终止线程。

程序在编译和运行时出现的另一类错误称之为异常,它是 JVM 通知程序员的一种方式。通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。

异常分为运行时异常与受查异常:

  1. 运行时异常, 是 RuntimeException 类或其子类 , 即只有在运行时才出现的异常。如,NullPointerException、 ArrayIndexOutOfBoundsException、 IllegalArgumentException 等均属于运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。(Hibernate 异常 HibernateException 也属于运行时异常)。
  2. 受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如 SQLException, ClassNotFoundException, IOException 等都属于受查异常。

RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。 定义异常时,只要未明确声明定义的为 RuntimeException 的子类,那么定义的就是受查异常。

2. 事务定义接口

事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量: 事务隔离级别、事务传播行为、事务默认超时时限, 及对它们的操作。

定义了五个事务隔离级别常量

这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

  • DEFAULT: 采用 DB 默认的事务隔离级别。 MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
  • READ_UNCOMMITTED: 读未提交。 未解决任何并发问题。
  • READ_COMMITTED: 读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ: 可重复读。解决脏读、不可重复读,存在幻读。
  • SERIALIZABLE: 串行化。不存在并发问题。
定义了七个事务传播行为常量

谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如, A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

  • REQUIRED: 指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
  • SUPPORTS: 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
  • MANDATORY: 指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。
  • REQUIRES_NEW: 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
  • NOT_SUPPORTED: 指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。
  • NEVER: 指定的方法不能在事务环境下执行,若当前存在事务,就直接抛出异常。
  • NESTED: 指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行;若当前没有事务,则创建一个新事务。
定义了默认事务超时时

常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的 none 值。

注意:
事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

程序举例

需求:
实现模拟购买股票。存在两个实体:银行账户 Account 与股票账户 Stock。当要购买股票时,需要从 Account 中扣除相应金额的存款,然后在 Stock 中增加相应的股票数量。而在这个过程中,可能会抛出一个用户自定义的异常。异常的抛出,将会使两个操作回滚。

实现步骤:

1. 创建数据库表

{% asset_img dao06.jpg %}

{% asset_img dao07.jpg %}

2. 创建实体类
public class Account {
	private Integer aid;
	private String aname;
	private double balance;	//余额

}
public class Stock {
	private Integer sid;
	private String sname;	//股票名称
	private int count;		//股票数量

}
3. 定义 service 的实现类

根据需求,我们可以先定义 Service 实现类,自动生成 Service 接口、Dao 接口和异常类,以减少代码写入。

public class BuyStockServiceImpl implements IBuyStockService {

	private IAccountDao adao;
	private IStockDao sdao;
	
	public void setAdao(IAccountDao adao) {
		this.adao = adao;
		
	}
	
	public void setSdao(IStockDao sdao) {
		this.sdao = sdao;
		
	}

	@Override
	public void openAccount(String aname, double money) {
		adao.insertAccount(aname,money);
	}

	@Override
	public void openStock(String sname, int amount) {
		sdao.insertStock(sname,amount);
	}

	@Override
	public void buyStock(String aname, double money, String sname, int amount) throws BuyStockException {
		boolean isBuy = true;
		adao.updateAccount(aname,money,isBuy);
		if(1 == 1) {
			throw new BuyStockException("购买股票异常");
		}
		sdao.updateStock(sname,amount,isBuy);
	}

}
4. 定义 Service 接口
public interface IBuyStockService {
	void openAccount(String aname,double money);
	void openStock(String sname, int amount);
	
	void buyStock(String aname,double money,String sname,int amount) throws BuyStockException;

}
5. 定义 dao 接口
public interface IAccountDao {

	void insertAccount(String aname, double money);

	void updateAccount(String aname, double money, boolean isBuy);

}
public interface IStockDao {

	void insertStock(String sname, int amount);

	void updateStock(String sname, int amount, boolean isBuy);

}
6. 定义异常类
public class BuyStockException extends Exception {
	public BuyStockException() {
		super();
	}
	
	public BuyStockException(String message) {
		super(message);
	}

}
7. 定义 dao 实现类
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

	@Override
	public void insertAccount(String aname, double money) {
		String sql = "insert into account(aname, balance) values(?,?)";
		this.getJdbcTemplate().update(sql, aname, money);
	}

	@Override
	public void updateAccount(String aname, double money, boolean isBuy) {
		String sql = "update account set balance=balance+? where aname=?";
		if (isBuy) {
			sql = "update account set balance=balance-? where aname=?";
		}
		this.getJdbcTemplate().update(sql, money, aname);
	}

}
public class StockDaoImpl extends JdbcDaoSupport implements IStockDao {

	@Override
	public void insertStock(String sname, int amount) {
		String sql = "insert into stock(sname,count) values(?,?)";
		this.getJdbcTemplate().update(sql, sname,amount);
	}

	@Override
	public void updateStock(String sname, int amount, boolean isBuy) {
		String sql = "update stock set count=count-? where sname=?";
		if (isBuy) {
			sql = "update stock set count=count+? where sname=?";
			
		}
		this.getJdbcTemplate().update(sql, amount,sname);
	}

}
8. 定义 Spring 配置文件(IoC 应用)
	<!-- 注册数据源:C3P0 -->
	<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}"/>
		<property name="jdbcUrl" value="${jdbc.url}"/>
		<property name="user" value="${jdbc.user}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
	
	<!-- 注册属性文件 -->
	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 注册Dao -->
	<bean id="accountDao" class="com.huang.dao.AccountDaoImpl">
		<property name="dataSource" ref="myDataSource"/>
	</bean>
	<bean id="stockDao" class="com.huang.dao.StockDaoImpl">
		<property name="dataSource" ref="myDataSource"/>
	</bean>
	
	<!-- 注册Service -->
	<bean id="buyStockService" class="com.huang.service.BuyStockServiceImpl">
		<property name="adao" ref="accountDao"/>
		<property name="sdao" ref="stockDao"/>
	</bean>
9. 定义测试类
public class MyTest {

	
	private IBuyStockService service;

	@Before
	public void before() {
		// 创建容器对象,加载Spring配置文件
		String resource = "applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		service = (IBuyStockService) ac.getBean("buyStockService");
	}
	
	@Test
	public void test01() {
		service.openAccount("张三", 10000);
		service.openStock("现代学院", 0);
	}
	
	@Test
	public void test02() throws BuyStockException {
		service.buyStock("张三", 2000, "现代学院", 5);
	}
	

}
10. 运行结果

{% asset_img dao08.jpg %}

{% asset_img dao09.jpg %}

{% asset_img dao10.jpg %}

使用Spring的事务代理工厂管理事务

该方式是,需要为目标类,即 Service 的实现类创建事务代理。事务代理使用的类是 TransactionProxyFactoryBean,该类需要初始化如下一些属性:

(1) transactionManager:事务管理器
(2) target:目标对象,即 Service 实现类对象
(3) transactionAttributes:事务属性

1. 修改 Spring 配置文件(AOP应用)
	<!-- 注册事务管理器 -->
	<bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="myDataSource"/>
	</bean>
	
	<!-- 生成事务代理对象 -->
	<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<property name="transactionManager" ref="myTransactionManager"/>
		<property name="target" ref="buyStockService"/>
		<property name="transactionAttributes">
			<props>
				<prop key="open*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
				<!-- 
					-异常:表示发生指定异常后回滚,这时的异常通常是受查异常
					+异常:表示发生指定异常后提交,这时的异常通常是运行时异常
				 -->
				<prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED, -BuyStockException</prop>
			</props>
		</property>
	</bean>
2. 修改测试类
service = (IBuyStockService) ac.getBean("serviceProxy");

使用 Spring 的事物注解管理事务

通过@Transactional 注解方式,也可将事务织入到相应方法中。而使用注解方式,只需在配置文件中加入一个 tx 标签,以告诉 Spring 使用注解来完成事务的织入。该标签只需指定一个属性,事务管理器。

@Transactional 的所有可选属性如下所示:

  • propagation: 用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
  • isolation: 用于设置事务的隔离级别。该属性类型为 Isolation 枚举 ,默认值为 Isolation.DEFAULT。
  • readOnly: 用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
  • timeout: 用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
  • rollbackFor: 指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • rollbackForClassName: 指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackFor: 指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackForClassName: 指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

注意:
@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

1. 修改 Spring 配置文件(AOP应用)
	<!-- 注册事务管理器 -->
	<bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="myDataSource"/>
	</bean>
	
	<!-- 注册事务注解驱动 -->
	<tx:annotation-driven transaction-manager="myTransactionManager"/>
2. 修改测试类
service = (IBuyStockService) ac.getBean("buyStockService");
3. 在 Service 实现类方法上添加注解

{% asset_img dao11.jpg %}

使用AspectJ的AOP配置管理事务

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。

修改配置文件(AOP应用)
	<!-- 注册事务管理器 -->
	<bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="myDataSource"/>
	</bean>
	
	<!-- 注册事务通知 -->
	<tx:advice id="txAdvice" transaction-manager="myTransactionManager">
		<tx:attributes>
		    
			<!-- 这里指定的是:为每一个连接点指定所要应用的事务属性 -->
			<tx:method name="open*" isolation="DEFAULT" propagation="REQUIRED"/>
			<tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="BuyStockException"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- AOP配置 -->
	<aop:config>
		<!-- 这里指定的是切入点 -->
		<aop:pointcut expression="execution(* *..service.*.*(..))" id="myPointcut"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
	</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值