java - 如果spring数据JPA不存在,如何处理并发创建
问题描述
我在我的 java-springboot 项目中使用 spring data JPA。我有一种方法可以处理许多相互关联的实体(其中一些是延迟加载的),并且该方法由多个线程同时运行。我的问题是 - 当说线程 T1 来并尝试为作者创建一本新书时,它会检查数据库中是否已经存在书籍信息并开始创建新记录,同时线程 T2 也尝试相同和开始创建相同的资源,但是在持久化时,它得到 DataIntegrtyViolation 异常。
@Entity
@Table(name = "BookInfo", uniqueConstraints = @UniqueConstraint(columnNames = {
"publisherName",
"bookTitle", "authorName" }), indexes = {
@Index(name = "BookInfoProviderIndex", columnList = "publisherName")
})
public class BookInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY/* , generator="table_gen" */)
private Long id;
@Column(name = "publisherName", nullable = false)
private String publisherName;
@Column(name = "bookTitle")
private String bookTitle;
@Column(name = "authorName")
private String authorName;
存储库 -
@Repository
public interface BookInfoRepository extends PagingAndSortingRepository<BookInfo, Long> {
public BookInfo findFirstByPublisherNameAndBookTitleAndAuthorName(String publisherName,
String bookTitle, String authorName);
}
以及导致问题的方法 -
@Service
public class AuthorService{
@Autowired
BookInfoRepository bookRepo;
@Transactional
public createAutoBiography(AuthorDTO dto){
BookInfo book = null;
book = bookRepo.findFirstByPublisherNameAndBookTitleAndAuthorName(dto.getPubName(), dto.getTitle(), dto.author());
if(book != null){
book = buildBookInfo(dto);
book = bookRepo.save(book);
}
...//other methods to create dependent resources for Author class and save the entities using the respective repositories
}
}
我尝试了以下 -
- 在 repo 方法上添加了@Lock(PESSIMISTIC_WRITE)
findFirstByPublisherNameAndBookTitleAndAuthorName()
- 这有效,但冲突不会很频繁,我担心它将对整体功能产生性能影响,以处理上述偶尔发生的冲突。 - 使用 @Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW) 将书籍创建部分作为单独的方法 - 但我没有收到会话错误
- 使 BookInfo 存储库实现 JpaRepository 而不是 PagingAndSortingRepository 以使用 saveAndFlush() 而不是 save(),但看起来即使 saveAndFlush 也不会提交到 DB,提交只会在外部方法的事务结束时隐式发生
此外,随机注意到,由于并发性,Author 对象本身的资源创建存在冲突,其中不是将新遇到的 BookInfo 添加到现有作者,而是使用新书信息为作者创建一个新条目
一般来说,使 JPA 事务线程安全的最佳方法是什么?
解决方案
使用@Transactional(propagation = Propagation.REQUIRES_NEW) 找到了解决方案(您需要spring框架提供的@Transactional,而不是javax.transaction.Transactional)。它对我不起作用的原因是由于对 Spring Data JPA 如何管理事务代理的非常不直观的警告。考虑一下 -
@Service
public class AuthorService {
@Transactional
public void createAutoBiography() {
createBook();
// process other entities
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createBook() {
// ...
}
}
在这里,您会期望创建两个事务,一个由 createAutoBiography() 创建,该事务由 createBook() 方法中创建的另一个事务暂停-这就是我的假设错误的地方。
Spring 创建了一个事务性 AuthorService 代理,但是一旦我们进入 AuthorService 类并调用其他内部方法,就不再涉及代理了。这意味着,不会创建新事务。这意味着第二种方法中的任何异常都会关闭整个服务的事务,并且在尝试下一个 jpa 操作时出现 No session 错误。
所以通过将 createBook() 方法放在一个单独的服务类中并添加一个类级别的 @Transactional(propagation = Propagation.REQUIRES_NEW) 注释来解决这个问题。
现在 createBook() 执行发生在一个单独的事务中,并在此方法返回(事务结束)时立即将其写入 DB,而 createAutoBiography() 中的原始事务被挂起。因此,现在如果我们捕获异常(如上面的@slauth 所述)或使用某种数据库锁定,我们可以分别处理/避免 DataInegrityViolation 异常。此外,即使不处理异常,也只有第二个事务应该回滚,而主事务仍然可以执行(不过需要测试一下)。
推荐阅读
- javascript - 单击包含特定类的div中的任何位置时触发事件?(通过谷歌标签管理器)
- nativescript - NativeScript 中的不同屏幕尺寸
- mysql - 如果它具有相同的日期,我如何使用 foreach 并隐藏其他结果?
- r - 使用 r 替换 json 文件中的空括号
- excel - 可以在数组公式中使用 INDIRECT 或替代方法来引用封闭的外部工作簿吗?
- android - 专注于 AutoCompleteTextView 在 Android TV 设备上不显示软键盘
- excel - 我的 VBA excel 代码在 Mac Excel 上运行,但不在 Windows Excel 2016 上运行
- laravel - 在 Laravel 中通过多对多关系模型对 eloquent 模型进行排序
- javascript - 自定义操作以编辑行覆盖其他操作
- sql - 在 PostgreSQL 中创建嵌套的 json blob