Spring事務(wù)管理詳細(xì)講解
說明:基于atguigu學(xué)習(xí)筆記。
事務(wù)回顧
事務(wù)是邏輯上的一組數(shù)據(jù)庫操作,要么都執(zhí)行,要么都不執(zhí)行。
假如,張三給李四轉(zhuǎn)賬100元,轉(zhuǎn)賬行為歐兩個(gè)關(guān)鍵操作:將張三的余額減200元,將李四的余額增加200元。如果兩個(gè)操作之間突然出現(xiàn)錯(cuò)誤,例如銀行系統(tǒng)崩潰導(dǎo)致張三余額減少,而李四的余額沒有增加,這樣的系統(tǒng)是有問題的。事務(wù)就是保證這兩個(gè)關(guān)鍵操作要么都成功,要么都要失敗。
事務(wù)的特性:
- 事務(wù)是最小的執(zhí)行單位,不允許分割。事務(wù)的原子性確保動(dòng)作要么全部完成,要么完全不起作用;
- 一致性: 確保從一個(gè)正確的狀態(tài)轉(zhuǎn)換到另外一個(gè)正確的狀態(tài),這就是一致性;
- 并發(fā)訪問數(shù)據(jù)庫時(shí),一個(gè)用戶的事務(wù)不被其他事務(wù)所干擾,各并發(fā)事務(wù)之間數(shù)據(jù)庫是獨(dú)立的;
- 持久性:一個(gè)事務(wù)被提交之后,對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響。
spring事務(wù)操作
在 Spring 進(jìn)行事務(wù)管理操作,編程式事務(wù)管理和聲明式事務(wù)管理。編程式事務(wù)管理一般是代碼手動(dòng)控制,比較繁瑣,所以一般推薦使用聲明式事務(wù)管理。
為了講解事務(wù)的機(jī)制,以銀行在那個(gè)轉(zhuǎn)賬為例,創(chuàng)建服務(wù)類、dao類,如下:
dao接口:
package com.example.dao; public interface UserDao { /** * 取錢 */ void reduceMoney(); /** * 存錢 */ void addMoney(); }
dao接口實(shí)現(xiàn)類
package com.example.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; @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"); } }
service類
package com.example.service; import com.example.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserDao userDao; /** * 模擬轉(zhuǎn)賬操作 */ public void accountMoney() { //lucy 少 100 userDao.reduceMoney(); //mary 多 100 userDao.addMoney(); } }
其中accountMoney方法模擬了轉(zhuǎn)賬操作。
基于注解聲明事務(wù)
在 Spring 進(jìn)行聲明式事務(wù)管理,使用注解@Transactional注解,底層使用 AOP 原理。其本質(zhì)是對方法前后進(jìn)行攔截,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個(gè)事務(wù),執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行的情況提交或者回滾。
@Transactional注解使用
下面使用注解@Transactional來模擬銀行轉(zhuǎn)賬開啟事務(wù)。
1.配置事務(wù)管理器
配置文件如下:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入數(shù)據(jù)源--> <property name="dataSource" ref="dataSource"></property> </bean>
2.在 spring 配置文件引入名稱空間 tx
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> </beans>
3.配置文件中開啟事務(wù)注解
<!--開啟事務(wù)注解--> <tx:annotation-driven transactionmanager="transactionManager"></tx:annotation-driven>
整體的配置文件如下:
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 開啟注解掃描 --> <context:component-scan base-package="com.example"></context:component-scan> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/cys-test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> </bean> <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"> <!--注入數(shù)據(jù)源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--開啟事務(wù)--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
4.在 service 類上面添加事務(wù)注解
或者 service 類里面方法上面添加事務(wù)注解(@Transactional)。
如果把這個(gè)注解添加類上面,這個(gè)類里面所有的方法都添加事務(wù),如果把這個(gè)注解添加方法上面,為這個(gè)方法添加事務(wù)。
package com.example.service; import com.example.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional public class UserService { @Autowired private UserDao userDao; /** * 模擬轉(zhuǎn)賬操作 */ public void accountMoney() { System.out.println("開始轉(zhuǎn)賬"); //lucy 少 100 userDao.reduceMoney(); // 模擬中間異常 int i = 10/0; //mary 多 100 userDao.addMoney(); System.out.println("轉(zhuǎn)賬結(jié)束"); } }
其中再轉(zhuǎn)賬的過程中使用除數(shù)為0的異常來模擬一同出現(xiàn)異常。
5.測試類
package com.example.test; import com.example.service.UserService; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test01 { @Test public void testAccount() { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = app.getBean(UserService.class); userService.accountMoney(); } }
經(jīng)過測試結(jié)果就是:如果不開啟事務(wù),中間出現(xiàn)異常,前面已經(jīng)執(zhí)行的操作就會(huì)提交到數(shù)據(jù)庫,這樣上面的案例的結(jié)果就是lucy的錢少了100,但是mary的錢并沒有增多。如果開啟事務(wù),則lucy的錢少了100和mary的錢加100是一個(gè)原子操作,中間出現(xiàn)異常就會(huì)把全部操作回滾。
事務(wù)傳播機(jī)制
當(dāng)事務(wù)方法被另外一個(gè)事務(wù)方法調(diào)用時(shí),必須指定事務(wù)應(yīng)該如何傳播,例如,方法可能繼續(xù)在當(dāng)前事務(wù)中執(zhí)行,也可以開啟一個(gè)新的事務(wù),在自己的事務(wù)中執(zhí)行。
聲明式事務(wù)的傳播行為可以通過 @Transactional 注解中的 propagation 屬性來定義,格式如下:
@Service @Transactional(propagation = Propagation.REQUIRED) public class UserService { }
Propagation屬性有如下幾個(gè)值,其中PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW 兩種傳播行為是比較常用。
傳播行為 | 描述 |
---|---|
PROPAGATION_REQUIRED | 默認(rèn)的Spring事物傳播級別,若當(dāng)前存在事務(wù),則加入該事務(wù),若不存在事務(wù),則新建一個(gè)事務(wù) |
PROPAGATION_REQUIRE_NEW | 若當(dāng)前沒有事務(wù),則新建一個(gè)事務(wù)。若當(dāng)前存在事務(wù),則新建 一個(gè)事務(wù),新老事務(wù)相互獨(dú)立。外部事務(wù)拋出異?;貪L不會(huì)影響內(nèi)部事務(wù)的正常提交 |
PROPAGATION_NESTED | 如果當(dāng)前存在事務(wù),則嵌套在當(dāng)前事務(wù)中執(zhí)行。如果當(dāng)前沒有事務(wù), 則新建一個(gè)事務(wù),類似于REQUIRE_NEW |
PROPAGATION_SUPPORTS | 支持當(dāng)前事務(wù),若當(dāng)前不存在事務(wù),以非事務(wù)的方式執(zhí)行 |
PROPAGATION_NOT_SUPPORTED | 以非事務(wù)的方式執(zhí)行,若當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起 |
PROPAGATION_MANDATORY | 強(qiáng)制事務(wù)執(zhí)行,若當(dāng)前不存在事務(wù),則拋出異常 |
PROPAGATION_NEVER | 以非事務(wù)的方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常 |
事務(wù)隔離級別
@Transactional 注解中的 isolation 屬性來定義事務(wù)的隔離級別,有以下五種隔離級別:
- ISOLATION_DEFAULT,使用數(shù)據(jù)庫默認(rèn)的隔離級別,MySql 默認(rèn)采用的是REPEATABLE_READ,也就是可重復(fù)讀。
- ISOLATION_READ_UNCOMMITTED,最低的隔離級別,可能會(huì)出現(xiàn)臟讀、幻讀或者不可重復(fù)讀。
- ISOLATION_READ_COMMITTED,允許讀取并發(fā)事務(wù)提交的數(shù)據(jù),可以防止臟讀,但幻讀和不可重復(fù)讀仍然有可能發(fā)生。
- ISOLATION_REPEATABLE_READ,對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被自身事務(wù)所修改的,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生。
- ISOLATION_SERIALIZABLE,最高的隔離級別,雖然可以阻止臟讀、幻讀和不可重復(fù)讀,但會(huì)嚴(yán)重影響程序性能。
通常情況下,采用默認(rèn)的隔離級別 ISOLATION_DEFAULT 就可以了,也就是交給數(shù)據(jù)庫來決定。
@Transactional其他屬性
1、timeout:超時(shí)時(shí)間
(1)事務(wù)需要在一定時(shí)間內(nèi)進(jìn)行提交,如果不提交進(jìn)行回滾
(2)默認(rèn)值是 -1 ,設(shè)置時(shí)間以秒單位進(jìn)行計(jì)算
2、readOnly:是否只讀
(1)讀:查詢操作,寫:添加修改刪除操作
(2)readOnly 默認(rèn)值 false,表示可以查詢,可以添加修改刪除操作
(3)設(shè)置 readOnly 值是 true,設(shè)置成 true 之后,只能查詢
3、rollbackFor:回滾
(1)設(shè)置出現(xiàn)哪些異常進(jìn)行事務(wù)回滾
4、noRollbackFor:不回滾
(1)設(shè)置出現(xiàn)哪些異常不進(jìn)行事務(wù)回滾
基于XML 聲明式事務(wù)
步驟:
第一步 配置事務(wù)管理器
第二步 配置通知
第三步 配置切入點(diǎn)和切面
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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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/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"> <!-- 開啟注解掃描 --> <context:component-scan base-package="com.example"></context:component-scan> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/cys-test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入 dataSource--> <property name="dataSource" ref="dataSource"></property> </bean> <!--創(chuàng)建事務(wù)管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入數(shù)據(jù)源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--2 配置通知--> <tx:advice id="txadvice"> <!--配置事務(wù)參數(shù)--> <tx:attributes> <!--指定哪種規(guī)則的方法上面添加事務(wù)--> <tx:method name="accountMoney" propagation="REQUIRED"/> <!--<tx:method name="account*"/>--> </tx:attributes> </tx:advice> <!--3 配置切入點(diǎn)和切面--> <aop:config> <!--配置切入點(diǎn)--> <aop:pointcut id="pt" expression="execution(*com.atguigu.spring5.service.UserService.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/> </aop:config> </beans>
完全注解開發(fā)
創(chuàng)建配置類,使用配置類替代 xml 配置文件
package com.example.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; @Configuration //配置類 @ComponentScan(basePackages = "com.example") //組件掃描 @EnableTransactionManagement //開啟事務(wù) public class TxConfig { //創(chuàng)建數(shù)據(jù)庫連接池 @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("root"); return dataSource; } //創(chuàng)建 JdbcTemplate 對象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource) { //到 ioc 容器中根據(jù)類型找到 dataSource JdbcTemplate jdbcTemplate = new JdbcTemplate(); //注入 dataSource jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } //創(chuàng)建事務(wù)管理器 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
到此這篇關(guān)于Spring事務(wù)管理詳細(xì)講解的文章就介紹到這了,更多相關(guān)Spring事務(wù)管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java獲取年月日(格式:xxxx年xx月xx日)的方法詳解
在開發(fā)應(yīng)用程序時(shí),經(jīng)常需要獲取當(dāng)前的年、月、日,并以特定格式進(jìn)行展示或處理,本文將介紹如何獲取年月日,并將其格式化為“xxxx年xx月xx日”的形式,幫助你在應(yīng)用程序中處理日期信息,需要的朋友可以參考下2023-10-10Java 常用類解析:java異常機(jī)制,異常棧,異常處理方式,異常鏈,異常丟失詳解
這篇文章主要介紹了Java 常用類解析:java異常機(jī)制,異常棧,異常處理方式,異常鏈,異常丟失詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03java進(jìn)制轉(zhuǎn)換工具類實(shí)現(xiàn)減少參數(shù)長度
這篇文章主要為大家介紹了java進(jìn)制轉(zhuǎn)換工具類實(shí)現(xiàn)減少參數(shù)長度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02spring?boot?Slf4j日志框架的體系結(jié)構(gòu)詳解
在項(xiàng)目開發(fā)中記錄日志是必做的一件事情,springboot內(nèi)置了slf4j日志框架,下面這篇文章主要給大家介紹了關(guān)于spring?boot?Slf4j日志框架的體系結(jié)構(gòu),需要的朋友可以參考下2022-05-05Java?@SpringBootApplication注解深入解析
這篇文章主要給大家介紹了關(guān)于Java?@SpringBootApplication注解的相關(guān)資料,@SpringBootApplication這個(gè)注解是Spring?Boot項(xiàng)目的基石,創(chuàng)建SpringBoot項(xiàng)目之后會(huì)默認(rèn)在主類加上,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02