首页 > 解决方案 > 如果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 
  }
}

我尝试了以下 -

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

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

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

标签: javaspring-bootjpaconcurrencyspring-data-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 异常。此外,即使不处理异常,也只有第二个事务应该回滚,而主事务仍然可以执行(不过需要测试一下)。

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


推荐阅读