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

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

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

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

1. 背景介绍

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

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

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

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

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

3. 原因分析

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

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

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

4. 解决办法

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

解决方法1:

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

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

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

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

解决办法2:

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

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

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

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

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

解决方法3:

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

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

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

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

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

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

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

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

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

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

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

标签:
  • 热门焦点
  • SpringBoot中使用Cache提升接口性能详解

    环境:springboot2.3.12.RELEASE + JSR107 + Ehcache + JPASpring 框架从 3.1 开始,对 Spring 应用程序提供了透明式添加缓存的支持。和事务支持一样,抽象缓存允许一致地使用各
  • 一年经验在二线城市面试后端的经验分享

    忠告这篇文章只适合2年内工作经验、甚至没有工作经验的朋友阅读。如果你是2年以上工作经验,请果断划走,对你没啥帮助~主人公这篇文章内容来自 「升职加薪」星球星友 的投稿,坐
  • 共享单车的故事讲到哪了?

    来源丨海克财经与共享充电宝相差不多,共享单车已很久没有被国内热点新闻关照到了。除了一再涨价和用户直呼用不起了。近日多家媒体再发报道称,成都、天津、郑州等地多个共享单
  • ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • 网红炒股不为了赚钱,那就是耍流氓!

    来源:首席商业评论6月26日高调宣布入市,网络名嘴大v胡锡进居然进军了股市。在一次财经媒体峰会上,几个财经圈媒体大佬就&ldquo;胡锡进炒股是否知道认真报道&rdquo;展开讨论。有
  • iQOO 11S屏幕细节公布:首发三星2K E6全感屏 安卓最好的直屏手机

    日前iQOO手机官方宣布,新一代电竞旗舰iQOO 11S将会在7月4日19:00正式与大家见面。随着发布时间的日益临近,官方关于该机的预热也更加密集,截至目前已
  • iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    在昨日举行的的联发科新一代旗舰芯片天玑9200+的发布会上,iQOO官方也正式宣布,全新的iQOO Neo8系列新品将全球首发搭载这款当前性能最强大的移动平台
  • Windows 11发布,微软一改往常对老机型开放的态度

    距离 Windows 11 发布已经过去一周,在过去一周里,很多数码爱好者围绕其对 Android 应用的支持、对老机型的升级问题展开了激烈讨论。与以往不同的是,在这次大
  • 外交部:美方应停止在网络安全问题上不负责任地指责他国

      中国外交部今天(16日)举行例行记者会。会上,有记者问,美国情报官员称,他们正在阻拦来自中国以及其他国家的黑客获取相关科研成果。 中方对此有何评论?对此
Top