当前位置:首页 > 科技  > 软件

被简单的用户注册坑了!出现用户重复

来源: 责编: 时间:2024-01-10 09:35:10 273观看
导读环境:SpringBoot3.0.91. 背景介绍简单介绍下出现问题的场景;用户注册后,系统需要发送一封确认邮件。一旦邮件发送成功,用户的状态应更新为“已发送”。但是,在使用Spring Data JPA时,出现了重复数据的问题,注册的用户有2条。

环境:SpringBoot3.0.9YWL28资讯网——每日最新资讯28at.com

1. 背景介绍

简单介绍下出现问题的场景;用户注册后,系统需要发送一封确认邮件。一旦邮件发送成功,用户的状态应更新为“已发送”。但是,在使用Spring Data JPA时,出现了重复数据的问题,注册的用户有2条。YWL28资讯网——每日最新资讯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) ;  }  }

测试YWL28资讯网——每日最新资讯28at.com

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

控制台输出YWL28资讯网——每日最新资讯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条结果YWL28资讯网——每日最新资讯28at.com

图片图片YWL28资讯网——每日最新资讯28at.com

3. 原因分析

在保存用户后打印User对象,同时在发邮件处再次查询数据YWL28资讯网——每日最新资讯28at.com

this.userRepository.save(user) ;System.out.println(user.getId() + " ---- ")  ;// 发送邮件处查询数据user.setState(1) ;System.out.println(userRepository.findById(user.getId()).orElseGet(() -> null)) ;

执行结果YWL28资讯网——每日最新资讯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操作。也就是说在发送邮件操作时,上一步的保存用户的事务并没有提交。YWL28资讯网——每日最新资讯28at.com

4. 解决办法

在一个事务中如果你调用save方法,这时候并不会里面将数据插入到数据库中,而是会等到事务提交以后。YWL28资讯网——每日最新资讯28at.com

解决方法1:

在对应的UserRepository中重写findById的方法,然后在方法上添加共享锁    (lock in share modeYWL28资讯网——每日最新资讯28at.com

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

接下来在发送邮件的方法出调用上面的findById方法重新从数据库中拉取数据YWL28资讯网——每日最新资讯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) ;}

控制台输出YWL28资讯网——每日最新资讯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 modeYWL28资讯网——每日最新资讯28at.com

解决办法2:

缩小事务范围,不要在saveUser方法上加事务;调用的save方法内部实现是已经带有了@Transactional注解,如下:YWL28资讯网——每日最新资讯28at.com

SimpleJpaRepositoryYWL28资讯网——每日最新资讯28at.com

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

去掉了saveUser方法上的事务后,数据正常insert了一条,update一条。YWL28资讯网——每日最新资讯28at.com

该种方法实现非常的简单,但是如果saveUser方法中有多个事务操作,这时候你的通过别的方式实现。YWL28资讯网——每日最新资讯28at.com

解决方法3:

通过事件机制,该种方式有如下优点:YWL28资讯网——每日最新资讯28at.com

  • 解耦:通过事件,你可以将用户注册与发送邮件两个操作分离,使它们之间不存在直接的依赖关系。这样,如果以后需要更改邮件发送逻辑或替换为其他服务,只需要修改事件监听器,而不需要修改用户注册的代码。
  • 灵活性:事件机制提供了高度的灵活性。你可以在用户注册成功后触发多个不同的事件,每个事件可以有不同的处理逻辑。这样,你可以很容易地扩展功能,例如除了发送邮件外,还可以触发其他相关的业务逻辑。
  • 异步处理:事件处理通常是异步的,这意味着用户注册后,不需要等待邮件发送完成。这种异步处理可以提高应用的响应速度和吞吐量。
  • 可扩展性:由于事件处理是基于发布-订阅模式的,因此你可以轻松地添加新的事件监听器来扩展功能。如果以后需要集成其他服务或功能,例如发送短信、推送通知等,只需要创建相应的事件监听器即可。

实现方式如下YWL28资讯网——每日最新资讯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)) ;}

测试YWL28资讯网——每日最新资讯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=?

正确执行。YWL28资讯网——每日最新资讯28at.com

总结:在不同的线程上下文中对同一数据操作,要确保上一个事务正确的提交。否则会出现数据不一致的情况。在本例中是插入后再更新。如果是对已存在的数据做更新操作情况是一样的出现数据不一致的情况。YWL28资讯网——每日最新资讯28at.com

完毕!!!YWL28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-59646-0.html被简单的用户注册坑了!出现用户重复

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 教你如何使用 eval 函数解析和执行字符串代码,让你的程序更加智能!

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

标签:
  • 热门焦点
  • 一篇聊聊Go错误封装机制

    %w 是用于错误包装(Error Wrapping)的格式化动词。它是用于 fmt.Errorf 和 fmt.Sprintf 函数中的一个特殊格式化动词,用于将一个错误(或其他可打印的值)包装在一个新的错误中。使
  • 如何通过Python线程池实现异步编程?

    线程池的概念和基本原理线程池是一种并发处理机制,它可以在程序启动时创建一组线程,并将它们置于等待任务的状态。当任务到达时,线程池中的某个线程会被唤醒并执行任务,执行完任
  • .NET 程序的 GDI 句柄泄露的再反思

    一、背景1. 讲故事上个月我写过一篇 如何洞察 C# 程序的 GDI 句柄泄露 文章,当时用的是 GDIView + WinDbg 把问题搞定,前者用来定位泄露资源,后者用来定位泄露代码,后面有朋友反
  • 零售大模型“干中学”,攀爬数字化珠峰

    文/侯煜编辑/cc来源/华尔街科技眼对于绝大多数登山爱好者而言,攀爬珠穆朗玛峰可谓终极目标。攀登珠峰的商业路线有两条,一是尼泊尔境内的南坡路线,一是中国境内的北坡路线。相
  • 阿里瓴羊One推出背后,零售企业迎数字化新解

    作者:刘旷近年来随着数字经济的高速发展,各式各样的SaaS应用服务更是层出不穷,但本质上SaaS大多局限于单一业务流层面,对用户核心关切的增长问题等则没有提供更好的解法。在Saa
  • 三星推出Galaxy Tab S9系列平板电脑以及Galaxy Watch6系列智能手表

    2023年7月26日,三星电子正式发布了Galaxy Z Flip5与Galaxy Z Fold5。除此之外,Galaxy Tab S9系列平板电脑以及三星Galaxy Watch6系列智能手表也同期
  • 三星Galaxy Z Fold/Flip 5国行售价曝光 :最低7499元/12999元起

    据官方此前宣布,三星将于7月26日也就是明天在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy
  • Windows 11发布,微软一改往常对老机型开放的态度

    距离 Windows 11 发布已经过去一周,在过去一周里,很多数码爱好者围绕其对 Android 应用的支持、对老机型的升级问题展开了激烈讨论。与以往不同的是,在这次大
  • 北京:科技教育体验基地开始登记

      北京“科技馆之城”科技教育体验基地登记和认证工作日前启动。首批北京科技教育体验基地拟于2023年全国科普日期间挂牌,后续还将开展常态化登记。  北京科技教育体验基
Top