Spring 事务管理

一、spring的事务管理

1.事物的概念:

事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性。

事务就是一系列的动作, 它们被当作一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。
2.事务的属性
事务的4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity):
事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用。
一致性(consistency):
一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中。
隔离性(isolation):
可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏。
持久性(durability):
一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中。

二、事务的分类

编程式事务和生命式事务
1.编程式事务管理:
将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。可以清楚的定义事务的边界,可以实现细粒度的事务控制,开发者可以通过程序代码来控制你的事务何时开始,何时结束。如:JDBC的事务处理。
但是事务的控制与业务代码耦合在一起,不能共用,而且开发效率低,不便于维护。在代码中 通过 TransactionTemplate 手动进行事务管理 ,在实际开发中很少被用到。
2.声明式事务:
将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理。 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化。 Spring 通过 Spring AOP 框架支持声明式事务管理。
如果需要修改或者删除事务,移除配置即可,解除了系统服务(事务)与业务代码的耦合,而且方便管理。在实际开发中,一般提倡用声明式事务管理。
三中实现方式:
1.用xml配置文件实现事务管理
2.用注解的方式实现事务管理
3.用AOP的方式实现事务管理

三、spring中的事务管理器

1. spring的事务API

Spring 的核心事务管理抽象是


 
PlatformTransactionManager 平台事务管理器
* void commit(TransactionStatus status)
提交事务
* TransactionStatus getTransaction(TransactionDefinition definition)
根据事务定义信息,获得当前状态
* void rollback(TransactionStatus status)
回滚事务
 
 
 
TransactionDefinition 事务定义信息 (配置信息 来自xml配置文件 和 注解)
包括 (隔离、传播、超时、只读)
* ISOLATION_xxx 事务隔离级别
* PROPAGATION_xxx 事务传播行为
* int getTimeout() 获得超时信息(了解)
* boolean isReadOnly() 判断事务是否只读(了解)

 
TransactionStatus 事务具体运行状态
* 每一个时刻点 事务具体状态信息

管理封装了一组独立于技术的方法。 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的。

关系:
PlatformTransactionManager 根据 TransactionDefinition 进行事务管理, 管理过程中事务存在多种状态, 每个状态信息通过 TransactionStatus 表示

PlatformTransactionManager 接口详解

不同平台事务管理器实现
1) 使用Spring JDBC或myBatis 进行持久化数据时使用
 
2) 使用Hibernate5.0版本进行持久化数据时使用
 
3)使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用(了解)
 

四、事务的实现

1.基于xml的实现
首先创建一个IUserDao接口

package com.dqsy.spring.dao;

import java.math.BigDecimal;

import com.dqsy.spring.entity.User;

/**
 * 用户账户操作接口
 * @author lenovo
 *
 */
public interface IUserDao {

	/**
	 * 添加金额
	 */
	public boolean addMoney(int userId, BigDecimal money);
	
	/**
	 * 减少金额
	 */
	public boolean subMoney(int userId, BigDecimal money);
	
	/**
	 * 添加用户
	 */
	public boolean addUser(User user);
	
	/**
	 * 转出账户的金额
	 */
	public BigDecimal getAccount(int fromUserId);
}
实现这个接口
package com.dqsy.spring.dao.impl;
import java.math.BigDecimal;
import org.springframework.jdbc.core.JdbcTemplate;
import com.dqsy.spring.dao.IUserDao;
import com.dqsy.spring.entity.User;

public class IUserDaoImpl implements IUserDao {
	
	private JdbcTemplate jdbcTemplate;

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

	@Override
	public boolean addMoney(int userId, BigDecimal money) {
		// TODO Auto-generated method stub
		boolean flag = false;
		String sql = "update user set account = account + ? where id=?";
		int rows = this.jdbcTemplate.update(sql, money, userId);
		if(rows > 0){
			flag = true;
		}
		return flag;
	}

	@Override
	public boolean subMoney(int userId, BigDecimal money) {
		// TODO Auto-generated method stub
		boolean flag = false;
		String sql = "update user set account = account - ? where id=?";
		int rows = this.jdbcTemplate.update(sql, money, userId);
		if(rows > 0){
			flag = true;
		}
		return flag;
	}

	@Override
	public boolean addUser(User user) {
		// TODO Auto-generated method stub
		boolean flag = false;
		String sql = "insert into user(name, account) values(?, ?)";
		int rows = this.jdbcTemplate.update(sql, user.getName(), user.getAccount());
		if(rows > 0){
			flag = true;
		}
		return flag;
	}

	@Override
	public BigDecimal getAccount(int fromUserId) {
		// TODO Auto-generated method stub
		String sql = "select account from user where id=?";
		BigDecimal account = this.jdbcTemplate
				.queryForObject(sql, BigDecimal.class, fromUserId);
		return account;
	}

}
建立User实体类
package com.dqsy.spring.entity;

import java.math.BigDecimal;

public class User {
	private int id;
	private String name;
	private BigDecimal account;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public BigDecimal getAccount() {
		return account;
	}
	public void setAccount(BigDecimal account) {
		this.account = account;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + ", account=" + account + "]";
	}
}
建立IUserService接口
package com.dqsy.spring.service;

import java.math.BigDecimal;

/**
 * 用户转账的服务
 * @author Administrator
 *
 */
public interface IUserService {

	public void operateAccount(int fromUserId, int toUserId, BigDecimal money);
	
	public void operateMulAccount(int fromUserId, int toUserId, BigDecimal money);
}
实现这个接口
package com.dqsy.spring.service.impl;

import java.math.BigDecimal;

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.dqsy.spring.dao.IUserDao;
import com.dqsy.spring.exception.UserAccountException;
import com.dqsy.spring.service.IUserService;



/**
 * 转账服务业务层
 * 
 * @author Administrator
 *
 */
public class IUserServiceImpl implements IUserService {

	private IUserDao userDao;

	public void setUserDao(IUserDao userDao) {
		this.userDao = userDao;
	}

	/**
	 * 实现转账 money : 转账金额
	 */
	@Override
	public void operateAccount(int fromUserId, int toUserId, BigDecimal money) {
		// TODO Auto-generated method stub
		System.out.println("....开始转账.....");
		System.out.println("转出金额 : " + money);
		// 1.转出账户的金额
		try {
			BigDecimal fromAccount = userDao.getAccount(fromUserId);

			/**
			 * 当fromAccount < money 是等于-1 当fromAccount = money 是等于0 当fromAccount
			 * > money 是等于 1
			 */
			if (!(fromAccount.compareTo(money) == -1)) {
				// 2.从转出账户中扣除金额
				userDao.subMoney(fromUserId, money);	
				//int a = 1/0;
				// 3.在转入账户上加上转入金额
				userDao.addMoney(toUserId, money);
			} else {
				throw new UserAccountException("余额不足");
			}
		} catch (UserAccountException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("....结束转账.....");
	}
	@Override
	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
	public void operateMulAccount(int fromUserId, int toUserId, BigDecimal money) {
		// TODO Auto-generated method stub
		System.out.println("....开始转账.....");
		BigDecimal fromAccount = userDao.getAccount(fromUserId);
		if (!(fromAccount.compareTo(money) == -1)) {
			userDao.subMoney(fromUserId, money);
			//int  a = 1/0;
			
			userDao.addMoney(toUserId, money);
		}
		System.out.println("....结束转账.....");
	}




}
因为第一个方法operateAccount()方法是没有用事务的测试,在这我也付上代码
异常处理:
package com.dqsy.spring.exception;

public class UserAccountException extends Exception {

	public UserAccountException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
		super(arg0, arg1, arg2, arg3);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String arg0, Throwable arg1) {
		super(arg0, arg1);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(Throwable arg0) {
		super(arg0);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String string) {
		// TODO Auto-generated constructor stub
		super(string);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

}
配置applicationContext.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:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
	<bean id="userService" class="com.dqsy.spring.service.impl.IUserServiceImpl">
		<property name="userDao" ref="userDao"></property>
	</bean>

	<bean id="userDao" class="com.dqsy.spring.dao.impl.IUserDaoImpl">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>

	<!-- 注册SpringJDBC查询模板 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 导入外部资源文件 -->
	<context:property-placeholder location="classpath:db.properties"/>

	<!-- 注册Spring自带的管理数据库连接的数据源 -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<!-- 数据库连接的四要素 -->
		<property name="driverClassName" value="${jdbc.driverClassName}"></property>
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
	<!-- 注册事务管理器 -->
	<bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 注册事务代理 -->
	<bean id="transProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<property name="transactionManager" ref="txMgr"></property>
		<property name="target" ref="userService"></property>
		<property name="transactionAttributes">
			<props>
				<prop key="oper*">ISOLATION_DEFAULT, PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>
</beans>

测试:

package com.dqsy.spring.test;

import java.math.BigDecimal;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.dqsy.spring.service.IUserService;

public class AccountTransTest {
	private ApplicationContext ctx = null;
	@Before
	public void init(){
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	}
	@Test
	public void test(){
		IUserService userService = (IUserService) ctx.getBean("transProxy");
		userService.operateMulAccount(10,11, new BigDecimal(100));
	}
	
}
结果:
是可以转的,然后在IUserService中的operatorMul方法中手动添加一个异常int a=1/0;那么结果为?
查看数据库,没有变化,说明事务起作用了.
2.基于AspectJ实现事务管理
把注册事务代理的那部分代码改为如下:
	<!-- 注册通知 -->
	<tx:advice id="txAdvice" transaction-manager="txMgr">
		<tx:attributes>
			<tx:method name="oper*" isolation="DEFAULT" propagation="REQUIRED"/>
		</tx:attributes>
	</tx:advice>
	<!--切入点与通知配置 -->
	<aop:config>
		<aop:pointcut expression="execution(* *..IUserService.*(..))" id="userPointCut"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="userPointCut"/>
	</aop:config>
测试类:
package com.dqsy.spring.test;

import java.math.BigDecimal;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.dqsy.spring.service.IUserService;

public class AccountAopTest {
	private ApplicationContext ctx = null;
	@Before
	public void init(){
		ctx = new ClassPathXmlApplicationContext("applicationContext-aspectj.xml");
	}
	@Test
	public void test(){
		IUserService userService = (IUserService) ctx.getBean("userService");
		userService.operateMulAccount(10,11, new BigDecimal(100));
	}
	
}
结果同上
3.基于注解实现事务(非常重要)
Spring 还允许简单地用 @Transactional 注解来标注事务方法。
为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解。根据 Spring AOP 基于代理机制, 只能标注公有方法。
可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的。
在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了。
如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器。
把注册事务代理的那部分代码改为如下:
	<!-- 事务管理的注解 -->
	<tx:annotation-driven transaction-manager="txMgr"/>
IUserServiceImpl中添加注解:

这是得到的结果同样是相同的

五、事务传播属性

当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为.

常用的是二种:
* PROPAGATION_REQUIRED :如果存在一个事务,则支持当前事务,如果没有事务则开启事务
* PROPAGATION_REQUIED_NEW :总是开启一个新事务

REQUIRED 传播行为

从起始点到终止点只有一个事务。要么行,要么不行。
 

REQUIRES_NEW 传播行为

它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它。

简单的理解就是把一个大的事务管理分成若干个小事务,即使,在几个小事务后面有异常,但是前面的几个事务都提交了
 
 

事务的隔离级别

从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行。
在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
事务的隔离级别可以通过隔离事务属性指定。

事务并发产生的问题:
脏读
:一个事务读取到了另外一个事务没有提交的数据
事务1:更新一条数据
------------->事务2:读取事务1更新的记录
事务1:调用commit进行提交
***此时事务2读取到的数据是保存在数据库内存中的数据,称为脏读。
***读到的数据为脏数据

不可重复读:在同一事务中,两次读取同一数据,得到内容不同
事务1:查询一条记录
-------------->事务2:更新事务1查询的记录
-------------->事务2:调用commit进行提交
事务1:再次查询上次的记录
***此时事务1对同一数据查询了两次,可得到的内容不同,称为不可重复读。

幻读:同一事务中,用同样的操作读取两次,得到的记录数不相同
事务1:查询表中所有记录
-------------->事务2:插入一条记录
-------------->事务2:调用commit进行提交
事务1:再次查询表中所有记录
***此时事务1两次查询到的记录是不一样的,称为幻读


事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持。
MySQL支持四种。前四种。
Oracle支持二种。READ_COMMITED , SERIALIZABLE

总结:

声明式事务管理:

基于TransactionFactoryProxyBean(很少使用,需要为每一个进行事务管理的类,配置一个增强生成代理。)

基于AspectJ的xml方式(常用,配置比xml复杂,但在配置文件中可以清晰看到增强的位置,并且类上不需要做任何修改。)

基于注解方式(常用,配置简单,但是需要修改业务层的类,为它们加上@Transactional注解。)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋斗的小巍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值