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

當前位置:首頁 > 科技  > 軟件

被簡單的用戶注冊坑了!出現用戶重復

來源: 責編: 時間:2024-01-10 09:35:10 205觀看
導讀環境:SpringBoot3.0.91. 背景介紹簡單介紹下出現問題的場景;用戶注冊后,系統需要發送一封確認郵件。一旦郵件發送成功,用戶的狀態應更新為“已發送”。但是,在使用Spring Data JPA時,出現了重復數據的問題,注冊的用戶有2條。

環境:SpringBoot3.0.935W28資訊網——每日最新資訊28at.com

1. 背景介紹

簡單介紹下出現問題的場景;用戶注冊后,系統需要發送一封確認郵件。一旦郵件發送成功,用戶的狀態應更新為“已發送”。但是,在使用Spring Data JPA時,出現了重復數據的問題,注冊的用戶有2條。35W28資訊網——每日最新資訊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】發送郵件%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) ;  }  }

測試35W28資訊網——每日最新資訊28at.com

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

控制臺輸出35W28資訊網——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)給【zs@qq.com】發送郵件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,數據庫中有2條結果35W28資訊網——每日最新資訊28at.com

圖片圖片35W28資訊網——每日最新資訊28at.com

3. 原因分析

在保存用戶后打印User對象,同時在發郵件處再次查詢數據35W28資訊網——每日最新資訊28at.com

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

執行結果35W28資訊網——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)22 ---- 給【zs@qq.com】發送郵件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值,但是在發送郵件再次查詢時打印的null,數據庫并沒有數據。既然沒有數據,那么調用save方法當然會執行insert操作。也就是說在發送郵件操作時,上一步的保存用戶的事務并沒有提交。35W28資訊網——每日最新資訊28at.com

4. 解決辦法

在一個事務中如果你調用save方法,這時候并不會里面將數據插入到數據庫中,而是會等到事務提交以后。35W28資訊網——每日最新資訊28at.com

解決方法1:

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

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

接下來在發送郵件的方法出調用上面的findById方法重新從數據庫中拉取數據35W28資訊網——每日最新資訊28at.com

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

控制臺輸出35W28資訊網——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)26 ---- 給【zs@qq.com】發送郵件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=?

執行的sql上自動添加了共享鎖lock in share mode35W28資訊網——每日最新資訊28at.com

解決辦法2:

縮小事務范圍,不要在saveUser方法上加事務;調用的save方法內部實現是已經帶有了@Transactional注解,如下:35W28資訊網——每日最新資訊28at.com

SimpleJpaRepository35W28資訊網——每日最新資訊28at.com

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

去掉了saveUser方法上的事務后,數據正常insert了一條,update一條。35W28資訊網——每日最新資訊28at.com

該種方法實現非常的簡單,但是如果saveUser方法中有多個事務操作,這時候你的通過別的方式實現。35W28資訊網——每日最新資訊28at.com

解決方法3:

通過事件機制,該種方式有如下優點:35W28資訊網——每日最新資訊28at.com

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

實現方式如下35W28資訊網——每日最新資訊28at.com

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

測試35W28資訊網——每日最新資訊28at.com

Hibernate: insert into t_user (email, name, state) values (?, ?, ?)40 ---- task-1 - 給【zs@qq.com】發送郵件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=?

正確執行。35W28資訊網——每日最新資訊28at.com

總結:在不同的線程上下文中對同一數據操作,要確保上一個事務正確的提交。否則會出現數據不一致的情況。在本例中是插入后再更新。如果是對已存在的數據做更新操作情況是一樣的出現數據不一致的情況。35W28資訊網——每日最新資訊28at.com

完畢!!!35W28資訊網——每日最新資訊28at.com

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

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

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

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

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 江阴市| 岑溪市| 周至县| 棋牌| 黄浦区| 安泽县| 云安县| 和林格尔县| 永和县| 宣威市| 镇远县| 长武县| 富民县| 凤台县| 梓潼县| 延寿县| 观塘区| 从化市| 临城县| 聊城市| 景宁| 建宁县| 东兰县| 岐山县| 万宁市| 施甸县| 湟源县| 绍兴市| 桐柏县| 桂东县| 万宁市| 铜山县| 报价| 镇坪县| 绥德县| 通化县| 霍州市| 封丘县| 宝清县| 镇康县| 汝州市|