Spring 的 JPARepository 和 @Transactional 如何协同工作?

How does Spring's JPARepository and @Transactional behave together?

我有两种处理实体的方法(在 Spring 启动应用程序中)。该实体有两个字段,都是布尔值 isDefaultisPdfGenerated。第一个方法(从控制器调用)在创建新实体时更改 isDefault 标志,而第二个方法(从 @Scheduled 注释方法调用)在生成 pdf 后更改 isPdfGenrated该实体的文件。

我的问题是,有时第二种方法会找到 isPdfGenerated 标志设置为 false 的实体,即使文件已生成并保存在数据库中。

这两种方法都有 @Transactional 注释,实体的存储库接口扩展 JpARepository

我的猜测是第一种方法在第二种方法之前从数据库加载实体,但在第二种方法完成其工作后保存实体,从而覆盖了 isPdfGenerated 标志。

这可能吗?如果答案是肯定的,应该如何处理这种情况? JPARepository 不应该处理从外部源更新实体的情况吗?

下面是一些代码,可以更好地说明情况。

我的控制器:

@Controller
@RequestMapping("/customers")
public class MyController {

  @Autowired
  private EntityService entityService;

  @RequestMapping(value = "/{id}/changeDefault", method = RequestMethod.POST)
  public String changeDefault(@PathVariable("id") Long customerId, @ModelAttribute EntityForm entityForm, Model model) {

        Entity newDefaultEntity = entityService.updateDefaultEntity(customerId, entityForm);

        if (newDefaultEntity == null)
            return "redirect:/customers/" + customerId;

        return "redirect:/customers/" + customerId + "/entity/default;
  }
}

实体服务:

import org.springframework.transaction.annotation.Transactional;

@Service
public class EntityService {

  @Autowired
  private EntityRepository entityRepository;

  @Autowired
  private CustomerRepository customerRepository;

  @Transactional
  public Entity updateDefaultEntity(Long customerId, submittedData) {

      Customer customer = customerRepository.findById(customerId);
      if(customer == null)
        return customer; // I know there are better ways to do this

      Entity currentDefaultEntity = entityRepository.findUniqueByCustomerAndDefaultFlag(customer, true);
      if(currentDefaultEntity == null)
        return null; // I know there are better ways to do this also

      Entity newDefaultEntity = new Entity();
      newDefaultEntity.setField1(submittedData.getField1());
      newDefaultEntity.setField2(submittedData.getField2());
      newDefaultEntity.setCustomer(customer);

      oldDefaultEntity.setDefaultFlag(false);
      newDefaultEntity.setDefaultFlag(true);

      entityRepository.save(newDefaultEntity);
  }

  @Transactional
  public void generatePdfDocument(Entity entity) {

      Document pdfDocument = generateDocument(entity);
      if(pdfDocument == null)
        return;

      documentRepository.save(pdfDocument);

      entity.setPdfGeneratedFlag(true);
      entityRepository.save(entity);
  }

}

计划任务:

@Component
public class ScheduledTasks {

    private static final int SECOND_IN_MILLISECONDS = 1000;
    private static final int MINUTE_IN_SECONDS = 60;

    @Autowired
    private EntityRepository entityRepository;

    @Autowired
    private DocumentService documentService;

    @Scheduled(fixedDelay = 20 * SECOND_IN_MILLISECONDS)
    @Transactional
    public void generateDocuments() {

        List<Quotation> quotationList = entityRepository.findByPdfGeneratedFlag(false);
        for(Entity entity : entitiesList) {

          documentService.generatePdfDocument(entity);
        }
    }
 }

文档服务:

@Service
public class DocumentService {

  @Autowired
  private EntityRepository entityRepository;

  @Autowired
  private DocumentRepository documentRepository;

  @Transactional
  public void generatePdfDocument(Entity entity) {

      Document pdfDocument = generateDocument(entity);
      if(pdfDocument == null)
        return;

      documentRepository.save(pdfDocument);

      entity.setPdfGeneratedFlag(true);
      entityRepository.save(entity);
  }

}

实体库:

@Repository
public interface EntityRepository extends JpaRepository<Entity, Long> {

    Entity findById(@Param("id") Long id);

    List<Entity> findByPdfGeneratedFlag(@Param("is_pdf_generated") Boolean pdfGeneratedFlag);

    Entity findUniqueByCustomerAndDefaultFlag(
            @Param("customer") Customer customer,
            @Param("defaultFlag") Boolean defaultFlag
    );
}

文档存储库:

@Repository
public interface DocumentRepository extends JpaRepository<Document, Long> {

    Document findById(@Param("id") Long id);
}

实体:

@Entity
@Table(name = "entities")
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
public class Entity {

    private Long id;
    private boolean defaultFlag;
    private boolean pdfGeneratedFlag;
    private String field1;
    private String field2;
    private Customer customer;

    public Entity() { }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "is_default")
    public boolean isDefaultFlag() {
        return defaultFlag;
    }

    public void setDefaultFlag(boolean defaultFlag) {
        this.defaultFlag = defaultFlag;
    }

    @Column(name = "is_pdf_generated")
    public boolean isPdfGeneratedFlag() {
        return pdfGeneratedFlag;
    }

    public void setPdfGeneratedFlag(boolean pdfGeneratedFlag) {
        this.pdfGeneratedFlag = pdfGeneratedFlag;
    }

    @Column(name = "field_1")
    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    @Column(name = "field_2")
    public String getField2() {
        return field2;
    }

    public void setField2(String field2) {
        this.field2 = field2;
    }

    @ManyToOne
    @JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Entity quotation = (Entity) o;

        return id != null ? id.equals(entity.id) : entity.id == null;

    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }

    @Override
    public String toString() {
        return "Entity{" +
                "id=" + id +
                ", pdfGeneratedFlag=" + pdfGeneratedFlag +
                ", defaultFlag=" + defaultFlag +
                ", field1=" + field1 +
                ", field2=" + field2 +
                ", customer=" + (customer == null ? null : customer.getId()) +
                "}";
    }
}

我省略了另一个 类 因为它们要么是 POJO ( EntityForm ) 要么与其他域模型相同 类 ( Document ).

如果您谈论的是数据库中的一行在第一个进程读取之后但在更新之前被另一个进程更新,那么您需要采用某种乐观锁定策略。

这将由底层 ORM api(例如 Hibernate 或 Eclipselink)处理,而不是 Spring 数据(它将只处理 ORM 抛出的乐观锁定错误)。

看看这篇文章。请记住,如果您想要乐观锁定,您需要某种方法来确定行的版本。在 JPA 中,这通常是使用带有 @Version 标记注释的列来完成的。

https://vladmihalcea.com/hibernate-locking-patterns-how-does-optimistic-lock-mode-work/