Spring的事務管理的兩種實現方式

Spring的事務管理

事務是什麼

是用戶定義的一組數據庫操作的集合,要麼全成功,要麼全失敗

事務特徵

特徵 含義
原子性 事務是不可分割的一組操作要麼全做,要麼全不做
一致性 數據庫中數據從一個一致的狀態轉爲另一個一致的狀態
隔離性 不同的事務之間互不影響
持久性 事務一旦提交對數據的影響是持久的

事務隔離級別

查看MySQL默認隔離級別

SHOW VARIABLES LIKE ‘%tx_isolation%’

級別類型
類型 概念
讀未提交 事務A和B,事務A未提交的數據,事務B可以讀到。讀取到的數據是髒數據
讀已提交 事務A提交的數據,事務B才能讀取到。避免了髒讀
可重複讀 直到事務A結束前,一直能讀到相同的數據。避免了髒讀和不可重複讀(MySQL默認隔離級別)
串行化 不允許讀寫併發操作,是最高隔離級別,寫時讀必須等待,效率低。避免幻讀

事務併發問題

前提

不考慮隔離級別引發的安全問題

併發問題
併發問題 含義
髒讀 A事務讀取到B事務未提交的數據(只要沒提交就是髒讀),因爲A事務可能回滾
不可重複讀 A事務多次讀取某數據,期間B事務對該數據做了更改並已提交,導致多次讀的數據不一致UPDATE、DELETE
幻讀 事務A讀取到了另外一個事務已經提交的數據,導致多次查詢結果不一致INSERT

Spring事務管理

  1. 編程式事務:將事務管理代碼嵌入到業務方法中來控制事務提交和回滾
  2. 聲明式事務:將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理(思想是AOP)

事務管理的核心接口

  1. Spring針對不同的持久層框架,提供接口不同的實現類。
  2. 事務管理器:PlatformTransactionManager
    – getTranscation:獲取事務狀態
    – commit:提交事務
    – rollback:回滾事務
  3. Spring並不是直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委託給了Mybatis或者其他框架來實現事務管理

Spring中的聲明式事務管理方式

基於XML的事務管理

步驟

  1. 配置事務管理器DataSourceTransactionManager,配置數據源
  2. 配置事務增強(設置事務操作的方法匹配)
  3. 配置切面(配置切入點,配置切面advice-ref,pointcut-ref)

通過在配置文件中配置事務相關的規則
AccountMapper.java

public interface AccountMapper {
	
	// 轉出
	int outMoney(@Param("username")String username,@Param("money")int money);
	
	// 轉入
	int inMoney(@Param("username")String username,@Param("money")int money);
	
}

AccountMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hpe.mapper.AccountMapper">
	
	<!-- 轉出 -->
	<update id="outMoney">
		UPDATE account SET money = money-#{money} WHERE username=#{username}
	</update>
	
	<!-- 轉入 -->
	<update id="inMoney">
		UPDATE account SET money = money+#{money} WHERE username=#{username}
	</update>
</mapper>

AccountService.java

public interface AccountService {
	
	/**
	 * 轉賬的方法
	 * @param out 轉出人
	 * @param in 轉入人
	 * @param money 轉賬金額
	 */
	void updateAccount(String out,String in,int money);
}

AccountServiceImpl.java

package com.hpe.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.hpe.mapper.AccountMapper;
@Service("accountService")
public class AccountServiceImpl implements AccountService {

	/* 實現轉賬的操作:
	 * 1.編程式事務
	 * 2.聲明式事務
	 */
	@Autowired
	private AccountMapper accountMapper;

	@Override
	public void updateAccount(String out, String in, int money) {
		// 轉出
		accountMapper.outMoney(out, money);
		// 模擬異常(算數異常)
		//int i = 10/0;
		// 轉入
		accountMapper.inMoney(in, money);
	}

}

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:context="http://www.springframework.org/schema/context"
    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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
	<!-- 開啓註解掃描 -->
	<context:component-scan base-package="com.hpe"></context:component-scan>
	
	<!-- 加載外部資源文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<!-- 配置c3p0數據源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}"></property>
		<property name="jdbcUrl" value="${jdbc.url}"></property>
		<property name="user" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
	
	<!-- 通過Spring管理SqlSessionFactory mapper接口 -->
	<!-- 配置SqlSessionFactory爲了使用 Spring來管理SqlSessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 配置mybatis核心配置文件的路徑 -->
		<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
		<!-- 指定數據源 -->
		<property name="dataSource" ref="dataSource"></property>
		<!-- 指定批量創建別名的包 -->
		<property name="typeAliasesPackage" value="com.hpe.po"></property>
	</bean>
	
	<!-- 批量創建Mapper接口實現類的Bean,可以不指定id
		 默認Bean是由id的,是Mapper接口的名稱且首字母小寫-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 指定需要創建實現類的Mapper接口所在的包
			 可以指定多個包,使用逗號隔開即可-->
		<property name="basePackage" value="com.hpe.mapper"></property>
		<!-- 需要指定SqlSessionFactory,使用sqlSessionFactoryBeanName
			 通過這種方式就可以等到Spring初始化完成後,再去構造SqlSessionFactory,否則無法連接數據庫 -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
	</bean>
	
	<!-- Spring聲明式事務管理:XML方式 -->
	<!-- 1.配置事務管理器(XML及註解方式均需配置) -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入數據源 -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
		
	<!-- 2.配置事務增強:需要編寫切入點及細節 -->
	<!-- propagation:事務的傳播行爲
		   REQUIRED:必須的,如果有事務則使用,沒有則創建
		   SUPPORTS:可選的,如果有事務則使用,沒有不使用
	 	 isolation:事務的隔離級別
	 	   DEFAULT:mysql默認隔離級別(可重複讀)
	 	         讀未提交
	 	         讀已提交
	 	         可串行化:可避免幻讀
	 	 rollback-for:回滾(對哪寫異常回滾)
	 	 no-rollback-for:不回滾(對哪些異常不回滾)
	 	   注意: 如果不設置默認的話,是對運行時異常進行回滾
	 	 read-only:如果將select設置爲true,則告訴數據庫引擎查詢是隻讀的。
	 	 timeout=-1:過期時間(設置多長時間不回滾)
	 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 設置進行事務操作方法匹配的規則(規範程序員的命名規則) -->
			<tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT"/>
			<tx:method name="update*" propagation="REQUIRED"/>
			<tx:method name="delete*" propagation="REQUIRED"/>
			<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 3.配置切面 -->
	<aop:config>
		<!-- 配置切入點 -->
		<aop:pointcut expression="execution(* com.hpe.service.*.*(..))" id="pointcut1"/>
		<!-- 配置切面(應用增強)
			 advice-ref:使用哪個增強
			 pointcut-ref:將增強應用到哪個切入點上面(也可以使用pointcut指定相應的表達式即可)
		 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
	</aop:config>
	
</beans>

Test.java

public class SpringTest {

	// 使用事務模擬轉賬
	@Test
	public void test1(){
		// 1.加載Spring的配置文件
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 2.創建UserService對象
		AccountService accountService = context.getBean(AccountService.class);
		// 3.調用mapper中的方法
		accountService.updateAccount("張三", "李四", 100);
	}	
}
基於註解的事務管理

步驟

  1. 配置事務管理器DataSourceTransactionManager,配置數據源
  2. 配置事務相關的註解
  3. 在使用事務的方法或者類上面添加註解

代碼實戰
AccountMapper.java、AccountMapper.xml、AccountService.java相比於上面並未改動(此處不再貼重複代碼)
AccountServiceImpl2.java

package com.hpe.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.hpe.mapper.AccountMapper;
@Service("accountService2")
// @Transactional:表示事務的設置對這個類的所有方法都起作用
//@Transactional
public class AccountServiceImpl2 implements AccountService {

	@Autowired
	private AccountMapper accountMapper;

	// @Transactional:表示事務的設置只對這個方法起作用
	@Transactional
	@Override
	public void updateAccount(String out, String in, int money) {
		// 轉出
		accountMapper.outMoney(out, money);
		// 模擬異常(算數異常)
		//int i = 10/0;
		// 轉入
		accountMapper.inMoney(in, money);
	}

}

applicationContext2.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:context="http://www.springframework.org/schema/context"
    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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
	<!-- 開啓註解掃描 -->
	<context:component-scan base-package="com.hpe"></context:component-scan>
	
	<!-- 加載外部資源文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<!-- 配置c3p0數據源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}"></property>
		<property name="jdbcUrl" value="${jdbc.url}"></property>
		<property name="user" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
	
	<!-- 通過Spring管理SqlSessionFactory mapper接口 -->
	<!-- 配置SqlSessionFactory爲了使用 Spring來管理SqlSessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 配置mybatis核心配置文件的路徑 -->
		<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
		<!-- 指定數據源 -->
		<property name="dataSource" ref="dataSource"></property>
		<!-- 指定批量創建別名的包 -->
		<property name="typeAliasesPackage" value="com.hpe.po"></property>
	</bean>
	
	<!-- 批量創建Mapper接口實現類的Bean,可以不指定id
		 默認Bean是由id的,是Mapper接口的名稱且首字母小寫-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 指定需要創建實現類的Mapper接口所在的包
			 可以指定多個包,使用逗號隔開即可-->
		<property name="basePackage" value="com.hpe.mapper"></property>
		<!-- 需要指定SqlSessionFactory,使用sqlSessionFactoryBeanName
			 通過這種方式就可以等到Spring初始化完成後,再去構造SqlSessionFactory,否則無法連接數據庫 -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
	</bean>
	
	<!-- Spring聲明式事務管理:註解方式 -->
	<!-- 1.配置事務管理器(XML及註解方式均需配置) -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入數據源 -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
		
	<!-- 2.註冊事務註解驅動
		 transaction-manager:自動檢測事務處理 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
	
</beans>

測試方法

@Test
public void test2(){
	// 1.加載Spring的配置文件
	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
	// 2.創建UserService對象
	AccountService accountService = context.getBean("accountService2",AccountService.class);
	// 3.調用mapper中的方法
	accountService.updateAccount("張三", "李四", 100);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章