繁体   English   中英

如果 Spring 数据 JPA 不存在,如何处理并发创建

[英]How to handle Concurrent create if not exists with spring data JPA

我在我的 java-springboot 项目中使用 spring 数据 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 
  }
}

我尝试了以下 -

  1. findFirstByPublisherNameAndBookTitleAndAuthorName()库方法上添加了 @Lock(PESSIMISTIC_WRITE) - 这有效,但冲突不会很频繁,我担心它对整体功能的性能影响,以处理上述偶尔的冲突。
  2. 使用 @Transactional(isolation = Isolation.SERIALIZABLE,propagation = Propagation.REQUIRES_NEW) 将书籍创建部分作为单独的方法 - 但我没有收到会话错误
  3. 使 BookInfo 存储库实现 JpaRepository 而不是 PagingAndSortingRepository 以使用 saveAndFlush() 而不是 save(),但看起来即使 saveAndFlush 也不会提交到数据库,提交只会在外部方法的事务结束时隐式发生

此外,随机注意到 Author 对象本身的资源创建冲突,由于并发,它不是将新遇到的 BookInfo 添加到现有作者,而是使用新书信息为作者创建一个新条目

一般来说,使 JPA 事务线程安全的最佳方法是什么?

您可以简单地捕获DataIntegrtyViolation并再次调用findFirstByPublisherNameAndBookTitleAndAuthorName (或递归地createAutoBiography )。

使用 @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 操作时我没有收到会话错误。

因此,通过将 createBook() 方法放在单独的服务类中并添加类级别 @Transactional(propagation = Propagation.REQUIRES_NEW) 注释来解决此问题。

现在 createBook() 执行发生在一个单独的事务中,一旦这个方法返回(事务结束),它就会被写入数据库,而 createAutoBiography() 中的原始事务被挂起。 因此,现在如果我们捕获异常(如上面@slauth 所述)或使用某种数据库锁定,我们可以分别处理/避免 DataInegrityViolation 异常。 另外,即使没有处理异常,只有第二个事务应该回滚,而主事务仍然可以执行(尽管需要测试)。

参考 - https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth#transactional-pitfalls

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM