Spring再认识-事务

参考文章:
1):https://blog.csdn.net/CHINACR07/article/details/78817449
2):https://blog.csdn.net/qq_38921377/article/details/72784494
3):
https://blog.csdn.net/feng20092009/article/details/70158964
4):
https://blog.csdn.net/qq_35069223/article/details/81166338

一,什么是事务(transaction)

  1. 事务的基本概念
    事务是应用程序中的一系列严密的操作,所有操作必须全部成功完成,只要其中有一个步骤失败,就会撤销所有的更改,也就是要么全部成功,要么全部失败。

  2. spring的事务管理基于底层数据库本身的事务处理机制。

3.事务的ACID四个特性

1)原子性(Atomicity)
整个事务中的所有操作是不可再分割的原子单元,事务中的所有操作要么都执行成功,要么都执行失败

2)一致性(Consistency)
事务执行时,如果出错,则应该变回原来的状态,保持一致

3)隔离性(isolation)
隔离是指在并发的操作中,不同的事务应该相互隔离,互不影响。

4)持久性(Durability)
一旦事务提交成功,事务中的所有数据操作都必须被持久化保存到数据库中,即使提交事务后,数据库崩溃,在数据库重启后,也1必须能保证通过某种机制恢复数据。

二,数据库的传播行为

事务传播行为就是多个事务方法调用时,如何定义方法间事务的传播,spring定义了以下七种传播行为

1.propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务,加入到这个事务中,这是spring默认选择。

2.propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法运行。

3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常(mandatory:强制性的)

4):propagation_required_new:新建事务,如果存在当前事务,把当前事务挂起。

5):propagation_not_support:以非事务方法执行操作,如果当前存在事务,就把事务挂起。

6):propagation_never:以非事务方式执行操作,如果当前存在事务,则抛出异常

7):propagation_nested:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与propagation_required类似的操作(nested嵌套)

三,并发时操作数据可能带来的副作用

  1. 脏读:又称无效数据读出,一个事务读取了另外一个事务还没有提交的数据,就叫做脏读。

eg:例如我使用微信支付一笔钱,但是我中途撤销了,数据库已经修改了他的数据,那么数据库的读取的数据就是脏的。

解决方法,把数据库的事务隔离级别调整为READ_COMMITTED

  1. 不可重复读:当前事务a先进行了一次数据读取,这时候事务b进入修改了数据,事务a在还没有结束的时候,再次读取到的数据是事务b修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义

解决办法:把数据库的事务隔离级别调整到REPEATABLE_READ

  1. 幻读:事务在插入已经检查过不存在的记录时,惊奇的发现这些数据已经存在了,之前的检测获取到的数据如同鬼影一般。。

eg:事务A首先根据条件索引得到n条数据,如何事务B改变了这N条数据之外的M条或者增添了M条符号事务A搜索条件的数据,导致事务A再次搜索发现又N+M条数据了,就产生了幻读。

解决办法:把数据库的事务隔离级别调整到SERIALIZABLE_READ

四,事务的隔离级别(预防上面的情况):

  1. read uncommited:最低的事务隔离级别,允许另外一个事务可以看到这个事务未提交的数据。(三种副作用都不可以预防)

  2. read commited: 保证一个事务提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。(可预防脏读)

  3. repeatable read:一个事务不会看到同一时刻另一个事务修改但尚未提交(回滚)的数据(可预防脏读和不可重复读)

  4. serializable:这种隔离级别是所有的事务都在一个执行队列中,依次顺序执行,而不是并行(可以避免三种副作用,但是效率低)

五,spring事务管理的几种实现(主要讲声明式管理)

1):编程式事务管理对基于pojo的应用来说是唯一选择,我们需要在代码调用beginTransaction(),commit(),rollback()等事务管理相关的方法,这就是编程式事务管理。
2):基于TransactionProxyFactoryBean的声明式事务管理
3):基于@Transactional的声明式事务管理
4):基于Aspectj Aop配置事务

实例

  1. 首先式pojo(省略get和set方法和构造方法)
//账户对象
public class Account{
	private int accountid;
	private String name;
	private String balance;
}

//股票对象
public class Stock{
	private int stockid;
	private String name;
	private Integer count;
}
  1. 接着是Dao层(主要写账户和股票的增加和更新)

2.1)账户方面的

public interface AccountDao{
	void addAccount(String name,double money);
	void updateAccount(String name,double money,boolean isbuy);
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{
	@Override
	public void addAcount(String name,double money){
		String sql="insert account(name,balance) value (?,?);"
		this,getjdbcTemplate().update(sql,name,money);
	}

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

2.2)股票方面的

public interface StockDao {
	
	void addStock(String sname,int count);
	
	void updateStock(String sname,int count,boolean isbuy);
 
}

public class StockDaoImpl extends JdbcDaoSupport implements StockDao {
 
	@Override
	public void addStock(String sname, int count) {
		String sql = "insert into stock(name,count) values(?,?)";
		this.getJdbcTemplate().update(sql,sname,count);
	}
 
	@Override
	public void updateStock(String sname, int count, boolean isbuy) {
		String sql = "update stock set count = count-? where name = ?";
		if(isbuy)
			sql = "update stock set count = count+? where name = ?";
		this.getJdbcTemplate().update(sql, count,sname);
	}
	
}

重点Service层

public interface BuyStockService {
 
	public void addAccount(String accountname, double money);
	
	public void addStock(String stockname, int amount);
	
	public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException;
	
}

重点

public class BuyStockServiceImpl implements BuyStockService{
	private AccountDao accountDao;
	private StockDao stockDao;

	@Override
	public void addAccount(String accountname, double money) {
		accountDao.addAccount(accountname,money);
	}
 
	@Override
	public void addStock(String stockname, int amount) {
		stockDao.addStock(stockname,amount);
	}

	@Override
	public void buystock(String accountname,double money,String stockname,int amount)throw BuyStockException{
		boolean isBuy = true;
		accountDao.updateAccount(accountname, money, isBuy);
		if(isBuy==true){
			throw new BuyStockException("购买股票发生异常");
		}
			stockDao.updateStock(stockname, amount, isBuy);
	}

	public AccountDao getAccountDao() {
		return accountDao;
	}
 
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
 
	public StockDao getStockDao() {
		return stockDao;
	}
 
	public void setStockDao(StockDao stockDao) {
		this.stockDao = stockDao;
	}

}
  1. 自定义的异常类(继承了exception,拦截所有的异常)
public class BuyStockException extends Exception {
 
	public BuyStockException() {
		super();
	}
 
	public BuyStockException(String message) {
		super(message);
	}
 
}

配置(重点)

  1. 首先是基于TransctionProxyFactoryBean的声明式事务管理。
<?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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-aop-4.2.xsd
        ">
	
	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 注册数据源 C3P0 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
		 <property name="driverClass" value="${jdbc.driverClass}"></property>
		 <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
	</bean>
	
	<bean id="accountDao" class="transaction.test2.dao.AccountDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="stockDao" class="transaction.test2.dao.StockDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="buyStockService" class="transaction.test2.service.BuyStockServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
		<property name="stockDao" ref="stockDao"></property>
	</bean>
	
	
	<!-- 事务管理器 -->
	
	<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 事务代理工厂 -->
	<!-- 生成事务代理对象 -->
	<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<property name="transactionManager" ref="myTracnsactionManager"></property>
		<property name="target" ref="buyStockService"></property>
		<property name="transactionAttributes">
			<props>
				<!-- 主要 key 是方法   
					ISOLATION_DEFAULT  事务的隔离级别
					PROPAGATION_REQUIRED  传播行为
				-->
				<prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
				<!-- -Exception 表示发生指定异常回滚,+Exception 表示发生指定异常提交 -->
				<prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
			</props>
		</property>
	</bean>
	
</beans>  

测试类(主要调用buystock方法)

	public static void main(String[] args) {
		String resouce = "transaction/test2/applicationContext.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(resouce);
		BuyStockService buyStockService =  (BuyStockService) applicationContext.getBean("serviceProxy");
 
//		buyStockService.openAccount("小郑", 5000);
		
//		buyStockService.openStock("知晓科技", 0);
		
		try {
			buyStockService.buyStock("小郑", 1000, "知晓科技", 100);
		} catch (BuyStockException e) {
			e.printStackTrace();
		}
		
	}

  1. ** 基于@Transactional的声明式事务管理(只需该百年接口实现类和配置文件)**
public class BuyStockServiceImpl implements BuyStockService{
 
	private AccountDao accountDao;
	private StockDao stockDao;
	
	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
	@Override
	public void addAccount(String accountname, double money) {
		accountDao.addAccount(accountname,money);
		
	}
 
	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
	@Override
	public void addStock(String stockname, int amount) {
		stockDao.addStock(stockname,amount);
		
	}
 
	public BuyStockServiceImpl() {
		// TODO Auto-generated constructor stub
	}
	
	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
	@Override
	public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
		boolean isBuy = true;
		accountDao.updateAccount(accountname, money, isBuy);
		if(isBuy==true){
			throw new BuyStockException("购买股票发生异常");
		}
			stockDao.updateStock(stockname, amount, isBuy);
		
	}
 
	public AccountDao getAccountDao() {
		return accountDao;
	}
 
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
 
	public StockDao getStockDao() {
		return stockDao;
	}
 
	public void setStockDao(StockDao stockDao) {
		this.stockDao = stockDao;
	}
	
}

	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 注册数据源 C3P0 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
		 <property name="driverClass" value="${jdbc.driverClass}"></property>
		 <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
	</bean>
	
	<bean id="accountDao" class="transaction.test3.dao.AccountDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="stockDao" class="transaction.test3.dao.StockDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="buyStockService" class="transaction.test3.service.BuyStockServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
		<property name="stockDao" ref="stockDao"></property>
	</bean>
	
	
	<!-- 事务管理器 -->
	<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 启用事务注解 -->
	<tx:annotation-driven transaction-manager="myTracnsactionManager"/>

可以看出,使用@Transactional注解的方式配置文件要简单的多,将事务交给事务注解驱动。它有个缺陷是他会把所有的连接点都作为切点将事务织入进去,显然只需要在buyStock()方法织入事务即可。下面看看最后一种实现,它就可以精准的织入到指定的连接点

  1. 基于Aspectj AOP配置事务(其他跟第一种方式相同,但是配置信息需要改变)
	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 注册数据源 C3P0 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
		 <property name="driverClass" value="${jdbc.driverClass}"></property>
		 <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
	</bean>
	
	<bean id="accountDao" class="transaction.test4.dao.AccountDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="stockDao" class="transaction.test4.dao.StockDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="buyStockService" class="transaction.test4.service.BuyStockServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
		<property name="stockDao" ref="stockDao"></property>
	</bean>
	
	
	<!-- 事务管理器 -->
	<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<tx:advice id="txAdvice" transaction-manager="myTracnsactionManager">
		<tx:attributes>
			<!-- 为连接点指定事务属性 -->
			<tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED"/>
			<tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="BuyStockException"/>
		</tx:attributes>
	</tx:advice>
	
	<aop:config>
		<!-- 切入点配置 -->
		<aop:pointcut expression="execution(* *..service.*.*(..))" id="point"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="point"/>
	</aop:config>

发布了36 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/s_xchenzejian/article/details/100759729