我有一种情况,两个线程正在尝试检查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?有没有更优雅的方式来设计这种情况?也许没有解决方案,因为线程似乎在相同的毫秒内启动。
答案 0 :(得分:0)
为了防止出现这种情况,您必须确保一次只有一个线程执行您的代码。
我认为有两种方法可以做到:
将它放在同步块中,在同一个对象上同步。这仅在您的事务完全包含在同步块中时才有效,并且您的所有代码都在一台计算机上运行。在2017年,即使是“Hello World”预计将扩展到至少6台机器,后者似乎也不合理。所以...
使数据库执行锁定。您可以从数据库中选择数据,以便在您提交事务之前无人可以访问数据。这称为SELECT FOR UPDATE
。在您的情况下,您没有要更新的行,您想要创建一行。当您使用SELECT FOR UPDATE
选择完整的表(或实际上具有唯一约束的列)时,您应该能够这样做。
不要这样做! 所描述的方法将在完整索引上创建排他锁=&gt;没有插入(这是你的观点),没有更新,没有删除,并且取决于RDBMS,可能会进一步限制什么类型的操作被此阻止。这可能会严重限制可扩展性。
因此,如果你不能将你的操作放在一个区块中并在发生故障时重试,那么在下行此路由之前需要重新考虑。容易做;没有数据库依赖;没有SQL魔法,并且假设您很少真正得到冲突,可能是更好的性能和可伸缩性。
如果你真的想这样做,上面的链接应该让你开始。您将该代码放在custom method implementation for your repository。
中