【JavaEE】Spring 事務(wù)(1)
【JavaEE】Spring 事務(wù)(1)
1. 為什么要使用事務(wù)
比如跟錢相關(guān)的兩個(gè)操作:
第一步操作:小馬卡里 - 100元
第二步操作:老馬卡里 + 100元
這就是一個(gè)事務(wù),捆在一起的一組行為,就是事務(wù)
而它能保證的是,這個(gè)行為的原子性,一致性,隔離性,持久性:
- 兩個(gè)操作都成功
- 兩個(gè)操作都失敗
要么一起成功,要么一起失敗
但是,如果沒有事務(wù)呢,則兩個(gè)操作邏輯上是分開的:
- 第一個(gè)操作成功,第二個(gè)操作失敗,則小馬直接虧了100!
2. Spring中事務(wù)的實(shí)現(xiàn)
Spring中的事務(wù)操作主要分為兩類:
- 編程式事務(wù)(原生方式去寫代碼操作事務(wù))
- 聲明式事務(wù)(利用注解,“約定規(guī)則”去自動(dòng)開啟和提交事務(wù))
2.1 事務(wù)針對(duì)哪些操作
事務(wù)一般針對(duì)的是
- 持久化相關(guān)的操作,如數(shù)據(jù)庫(kù)操作、文件系統(tǒng)操作等
正如剛才的那樣,兩個(gè)用戶的轉(zhuǎn)賬操作
- 保證數(shù)據(jù)完整性的操作,如消息隊(duì)列等
通過使用事務(wù),可以在消息隊(duì)列中提供可靠的消息傳遞機(jī)制,減少消息丟失或重復(fù)處理的可能性,同時(shí)確保系統(tǒng)在出現(xiàn)故障情況下能夠正確恢復(fù)
事務(wù)的概念適用于需要保證一系列操作的原子性和一致性的任何場(chǎng)景
- 而其中,被持久化的數(shù)據(jù),被傳播的數(shù)據(jù)…等操作,都具有 “持久性影響” 的作用,所以要通過事務(wù)來控制其影響不要太糟糕
- 而一些操作,比如打印,都打印到控制臺(tái)了,不會(huì)回滾的,也沒有必要回滾,例如查看執(zhí)行日志…
- 至于其他的不可見的操作,又沒有持久化,是沒有影響力的,程序出異常后,這些數(shù)據(jù)也銷毀了~
2.2 MySQL 事務(wù)使用
--- 開啟事務(wù)
start transaction;
--- transaction就是事務(wù)的意思
--- 提交事務(wù)
commit;
--- 回滾事務(wù)
rollback;
三個(gè)重要的操作:
- 開啟事務(wù)
- 提交事務(wù)
- 回滾事務(wù)
2.3 Spring 編程式事務(wù)(手動(dòng)擋)
與MySQL操作事務(wù)類似:
- 開啟事務(wù)(獲取一個(gè)事務(wù)/創(chuàng)建一個(gè)事務(wù)并獲取)
- 提交事務(wù)
- 回滾事務(wù)
SpringBoot 內(nèi)置了兩個(gè)對(duì)象:
- DataSourceTransactionManager ?來獲取事務(wù)(開啟事務(wù))、提交或 回滾事務(wù)的
- TransactionDefinition 是事務(wù)的屬性,在獲取事務(wù)的時(shí)候需要將 TransactionDefinition 傳遞進(jìn)去從而獲得?個(gè)事務(wù) TransactionStatus
實(shí)現(xiàn)代碼如下:
@RestController public class UserController { @Resource private UserService userService; // JDBC 事務(wù)管理器 @Resource private DataSourceTransactionManager dataSourceTransactionManager; // ------------定義事務(wù)屬性------------ @Resource private TransactionDefinition transactionDefinition; @RequestMapping("/sava") public Object save(User user) { // ------------開啟事務(wù)------------ TransactionStatus transactionStatus = dataSourceTransactionManager .getTransaction(transactionDefinition); // ------------插?數(shù)據(jù)庫(kù)------------ int result = userService.save(user); // ------------提交事務(wù)------------ dataSourceTransactionManager.commit(transactionStatus); // // ------------回滾事務(wù)------------ // dataSourceTransactionManager.rollback(transactionStatus); return result; } }
反正就是,麻煩,難記,不簡(jiǎn)潔,容易出錯(cuò)(難寫)
2.4 Spring 聲明式事務(wù)(自動(dòng)擋)
聲明式事務(wù)的實(shí)現(xiàn)很簡(jiǎn)單
- 只需要在需要的類或者方法上添加 @Transactional 注解 就可以實(shí)現(xiàn)了
無需手動(dòng)開啟/提交/回滾事務(wù):
- 進(jìn)入方法,自動(dòng)開啟事務(wù)
- 方法執(zhí)行完會(huì),自動(dòng)提交事務(wù)
- 如果中途發(fā)生了沒有處理的異常,自動(dòng)回滾事務(wù)
具體規(guī)則/作用范圍是:
- 加在類上,內(nèi)部的所有非靜態(tài)public方法都相當(dāng)于加了 @Transactional 注解
- 加在非靜態(tài)public方法上,這個(gè)方法就是一個(gè)事務(wù)
- 所在的類,必須被五大類注解修飾,這跟其事務(wù)的實(shí)現(xiàn)有關(guān)
- 而且有了五大類注解,Spring開發(fā)才能進(jìn)行呀~
代碼實(shí)現(xiàn):
@Service
@Transactional
public class Test {
@Autowired
private Mapper mapper;
public int save(User user) {
mapper.save(user);
}
}
@RequestMapping("/save")
@Transactional
public Object save(User user) {
int result = userService.save(user);
return result;
}
跟往常的注解版和不使用注解版的代碼一樣:
- 不使用注解版: 靈活,能實(shí)現(xiàn)很多功能,但是麻煩,使用困難,甚至正常人壓根沒法寫,例如事務(wù)傳播機(jī)制的代碼實(shí)現(xiàn)起來就比較復(fù)雜
- 使用注解版: 使用規(guī)則約束,實(shí)現(xiàn)特定功能,但是方便,使用簡(jiǎn)單,且足以面對(duì)正常開發(fā)環(huán)境,不關(guān)心一些極端的不正常開發(fā)
- 對(duì)于注解的使用,就是:遵循約定,坐享其成,明白邏輯(作用),合理使用(邏輯分析合理)
編程式就相當(dāng)于車的手動(dòng)擋,聲明式就相當(dāng)于車的自動(dòng)擋,那么現(xiàn)實(shí)咱們買不起偏貴的自動(dòng)擋車,而我們現(xiàn)在可以無條件舒適地使用自動(dòng)擋,那咋不用嘞??????
2.5 小疑問(@Transactional注解原理)
2.5.1 @Transactional注解原理
- 這個(gè)行為,可能你也意識(shí)到了,其實(shí)就是AOP,對(duì)@Transactional注解下的代碼,進(jìn)行統(tǒng)一的處理
- 當(dāng)然,對(duì)于不同的事務(wù)/復(fù)雜事務(wù),處理可能不同~
- 這個(gè)在執(zhí)行日志中也能看到,可以平時(shí)觀察觀察~
@Transactional 實(shí)現(xiàn)思路圖:
@Transactionl執(zhí)行思路圖:
默認(rèn)就是這么一個(gè)事務(wù)管理器執(zhí)行這樣的邏輯
- 而如果配置了多個(gè)事務(wù)管理器,則需要通過參數(shù)value/transactionManager去指定
2.5.2 為什么必須被五大類注解修飾
其實(shí)就是因?yàn)?/em>
@Transactional注解是基于Spring AOP的,而Spring AOP則通過JDK的或者CGLib的動(dòng)態(tài)代理來實(shí)現(xiàn)AOP
對(duì)于使用
@Transactional
注解來實(shí)現(xiàn)事務(wù)管理,確實(shí)是通過動(dòng)態(tài)代理來實(shí)現(xiàn)的
- 當(dāng)你在一個(gè)類或方法上添加了
@Transactional
注解時(shí),Spring會(huì)通過動(dòng)態(tài)代理在運(yùn)行時(shí)為該類或方法創(chuàng)建一個(gè)代理對(duì)象。這個(gè)代理對(duì)象會(huì)攔截調(diào)用,并在適當(dāng)?shù)臅r(shí)機(jī)開啟、提交或回滾事務(wù)由于動(dòng)態(tài)代理的實(shí)現(xiàn)方式,確實(shí)需要滿足一些條件才能使
@Transactional
注解生效
- 具體來說,被注解的類或方法必須是Spring容器中的bean,而Spring容器會(huì)自動(dòng)為標(biāo)注了
@Service
、@Controller
、@Repository
、@Component
和@Configuration
等注解的類創(chuàng)建bean實(shí)例。這也是為什么我之前提到了五大類注解
2.5.3 為什么@Transactional不支持static方法
其實(shí)就是因?yàn)?/em>
無論JDK還是CGlib都無法對(duì)靜態(tài)方法提供代理。原因在于靜態(tài)方法是類級(jí)別的,調(diào)用需要知道類信息,而類信息在編譯器就已經(jīng)知道了,并 不支持在運(yùn)行期的動(dòng)態(tài)綁定
3. 實(shí)踐
3.1 創(chuàng)建項(xiàng)目
為了方便,我就直接使用之前mybatis項(xiàng)目里寫過的代碼了
- 因?yàn)槲覀兡壳皞?cè)重學(xué)習(xí)的點(diǎn)是在事務(wù)的實(shí)現(xiàn)!
model.UserInfo:
@Component @Data public class UserInfo { private int id; private String username; private String password; private String photo; private LocalDateTime createtime; private LocalDateTime updatetime; private Integer state; public UserInfo(String username, String password, Integer state) { this.username = username; this.password = password; this.state = state; } public UserInfo() { } }
mapper.UserMapper:
@Mapper //跟五大類注解@Repository,say 拜拜 public interface UserMapper { List<UserInfo> getAll(); //獲得所有用戶信息 UserInfo getUserById(Integer id); //通過id查找用戶 UserInfo getUserByUsername(@Param("username") String username); //通過username查找用戶 List<UserInfo> getAll2(@Param("option") String option); UserInfo login(@Param("username") String username, @Param("password") String password); int update(UserInfo userInfo); //刪除狀態(tài)為state的用戶 int delete(@Param("state") Integer state); //增加用戶 int insert(UserInfo userInfo); List<UserInfo> getAllLikeSome(@Param("likeString") String likeString); //用戶注冊(cè)提交信息 // int add(UserInfo userInfo); int add(String username, String password, Integer state, Integer id); int add2(UserInfo userInfo); List<UserInfo> select1(UserInfo userInfo); int update2(UserInfo userInfo); int deleteByIDs(List<Integer> list); int insertByUsers(List<UserInfo> list, List<UserInfo> list2); }
mybatis.UserInfoMapper.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.example.demo.mapper.UserMapper"> <resultMap id="BaseMap" type="com.example.demo.model.UserInfo"> </resultMap> <select id="getAll" resultMap="BaseMap"> select * from userinfo </select> <!-- <select id="getAll" resultType="com.example.demo.model.UserInfo">--> <!-- select id, username as name, password, photo,--> <!-- createtime, updatetime, state from userinfo--> <!-- </select>--> <select id="getUserById" resultType="com.example.demo.model.UserInfo"> select * from userinfo where id = #{id} </select> <select id="getUserByUsername" resultType="com.example.demo.model.UserInfo"> select * from userinfo where username = ${username} </select> <select id="getAll2" resultType="com.example.demo.model.UserInfo"> select * from userinfo order by id ${option} </select> <select id="login" resultType="com.example.demo.model.UserInfo"> select * from userinfo where username = '${username}' and password = '${password}' </select> <update id="update"> update userinfo set state = #{state} where username = #{username} </update> <delete id="delete"> delete from userinfo where state = #{state} </delete> <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id"> <!-- 自增主鍵 id 不能為null也沒有默認(rèn)值,如果id不設(shè)置或者設(shè)置為null,都會(huì)導(dǎo)致自增 --> insert into userinfo (username, password) values (#{username}, #{password}); </insert> <select id="getAllLikeSome" resultType="com.example.demo.model.UserInfo"> select * from userinfo where username like concat('%', #{likeString}, '%') </select> <insert id="add"> insert into userinfo ( <if test="id != 0"> id, </if> <if test="username != null"> username, </if> <if test="password != null"> password, </if> <if test="state != null"> state </if> ) values ( <if test="id != 0"> #{id}, </if> <if test="username != null"> #{username}, </if> <if test="password != null"> #{password}, </if> <if test="state != null"> #{state} </if> ) </insert> <insert id="add2"> insert into userinfo <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != 0"> id, </if> <if test="username != null"> username, </if> <if test="password != null"> password, </if> <if test="state != null"> state </if> </trim> values <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != 0"> #{id}, </if> <if test="username != null"> #{username}, </if> <if test="password != null"> #{password}, </if> <if test="state != null"> #{state} </if> </trim> </insert> <select id="select1" resultType="com.example.demo.model.UserInfo"> select * from userinfo <where> <if test="id != 0"> id = #{id} </if> <if test="username != null"> or username = #{username} </if> <if test="password != null"> or password = #{password} </if> <if test="state != null"> or state = #{state} </if> </where> <!-- <trim prefix="where" prefixOverrides="and">--> <!-- <trim prefixOverrides="or">--> <!-- <if test="id != 0">--> <!-- id = #{id}--> <!-- </if>--> <!-- <if test="username != null">--> <!-- or username = #{username}--> <!-- </if>--> <!-- <if test="password != null">--> <!-- or password = #{password}--> <!-- </if>--> <!-- <if test="state != null">--> <!-- or state = #{state}--> <!-- </if>--> <!-- </trim>--> <!-- </trim>--> </select> <update id="update2"> update userinfo <set> <if test="username != null"> username = #{username}, </if> <if test="password != null"> password = #{password}, </if> <if test="state != null"> state = #{state} </if> </set> where id = #{id} </update> <delete id="deleteByIDs"> delete from userinfo where id in <foreach collection="list" open="(" close=")" item="x" separator=","> #{x} </foreach> </delete> <insert id="insertByUsers"> insert into userinfo(username, password, state) values <foreach collection="list" item="x" open="(" close=")" separator="),("> #{x.username}, #{x.password}, #{x.state} </foreach> , <foreach collection="list2" item="x" open="(" close=")" separator="),("> #{x.username}, #{x.password}, #{x.state} </foreach> </insert> </mapper>
application.properties:
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?characterEncoding=utf8 # MyBatis 基于jdbc實(shí)現(xiàn)~ 底層用的就是jdbc:mysql協(xié)議,這個(gè)地址是本地?cái)?shù)據(jù)庫(kù)的地址,test_db就是我們的那個(gè)數(shù)據(jù)庫(kù) spring.datasource.username=root # 用戶名,默認(rèn)固定是root spring.datasource.password=mmsszsd666 # 密碼,是數(shù)據(jù)庫(kù)的密碼 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MyBatis配置信息 mybatis.mapper-locations=classpath:mybatis/*Mapper.xml # 執(zhí)行時(shí)打印SQL mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #由于其默認(rèn)情況下的日志類型為Debug,重要程度不高,所以我們需要設(shè)置我們對(duì)應(yīng)的目錄下的日志級(jí)別 logging.level.com.example.demo.controller=debug #將數(shù)據(jù)庫(kù)中的下?lián)Q線轉(zhuǎn)換成駝峰,比如 user_name -> userName mybatis-plus.configuration.map-underscore-to-camel-case=true
目錄結(jié)構(gòu):
3.2 編寫代碼
同樣的controller接受請(qǐng)求,service調(diào)用方法~
加@Transactional:
3.3 測(cè)試
訪問路由前:
delete from userinfo;
現(xiàn)在userinfo一條數(shù)據(jù)都沒有了~
效果:
瀏覽器:
控制臺(tái):
數(shù)據(jù)庫(kù):
- 符合預(yù)期:還是空的
- 因?yàn)榘l(fā)生了因?yàn)锧Transactional捕獲到了異常,發(fā)生回滾
去這段代碼后,效果:
3.4 注意事項(xiàng)
@Transactional 在異常被 try{}catch(){}
捕獲的情況下,不會(huì)進(jìn)行事務(wù)自動(dòng)回滾,這也很好理解,因?yàn)?try{}catch(){}
后,后面的代碼可以繼續(xù)運(yùn)行,這個(gè)異常是被我們寫的 try{}catch(){}
搶走處理了,注解是捕獲不到的~
代碼:
效果:
瀏覽器:
控制臺(tái):
數(shù)據(jù)庫(kù):
說明沒有回滾
3.4.1 事務(wù)不會(huì)自動(dòng)回滾解決方案1:重新拋出
效果:
無新增數(shù)據(jù),代表回滾成功
但是這不太美觀,“優(yōu)雅”,過于暴力
3.4.2 事務(wù)不會(huì)自動(dòng)回滾解決方案2:手動(dòng)回滾
TransactionAspectSupport.currentTransactionStatus()
可以得到當(dāng)前的事務(wù),然后設(shè)置回滾方法setRollbackOnly
就可以實(shí)現(xiàn)將當(dāng)前事務(wù)的回滾了
- 跟切面有關(guān)=>aop
效果:
無新增數(shù)據(jù),代表回滾成功
這種方式就比較“優(yōu)雅”了~
文章到此結(jié)束!謝謝觀看
可以叫我 小馬,我可能寫的不好或者有錯(cuò)誤,但是一起加油鴨??!文章來源:http://www.zghlxwxcb.cn/news/detail-678770.html代碼:事務(wù)/src/main · 游離態(tài)/馬拉圈2023年8月 - 碼云 - 開源中國(guó) (gitee.com)文章來源地址http://www.zghlxwxcb.cn/news/detail-678770.html
到了這里,關(guān)于【JavaEE】Spring事務(wù)-事務(wù)的基本介紹-事務(wù)的實(shí)現(xiàn)-@Transactional基本介紹和使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!