我正在使用play框架来开发一个使用JOOQ和spring事务访问postgres数据库的Web应用程序。
目前我正在实施以下列方式构建的用户注册:
请求被路由到控制器操作,该操作在DTO上映射所有参数,如电子邮件,密码等。 DTO的不同字段使用JSR 303约束进行注释。
电子邮件字段使用约束验证程序进行注释,以确保不会将相同的地址添加两次。此验证程序具有对UserRepository
的自动引用的引用,因此它可以调用它的isExistingEmail
方法。
调用用户服务的注册方法,基本上如下所示:
@Transactional(isolation = Isolation.SERIALIZABLE)
public User signupUser(UserDto userDto) {
validator.validate(userDto);
userRepository.add(userDto);
return tutor;
}
如果出现验证错误,服务内部的validator.validate(userDto)
调用将抛出RuntimeException。
请注意,存储库的add
方法使用@Transactional(propagation = Propagation.MANDATORY)
注释,而isExistingEmail
方法没有任何注释。
我的问题是,当我连续两次发布注册表单时,我从数据库收到一个唯一的约束错误,因为userRepository.isExistingEmail
调用都返回false。但是,我所期望的是第二次注册调用不允许将用户添加到存储库,因为我将事务的隔离级别设置为可序列化。
这是预期的行为还是可能存在JOOQ / spring事务配置问题?
我在服务中添加了TransactionSynchronizationManager.isActualTransactionActive()
调用,以确保事务实际处于活动状态。所以这部分似乎有效。
答案 0 :(得分:0)
经过一些研究并阅读postgres手册中有关事务隔离的documentation后,我逐渐意识到我对Spring管理事务的理解是缺乏的。
将隔离级别设置为SERIALIZABLE
postgres时,确实会阻止任何并发事务。相反,它将使用谓词锁来监视已提交的事务是否会产生与实际并行运行并发事务不同的结果。
如果第二个事务尝试提交时数据的状态无效,则只会由底层数据库驱动程序抛出异常。我能够通过临时删除电子邮件字段上的唯一约束来验证此行为并强制序列化失败。
我的最终解决方案是将隔离级别降低到READ_COMMITTED
并在调用userRepository.add(userDto)
时处理唯一约束违例异常,因为SERIALIZABLE
隔离级别不是真正需要处理的这个特殊的用例。
请让我知道处理这种标准情况的更好方法。