Spring Data CrudRepository多线程导致“保存唯一约束”冲突

时间:2017-05-17 20:53:30

标签: multithreading spring-data spring-data-jpa

我有一种情况,两个线程正在尝试检查userId,当它没有出现时,他们尝试创建它。问题是,userId是SQL创建的,当第二个线程尝试保存时,它已经通过了“if user == null”错误检查。

User.java的相关部分:

@Column(name = "username", unique=true, length = 150)
private String username;

DemoApp.java:

            String usrName = "testuser";
            // ur is a CrudRepository from Spring Boot for Users
            User existingU = ur.findOneByUsername(usrName);  // Both Threads cannot find usrName 
            if(existingU == null){ 
                //#thread 1 is able to save the user, but thread 2 causes "unique constraint SQL error." 
                existingU = new User(usrName, "firstname", "lastname");
                ur.save(existingU); // Thread 1 succeeds here... Thread 2 fail
                lg.info("Saved new user"); // Thread 1 outputs this.
                // Thread 2 errors out on .save and crashes
            } else {
                lg.info("User found in database"); // this never happens
            }

UserRepository(对于你的变量)

import org.springframework.data.repository.CrudRepository;


public interface UserRepository extends CrudRepository<User, Long> {
    User findOneByUsername(String userName);
}

问题:如何确保CrudRepository findByUsername是最新的并且不会进入existingU == null if-statement?有没有更优雅的方式来设计这种情况?也许没有解决方案,因为线程似乎在相同的毫秒内启动。

1 个答案:

答案 0 :(得分:0)

为了防止出现这种情况,您必须确保一次只有一个线程执行您的代码。

我认为有两种方法可以做到:

  1. 将它放在同步块中,在同一个对象上同步。这仅在您的事务完全包含在同步块中时才有效,并且您的所有代码都在一台计算机上运行。在2017年,即使是“Hello World”预计将扩展到至少6台机器,后者似乎也不合理。所以...

  2. 使数据库执行锁定。您可以从数据库中选择数据,以便在您提交事务之前无人可以访问数据。这称为SELECT FOR UPDATE。在您的情况下,您没有要更新的行,您想要创建一行。当您使用SELECT FOR UPDATE选择完整的表(或实际上具有唯一约束的列)时,您应该能够这样做。

  3. 不要这样做! 所描述的方法将在完整索引上创建排他锁=&gt;没有插入(这是你的观点),没有更新,没有删除,并且取决于RDBMS,可能会进一步限制什么类型的操作被此阻止。这可能会严重限制可扩展性。

    因此,如果你不能将你的操作放在一个区块中并在发生故障时重试,那么在下行此路由之前需要重新考虑。容易做;没有数据库依赖;没有SQL魔法,并且假设您很少真正得到冲突,可能是更好的性能和可伸缩性。

    如果你真的想这样做,上面的链接应该让你开始。您将该代码放在custom method implementation for your repository