日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

被簡單的用戶注冊坑了!出現(xiàn)用戶重復(fù)

來源: 責(zé)編: 時間:2024-01-10 09:35:10 204觀看
導(dǎo)讀環(huán)境:SpringBoot3.0.91. 背景介紹簡單介紹下出現(xiàn)問題的場景;用戶注冊后,系統(tǒng)需要發(fā)送一封確認(rèn)郵件。一旦郵件發(fā)送成功,用戶的狀態(tài)應(yīng)更新為“已發(fā)送”。但是,在使用Spring Data JPA時,出現(xiàn)了重復(fù)數(shù)據(jù)的問題,注冊的用戶有2條。

環(huán)境:SpringBoot3.0.929D28資訊網(wǎng)——每日最新資訊28at.com

1. 背景介紹

簡單介紹下出現(xiàn)問題的場景;用戶注冊后,系統(tǒng)需要發(fā)送一封確認(rèn)郵件。一旦郵件發(fā)送成功,用戶的狀態(tài)應(yīng)更新為“已發(fā)送”。但是,在使用Spring Data JPA時,出現(xiàn)了重復(fù)數(shù)據(jù)的問題,注冊的用戶有2條。29D28資訊網(wǎng)——每日最新資訊28at.com

2. 問題代碼

@Servicepublic class UserService {  @Resource  private UserRepository userRepository ;    private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()) ;  private final Function<User, Runnable> action = user -> () -> {    System.out.printf("給【%s】發(fā)送郵件%n", user.getEmail()) ;    user.setState(1) ;    userRepository.save(user) ;  } ;  @Transactional  public void saveUser(User user) {    this.userRepository.save(user) ;    POOL.execute(action.apply(user)) ;    // 模擬其它操作    TimeUnit.SECONDS.sleep(1) ;  }  }

測試29D28資訊網(wǎng)——每日最新資訊28at.com

@Resourceprivate UserService userService ;@Testpublic void testSave() {  User user = new User() ;  user.setName("張三") ;  user.setEmail("zs@qq.com") ;  userService.saveUser(user) ;}

控制臺輸出29D28資訊網(wǎng)——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)給【zs@qq.com】發(fā)送郵件Hibernate: select u1_0.id,u1_0.email,u1_0.name,u1_0.state from t_user u1_0 where u1_0.id=?Hibernate: insert into t_user (email, name, state) values (?, ?, ?)Hibernate: update t_user set email=?, name=?, state=? where id=?

輸出2條insert,數(shù)據(jù)庫中有2條結(jié)果29D28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片29D28資訊網(wǎng)——每日最新資訊28at.com

3. 原因分析

在保存用戶后打印User對象,同時在發(fā)郵件處再次查詢數(shù)據(jù)29D28資訊網(wǎng)——每日最新資訊28at.com

this.userRepository.save(user) ;System.out.println(user.getId() + " ---- ")  ;// 發(fā)送郵件處查詢數(shù)據(jù)user.setState(1) ;System.out.println(userRepository.findById(user.getId()).orElseGet(() -> null)) ;

執(zhí)行結(jié)果29D28資訊網(wǎng)——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)22 ---- 給【zs@qq.com】發(fā)送郵件Hibernate: select u1_0.id,u1_0.email,u1_0.name,u1_0.state from t_user u1_0 where u1_0.id=?null

打印出了User的id值,但是在發(fā)送郵件再次查詢時打印的null,數(shù)據(jù)庫并沒有數(shù)據(jù)。既然沒有數(shù)據(jù),那么調(diào)用save方法當(dāng)然會執(zhí)行insert操作。也就是說在發(fā)送郵件操作時,上一步的保存用戶的事務(wù)并沒有提交。29D28資訊網(wǎng)——每日最新資訊28at.com

4. 解決辦法

在一個事務(wù)中如果你調(diào)用save方法,這時候并不會里面將數(shù)據(jù)插入到數(shù)據(jù)庫中,而是會等到事務(wù)提交以后。29D28資訊網(wǎng)——每日最新資訊28at.com

解決方法1:

在對應(yīng)的UserRepository中重寫findById的方法,然后在方法上添加共享鎖    (lock in share mode29D28資訊網(wǎng)——每日最新資訊28at.com

public interface UserRepository extends JpaRepository<User, Long> {  @Lock(LockModeType.PESSIMISTIC_READ)  Optional<User> findById(Long id);}

接下來在發(fā)送郵件的方法出調(diào)用上面的findById方法重新從數(shù)據(jù)庫中拉取數(shù)據(jù)29D28資訊網(wǎng)——每日最新資訊28at.com

private final Function<User, Runnable> action = user -> () -> {  System.out.printf("給【%s】發(fā)送郵件%n", user.getEmail()) ;  // 由于加了鎖,所以這里會一直等待另外一個線程的事務(wù)結(jié)束或才會繼續(xù)執(zhí)行  User ret = userRepository.findById(user.getId()).get() ;  ret.setState(1) ;  userRepository.save(ret) ;}

控制臺輸出29D28資訊網(wǎng)——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)26 ---- 給【zs@qq.com】發(fā)送郵件Hibernate: select u1_0.id,u1_0.email,u1_0.name,u1_0.state from t_user u1_0 where u1_0.id=? lock in share modeHibernate: select u1_0.id,u1_0.email,u1_0.name,u1_0.state from t_user u1_0 where u1_0.id=?Hibernate: update t_user set email=?, name=?, state=? where id=?

執(zhí)行的sql上自動添加了共享鎖lock in share mode29D28資訊網(wǎng)——每日最新資訊28at.com

解決辦法2:

縮小事務(wù)范圍,不要在saveUser方法上加事務(wù);調(diào)用的save方法內(nèi)部實(shí)現(xiàn)是已經(jīng)帶有了@Transactional注解,如下:29D28資訊網(wǎng)——每日最新資訊28at.com

SimpleJpaRepository29D28資訊網(wǎng)——每日最新資訊28at.com

@Transactional@Overridepublic <S extends T> S save(S entity) {  // ...}

去掉了saveUser方法上的事務(wù)后,數(shù)據(jù)正常insert了一條,update一條。29D28資訊網(wǎng)——每日最新資訊28at.com

該種方法實(shí)現(xiàn)非常的簡單,但是如果saveUser方法中有多個事務(wù)操作,這時候你的通過別的方式實(shí)現(xiàn)。29D28資訊網(wǎng)——每日最新資訊28at.com

解決方法3:

通過事件機(jī)制,該種方式有如下優(yōu)點(diǎn):29D28資訊網(wǎng)——每日最新資訊28at.com

  • 解耦:通過事件,你可以將用戶注冊與發(fā)送郵件兩個操作分離,使它們之間不存在直接的依賴關(guān)系。這樣,如果以后需要更改郵件發(fā)送邏輯或替換為其他服務(wù),只需要修改事件監(jiān)聽器,而不需要修改用戶注冊的代碼。
  • 靈活性:事件機(jī)制提供了高度的靈活性。你可以在用戶注冊成功后觸發(fā)多個不同的事件,每個事件可以有不同的處理邏輯。這樣,你可以很容易地?cái)U(kuò)展功能,例如除了發(fā)送郵件外,還可以觸發(fā)其他相關(guān)的業(yè)務(wù)邏輯。
  • 異步處理:事件處理通常是異步的,這意味著用戶注冊后,不需要等待郵件發(fā)送完成。這種異步處理可以提高應(yīng)用的響應(yīng)速度和吞吐量。
  • 可擴(kuò)展性:由于事件處理是基于發(fā)布-訂閱模式的,因此你可以輕松地添加新的事件監(jiān)聽器來擴(kuò)展功能。如果以后需要集成其他服務(wù)或功能,例如發(fā)送短信、推送通知等,只需要創(chuàng)建相應(yīng)的事件監(jiān)聽器即可。

實(shí)現(xiàn)方式如下29D28資訊網(wǎng)——每日最新資訊28at.com

// 定義事件對象class UserCreatedEvent extends ApplicationEvent {  private static final long serialVersionUID = 1L;  private User source ;  public UserCreatedEvent(User user) {    super(user);    this.source = user ;  }}// 定義事件監(jiān)聽器// 在事務(wù)提交完成以后執(zhí)行@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)@Asyncpublic void sendMail(UserCreatedEvent event) {  User user = event.getUser();  System.out.printf("%s - 給【%s】發(fā)送郵件%n", Thread.currentThread().getName(), user.getEmail()) ;  user.setState(1);  userRepository.save(user) ;}// 在saveUser方法中需要發(fā)送事件@Transactionalpublic void saveUser(User user) {  this.userRepository.save(user) ;  eventMulticaster.multicastEvent(new UserCreatedEvent(user)) ;}

測試29D28資訊網(wǎng)——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)40 ---- task-1 - 給【zs@qq.com】發(fā)送郵件Hibernate: select u1_0.id,u1_0.email,u1_0.name,u1_0.state from t_user u1_0 where u1_0.id=?Hibernate: update t_user set email=?, name=?, state=? where id=?

正確執(zhí)行。29D28資訊網(wǎng)——每日最新資訊28at.com

總結(jié):在不同的線程上下文中對同一數(shù)據(jù)操作,要確保上一個事務(wù)正確的提交。否則會出現(xiàn)數(shù)據(jù)不一致的情況。在本例中是插入后再更新。如果是對已存在的數(shù)據(jù)做更新操作情況是一樣的出現(xiàn)數(shù)據(jù)不一致的情況。29D28資訊網(wǎng)——每日最新資訊28at.com

完畢!!!29D28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-59646-0.html被簡單的用戶注冊坑了!出現(xiàn)用戶重復(fù)

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 教你如何使用 eval 函數(shù)解析和執(zhí)行字符串代碼,讓你的程序更加智能!

下一篇: 性能工程成熟度模型

標(biāo)簽:
  • 熱門焦點(diǎn)
  • 官方承諾:K60至尊版將會首批升級MIUI 15

    全新的MIUI 15今天也有了消息,在官宣了K60至尊版將會搭載天璣9200+處理器和獨(dú)顯芯片X7的同時,Redmi給出了官方承諾,K60至尊重大更新首批升級,會首批推送MIUI 15。也就是說雖然
  • 小米官宣:2023年上半年出貨量中國第一!

    今日早間,小米電視官方微博帶來消息,稱2023年小米電視上半年出貨量達(dá)到了中國第一,同時還表示小米電視的巨屏風(fēng)暴即將開始。“公布一個好消息2023年#小米電視上半年出貨量中國
  • 天貓精靈Sound Pro體驗(yàn):智能音箱沒有音質(zhì)?來聽聽我的

    這幾年除了手機(jī)作為智能生活終端最主要的核心之外,第二個可以成為中心點(diǎn)的產(chǎn)品是什么?——是智能音箱。 手機(jī)在執(zhí)行命令的時候有兩種操作方式,手和智能語音助手,而智能音箱只
  • 6月安卓手機(jī)好評榜:魅族20 Pro蟬聯(lián)冠軍

    性能榜和性價比榜之后,我們來看最后的安卓手機(jī)好評榜,數(shù)據(jù)來源安兔兔評測,收集時間2023年6月1日至6月30日,僅限國內(nèi)市場。第一名:魅族20 Pro好評率:95%5月份的時候魅族20 Pro就是
  • 6月安卓手機(jī)性能榜:vivo/iQOO霸占旗艦排行榜前三

    2023年上半年已經(jīng)正式過去了,我們也迎來了安兔兔V10版本,在新的驍龍8Gen3和天璣9300發(fā)布之前,性能榜的榜單大體會以驍龍8Gen2和天璣9200+為主,至于那顆3.36GHz的驍龍8Gen2領(lǐng)先
  • 十個可以手動編寫的 JavaScript 數(shù)組 API

    JavaScript 中有很多API,使用得當(dāng),會很方便,省力不少。 你知道它的原理嗎? 今天這篇文章,我們將對它們進(jìn)行一次小總結(jié)。現(xiàn)在開始吧。1.forEach()forEach()用于遍歷數(shù)組接收一參
  • 2023 年的 Node.js 生態(tài)系統(tǒng)

    隨著技術(shù)的不斷演進(jìn)和創(chuàng)新,Node.js 在 2023 年達(dá)到了一個新的高度。Node.js 擁有一個龐大的生態(tài)系統(tǒng),可以幫助開發(fā)人員更快地實(shí)現(xiàn)復(fù)雜的應(yīng)用。本文就來看看 Node.js 最新的生
  • 騰訊蓋樓,字節(jié)拆墻

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之&ldquo;想重溫暴刷深淵、30+技能搭配暴搓到爽的游戲體驗(yàn)嗎?一起上晶核,即刻暴打!&rdquo;曾憑借直播騰訊旗下代理格斗游戲《DNF》一
  • 蘋果140W USB-C充電器:采用氮化鎵技術(shù)

    據(jù)10 月 30 日 9to5 Mac 消息報道,當(dāng)蘋果推出新的 MacBook Pro 2021 時,該公司還推出了新的 140W USB-C 充電器,附贈在 MacBook Pro 16 英寸機(jī)型的盒子里,也支
Top 主站蜘蛛池模板: 康平县| 金华市| 奉节县| 扬中市| 顺义区| 惠安县| 苍溪县| 眉山市| 白玉县| 松滋市| 东宁县| 呈贡县| 昌图县| 囊谦县| 上饶县| 会宁县| 汕尾市| 榆林市| 平阴县| 安乡县| 新津县| 那坡县| 白朗县| 海门市| 海晏县| 运城市| 抚松县| 泸溪县| 铜川市| 潜江市| 南澳县| 全州县| 岳池县| 合阳县| 砚山县| 成安县| 托克逊县| 楚雄市| 黎川县| 林周县| 闸北区|