Sping 数据库事务管理

一、数据库事务的ACID特性

原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保整个事务的所有操作要么全部完成,要么全部不完成。

一致性(Consistency):事务一旦完成不管成功还是失败,系统始终处于一致的状态。

隔离性(Isolation):不同的事务应该相互隔离,防止数据破坏。

持久性(Durability):在事务完成后,该事务对数据库所做的更改便持久的保存在数据库中,并不会被回滚,无论发生什么系统错误,它的结果都不应该受到影响

二、Spring事务隔离级别

ISOLATION_DEFAULT       使用后端数据库默认的隔离级别                     
ISOLATION_READ_UNCOMMITED 脏读,允许一个事务去读取另一个事务中未提交的数据,可能导致脏、幻、不可重复读。
ISOLATION_READ_COMMITTED 读/写提交,一个事务只能读取另一个事务已经提交的数据。可以防止脏读,但幻读和不可重复读仍可发生
ISOLATION_REPEATABLE_READ 可重复读,对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可能重复读,但是幻读仍可能发生
ISOLATION_SERIALIZABLE 序列化,完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中花费的代价最大的,但是也是最可靠的隔离级别
   

三、Spring事务的传播行为

首先来了解下为什么需要传播行为,通常我们使用service层来调用dao层提供的修改数据的方法,比如下面这样(伪代码)

	//AccountService
	class AccountService{
		//from 转账给 to money元
		public void transfer(String from,String to,int money) {
			//from用户减少money		
			accountDao.transferFrom(from, money);
			//to用户增加money
			accountDao.transferTo(to, money);
		}
	}

上面的代码很简单,就是from给to转账,而我们的事务一般都是加在业务层的方法上的,也就是说transfer方法其实就是执行在一个事务中,里面的数据库操作要么都完成,要么都不完成。但如果当我们的事务变得复杂的情况下(如下)
 

	//AccountService
	class AccountService{
		//from 转账给 to money元
		public void transfer(String from,String to,int money) {
			//from用户减少money		
			accountDao.transferFrom(from, money);
			//to用户增加money
			accountDao.transferTo(to, money);
			//这里调用了本类的其他方法
			xxx();
		}
		public void xxx(){
			dao1.xxx();
		}
	}

想象一下,如果这里transfer()方法执行在一个事务中,xxx()方法又执行在另一个事务中,此时我就无法保证事务的一致性,因此我们需要transfer()方法和xxx()方法处于同一事务中。事务的传播行为就可以解决该问题。

事务的传播行为(7种):

PROPAGATION_REQUIRED 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY 支持当前事务, 如果当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED 如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务

前3个传播行为定义了,不同的业务层方法调用时(比如说这里的transfer()方法中调用了xxx()方法),他们应该处于同一个事务中,但如果当前没有事务,它们的处理有所不同PROPAGATION_REQUIRED会新建一个事务,PROPAGATION_SUPPORTS会以非事务方式运行,而PROPAGATION_MANDATORY则会抛出异常。而随后的三个传播行为则定义了他们应该处于不同的事务中,最后一个则代表嵌套事务执行。

四、申明式事务(基于xml)

使用申明式事务时,如果业务方法没有发生异常,Sping就会让事务管理器提交事务,如果发生异常,则让事务管理器回滚事务。

开始撸码!!!

dao层

一个与转账有关的接口如下:

public interface AccountDao {
   
	/*
	 * @param from:转账方
	 * @param money:转转金额
	 */
	public void transferFrom(String from,int money);
	
	/*
	 * @param from:接受方
	 * @param money:接受金额
	 */
	public void transferTo(String to,int money);
}

接口的具体实现类

public class AccountDaoImpe implements AccountDao {
	//注入JdbcTemplate
	private JdbcTemplate jdbcTemplate;

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/*
	 * @param from:转账方
	 * @param money:转转金额
	 */
	@Override
	public void transferFrom(String from, int money) {
		String sql="update bank1 set money=money-? where name=?";
		jdbcTemplate.update(sql, new Object[]{money,from});
	}

	/*
	 * @param from:接受方
	 * @param money:接受金额
	 */
	@Override
	public void transferTo(String to, int money) {
		String sql="update bank1 set money=money+? where name=?";
		jdbcTemplate.update(sql, new Object[]{money,to});
	}

}

service层

AccountService接口

public interface AccountService {

	//进行转账业务
	public void transfer(String from,String to,int money);
}

具体的实现类(调用dao层来进行转账)

public class AccountServiceImpl implements AccountService {

	//注入AccountDaoImpe
	private AccountDaoImpe accountDao;

	public void setAccountDao(AccountDaoImpe accountDao) {
		this.accountDao = accountDao;
	}

	//进行转账业务
	@Override
	public void transfer(String from,String to,int money) {
		accountDao.transferFrom(from, money);
		accountDao.transferTo(to, money);
	}

}

xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 引入外部属性配置文件 -->
	<context:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置数据库连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置JdbcTemplate 模板类 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 配置dao -->
	<bean id="accountDao" class="dao.AccountDaoImpe">
		<!-- 注入jdbcTemplate -->
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>

	<!-- 配置service -->
	<bean id="accountService" class="service.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
	</bean>

</beans>

初始数据库中又如下数据

现在开始转账,假设张三给李四转100元。

//测试
	public static void main(String[] args) {
		ApplicationContext ioc=
				new ClassPathXmlApplicationContext("application-cfg.xml");
		AccountService service=(AccountService) ioc.getBean("accountService");
		service.transfer("张三", "李四", 100);
	}

结果:查看数据库可以发现成功的转账了。

注意此时我们还没有使用事务。如果在张三转给李四的过程中发生了异常,比如停电了!!!此时会如何?先将数据库恢复初始状态,两人余额都是2000,然后修改一处代码如下。

//进行转账业务
	@Override
	public void transfer(String from,String to,int money) {
		accountDao.transferFrom(from, money);
		int a=2/0;//模拟断电
		accountDao.transferTo(to, money);
	}

执行测试代码,结果如下:

很明显,张三减少100但是随后由于发生了异常李四余额没有增加100,这样问题就很大了。因此引入了事务,这里将事务加在业务层,让整个transfer()方法在同一个事务中执行,确保整个事务的所有操作要么全部完成,要么全部不完成。

修改xml文件如下(重点):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 引入外部属性配置文件 -->
	<context:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置数据库连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置JdbcTemplate 模板类 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 配置dao -->
	<bean id="accountDao" class="dao.AccountDaoImpe">
		<!-- 注入jdbcTemplate -->
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>

	<!-- 配置service -->
	<bean id="accountService" class="service.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
	</bean>

	<!-- 配置事务管理器 -->
	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入数据源 -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 事务增强,相当于切面类 -->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<!-- method 定义的方法会添加事务管理 -->
			<!-- isolation 事务的隔离级别 -->
			<!-- propagation 事务的传播行为 ,默认是同一个事务 -->
			<!-- read-only 事务是否只读 -->
			<!-- timeout="-1" 事务的超时时间,默认值使用数据库的超时时间 -->
			<!-- rollback-for: 遇到哪些异常就回滚,其他的都不回滚 -->
			<!-- tno-rollback-for:遇到哪些异常不回滚,其他的都回滚。 -->
			<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"
				read-only="false" timeout="-1" />
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<!-- 定义切入点 -->
		<aop:pointcut expression="execution(* service.AccountServiceImpl.transfer(..))"
			id="pointcut1" />
		<!-- 引入切面 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"></aop:advisor>
	</aop:config>
</beans>

再次测试,首先依然将数据库恢复初始状态,将产生异常的代码注释掉。

正常情况下

没毛病,将数据库恢复初始状态,再来模拟异常情况下(模拟断电)取消产生异常代码的注释。

可以发现这次没有出现总金额少100的情况。因为整个业务方法在一个事务中执行,如果产生异常,事务会回滚。

五、申明式事务——基于注解

xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 引入外部属性配置文件 -->
	<context:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置数据库连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置JdbcTemplate 模板类 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 配置dao -->
	<bean id="accountDao" class="dao.AccountDaoImpe">
		<!-- 注入jdbcTemplate -->
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>

	<!-- 配置service -->
	<bean id="accountService" class="service.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
	</bean>

	<!-- 配置事务管理器 -->
	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入数据源 -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
  
  <!-- 开启事务注解 -->
  <tx:annotation-driven/>
</beans>

通过注解来开启事务需要通过<tx:annotation-driven/>来开启

然后在业务层使用注解方式即可

//@Transactional放置在类级的声明中,会使得所有方法都有事务
//@Transactional
public class AccountServiceImpl implements AccountService {

	//注入AccountDaoImpe
	private AccountDaoImpe accountDao;

	public void setAccountDao(AccountDaoImpe accountDao) {
		this.accountDao = accountDao;
	}

	//进行转账业务
	//在方法上应用事务,注意方法的@Transactional会覆盖类上面声明的事务
	@Transactional
	@Override
	public void transfer(String from,String to,int money) {
		accountDao.transferFrom(from, money);
		int a=2/0;//模拟断电
		accountDao.transferTo(to, money);
	}

}

可以发现通过注解的方式配置事务很简单,同样的注解的方式也可以设置传播行为,隔离级别等信息

@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)

猜你喜欢

转载自blog.csdn.net/start_mao/article/details/80613830