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

时间:2017-03-21 07:47:09

标签: spring spring-mvc spring-boot spring-data-jpa spring-transactions

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

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

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

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

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

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

myController的:

@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;
  }
}

EntityService:

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);
  }

}

ScheduledTasks:

@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);
        }
    }
 }

DocumentService:

@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);
  }

}

EntityRepository:

@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
    );
}

的DocumentRepository:

@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)相同。

1 个答案:

答案 0 :(得分:3)

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

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

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

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