基礎架構 spring事務的傳播機制與隔離級别
-
- 基礎架構 spring事務的傳播機制與隔離級别
-
- 一、概念
-
- 1. 什麼是事務
- 2. 為什麼用事務
- 3. 事務四個特性(ACID)
- 4. 事務管理分類
- 二、聲明式事務-注解方式
-
- 1、在 spring 配置檔案配置事務管理器
- 2、 在 spring 配置檔案,開啟事務注解
- 3、在 service 類上( 或者 service 類裡面方法上面)添加事務注解
- 三、@Transactional參數詳解
-
- 注意事項
- Transactional參數
- 1. propagation:7事務傳播行為
- 2. isolation:4種事務隔離級别。
- 3. timeout:逾時時間。
- 4. readOnly:是否隻讀。
- 5. rollbackFor:復原。
- 6. noRollbackFor:不會滾。
- 四、聲明式事務-xml方式
- 五、案例
基礎架構 spring事務的傳播機制與隔離級别
一、概念
1. 什麼是事務
事務是資料庫操作最基本單元,邏輯上一組操作,要麼都成功,如果有一個失敗所有操
作都失敗。
2. 為什麼用事務
舉個經典例子,銀行轉賬,A給B轉100塊錢,這是邏輯上一組操作,但是具體是兩個操作,第一步是A減少100塊錢,第二步是B增加100塊錢,正常情況兩部完成各自送出沒問題,問題是當A減少100塊錢,B還未增加的時候系統挂了,就會導緻資料不一緻問題,這就是事務要解決的問題。
3. 事務四個特性(ACID)
原子性,一緻性,隔離性,持久性
4. 事務管理分類
事務管理兩種方式:程式設計式事務管理 和 聲明式事務管理
聲明式事務管理:
(1)基于注解方式(使用)
(2)基于 xml 配置檔案方式
二、聲明式事務-注解方式
1、在 spring 配置檔案配置事務管理器
<!--建立事務管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、 在 spring 配置檔案,開啟事務注解
<!-- (1)在 spring 配置檔案引入名稱空間 tx -->
<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.xsdhttp://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- (2)開啟事務注解 -->
<!--開啟事務注解-->
<tx:annotation-driven transactionmanager="transactionManager">< /tx:annotation-driven>
3、在 service 類上( 或者 service 類裡面方法上面)添加事務注解
(1)@Transactional,這個注解添加到類上面,也可以添加方法上面
(2)如果把這個注解添加類上面,這個類裡面所有的方法都添加事務
(3)如果把這個注解添加方法上面,為這個方法添加事務。
三、@Transactional參數詳解
注意事項
SpringBoot使用事物
@RestController
@EnableTransactionManagement // 開啟事務管理
public class TransactionalController {}
1. @EnableTransactionManagement注解其實在大多數情況下,不是必須的,最好在啟動類加上注解管理@EnableTransactionManagement。因為SpringBoot在 TransactionAutoConfiguration類裡為我們自動配置啟用了@EnableTransactionManagement注解。
2. 一般在service實作類添加注解@Transactional,添加注解的時候可以采用類上添加或者方法上添加,最好芳芳上,因為整個類中可能不是所有方法都需要開啟事務,比如查詢的方法不需要。
3. Transactional 注解隻能應用到 public 可見度的方法上。 如果應用在protected、private或者 package可見度的方法上,也不會報錯,不過事務設定不會起作用。
4. 預設情況下,Transactional 注解的事物所管理的方法中。
如果方法抛出運作時異常或error,那麼會進行事務復原;
如果方法抛出的是非運作時異常,那麼不會復原。
注:SQL異常屬于檢查異常(有的架構将SQL異常重寫為了運作時異常),但是有時我們寫SQL時,檢查異常并不會提示;而預設情況下,事物對檢查異常不會作出復原處理。
注:在很多時候,我們除了catch一般的異常或自定義異常外,我們還習慣于catch住Exception異常;然後再抛出Exception異常。但是Exception異常屬于非運作時異常(即:檢查異常),因為預設是運作時異常時事物才進行復原,那麼這種情況下,是不會復原的。我們可以在@Transacional注解中,通過rollbackFor = {Exception.class} 來解決這個問題。即:設定當Exception異常或Exception的所有任意子類異常時事物會進行復原。
注:被catch處理了的異常,不會被事物作為判斷依據;如果異常被catch 了,但是又在catch中抛出了新的異常,那麼事物會以這個新的異常作 為是否進行復原的判斷依據。
Transactional參數
Transactional參數 | 說明 |
---|---|
propagation | 事務的傳播行為,預設值為 REQUIRED。 |
isolation | 事務的隔離度,預設值采用 DEFAULT |
timeout | 事務的逾時時間,預設值為-1,不逾時。如果設定了逾時時間(機關秒),那麼如果超過該時間限制了但事務還沒有完成,則自動復原事務。 |
readOnly | 指定事務是否為隻讀事務,預設值為 false;為了忽略那些不需要事務的方法,比如讀取資料,可以設定 read-only 為 true。 |
rollbackFor | 復原,用于指定能夠觸發事務復原的異常類型,如果有多個異常類型需要指定,各類型之間可以通過逗号分隔。{Exception.class, RunTimeException.class,……} |
noRollbackFor | 不會滾 ,抛出 no-rollback-for 指定的異常類型,不復原事務。{xxx1.class, xxx2.class,……} |
1. propagation:7事務傳播行為

spring7種事務傳播機制
2. isolation:4種事務隔離級别。
3. timeout:逾時時間。
(1)事務需要在一定時間内送出,如果不送出進行復原
(2)預設值是-1,設定時間以秒為機關進行計算。
4. readOnly:是否隻讀。
(1)讀:查詢操作。寫:添加修改删除操作
(2)readOnly預設值是false,表示可以查詢,可以添加删除修改操作。
(3)設定readOnly值是true,表示隻能查詢操作。
5. rollbackFor:復原。
(1)設定出現哪些異常進行事務復原。
6. noRollbackFor:不會滾。
(1)設定出現哪些異常不進行事務復原。
四、聲明式事務-xml方式
1、在 spring 配置檔案中進行配置
第一步 配置事務管理器
第二步 配置通知第三步 配置切入點和切面
<!--1 建立事務管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事務參數-->
<tx:attributes>
<!--指定哪種規則的方法上面添加事務-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入點和切面-->
<aop:config>
<!--配置切入點-->
<aop:pointcut id="pt" expression="execution(*
com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
五、案例
package com.spring.dao;
/**
* 使用者DAO
*
* @author zrj
* @date 2020/12/20
* @since V1.0
**/
public interface UserDao {
/**
* 多錢
*/
void addMoney();
/**
* 少錢
*/
void reduceMoney();
}
package com.spring.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
* 使用者DAO實作類
*
* @author zrj
* @date 2020/12/20
* @since V1.0
**/
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* lucy轉賬100給mary
* 少錢
*/
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update( sql, 100, "lucy" );
}
/**
* 多錢
*/
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update( sql, 100, "mary" );
}
}
package com.spring.service;
import com.spring.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* 使用者service
*
* @author zrj
* @date 2020/12/20
* @since V1.0
**/
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private DataSourceTransactionManager transactionManager;
/**
* 1.程式設計式事務管理
* 手動處理事務,異常事務復原
*/
@Transactional
public void transferAccounts() {
// 開啟事務
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName( "SomeTxName" );
def.setPropagationBehavior( TransactionDefinition.PROPAGATION_REQUIRED );
TransactionStatus status = transactionManager.getTransaction( def );
try {
userDao.reduceMoney();
//模拟異常
int i = 10 / 0;
//mary多100
userDao.addMoney();
// 送出事務
transactionManager.commit( status );
} catch (Exception ex) {
// 事務復原
transactionManager.rollback( status );
throw ex;
}
}
/**
* 2. 聲明式事務,注解方式
* 配置檔案需要建立事務,開啟事務
* 方法異常事務復原,如果捕獲異常tryCatch則不會抛出異常,不會復原
*/
@Transactional(rollbackFor = Exception.class)
public void accountMoney() {
//lucy少100
userDao.reduceMoney();
//模拟異常
int i = 10 / 0;
//mary多100
userDao.addMoney();
}
/**
* 3. 聲明式事務,xml配置方式
*/
public void accountMoneyXml() {
//lucy少100
userDao.reduceMoney();
//模拟異常
int i = 10 / 0;
//mary多100
userDao.addMoney();
}
/**
* 4. 聲明式事務,Config配置方式
*/
@Transactional
public void accountMoneyConfig() {
//lucy少100
userDao.reduceMoney();
//模拟異常
//int i = 10 / 0;
//mary多100
userDao.addMoney();
}
}
package com.spring.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
* spring配置類
*
* @author zrj
* @date 2020/12/21
* @since V1.0
**/
@Configuration //配置類
@ComponentScan(basePackages = "com.spring") //元件掃描
@EnableTransactionManagement //開啟事務
public class TxConfig {
/**
* 建立資料庫連接配接池
* @return
*/
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName( "com.mysql.jdbc.Driver" );
dataSource.setUrl( "jdbc:mysql:///user_db" );
dataSource.setUsername( "root" );
dataSource.setPassword( "123456" );
return dataSource;
}
/**
* 建立JdbcTemplate對象
* @param dataSource
* @return
*/
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到ioc容器中根據類型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource( dataSource );
return jdbcTemplate;
}
/**
* 建立事務管理器
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource( dataSource );
return transactionManager;
}
}
bean1
<?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.atguigu"></context:component-scan>
<!-- 資料庫連接配接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- JdbcTemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--建立事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--開啟事務注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
bean2
<?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.spring"></context:component-scan>
<!-- 資料庫連接配接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
</bean>
<!-- JdbcTemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1 建立事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事務參數-->
<tx:attributes>
<!--指定哪種規則的方法上面添加事務-->
<tx:method name="accountMoneyXml" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入點和切面-->
<aop:config>
<!--配置切入點-->
<aop:pointcut id="pt" expression="execution(* com.spring.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>
package com.spring.test;
import com.spring.config.TxConfig;
import com.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* spring事務測試類
*
* @author zrj
* @date 2020/12/21
* @since V1.0
**/
public class SpringTransactionalTest {
/**
* 程式設計式事務測試
*/
@Test
public void transferAccountsTest() {
ApplicationContext context = new ClassPathXmlApplicationContext( "bean1.xml" );
UserService userService = context.getBean( "userService", UserService.class );
userService.transferAccounts();
}
/**
* 聲明式事務測試,注解方式
*/
@Test
public void accountMoneyTest() {
ApplicationContext context = new ClassPathXmlApplicationContext( "bean1.xml" );
UserService userService = context.getBean( "userService", UserService.class );
userService.accountMoney();
}
/**
* 聲明式事務測試,xml配置方式
*/
@Test
public void accountMoneyXmlTest() {
ApplicationContext context = new ClassPathXmlApplicationContext( "bean2.xml" );
UserService userService = context.getBean( "userService", UserService.class );
userService.accountMoneyXml();
}
/**
* 聲明式事務測試,注解方式,配置類
*/
@Test
public void accountMoneyConfigTest() {
ApplicationContext context = new AnnotationConfigApplicationContext( TxConfig.class );
UserService userService = context.getBean( "userService", UserService.class );
userService.accountMoneyConfig();
}
}