1

私はエンティティを扱う2つの方法(Spring起動アプリケーションで)があります。エンティティには、ブール値isDefaultisPdfGeneratedの2つのフィールドがあります。最初のメソッド(コントローラから呼び出される)は、新しいエンティティが作成されたときにisDefaultフラグを変更し、@Scheduled注釈付きメソッドから呼び出された2番目のメソッドは、そのエンティティのpdfファイルを生成した後にisPdfGenratedを変更します。SpringのJPARepositoryと@Transactionalはどのようにふるまいますか?

私の問題は、ファイルが生成されてデータベースに保存されていても、isPdfGeneratedフラグがfalseに設定されたエンティティを2番目の方法で見つけることがあることです。

両方のメソッドには@Transactionalアノテーションがあり、エンティティのリポジトリインタフェースはJpARepositoryに拡張されます。

私の推測では、2番目のメソッドがそのジョブを実行した後でエンティティを保存して、isPdfGeneratedフラグをオーバーライドする前に、2番目のメソッドはエンティティをデータベースからロードします。

これは可能ですか?答えが「はい」の場合、そのようなケースをどのように扱うべきですか?エンティティが外部ソースから更新された場合、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); 
} 

エンティティ:彼らはどちらかのPOJO(EntityForm)または他のドメイン・モデルと同じですので、私は他のクラスを省略している

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

クラス(Document)。

+2

コードを正確に追加することはできますか? – developer

+0

質問をコードで更新しました。 – screab

答えて

2

最初のプロセスがそれを読み込んだ後、更新される前に、別のプロセスによって更新されているデータベースの行については、ある種の楽観的なロック戦略を行う必要があります。

これは、Springデータ(ORMによってスローされた楽観的なロックエラーを処理するだけではなく)の下にあるORM api(たとえば、HibernateまたはEclipselink)によって処理されます。

この記事をご覧ください。オプティミスティック・ロックが必要な場合は、行のバージョンを判別する方法が必要であることに注意してください。 JPAでは、通常、@ Versionタグで注釈が付けられた列を使用して行われます。

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

+0

この記事は正しい軌道に乗っているようですが、実際のところ役立ちません。私はまた、バージョンの列を追加せずにソリューションを好むだろう。 – screab

+0

Eclipselinkは行全体を楽観的にロックすることをサポートしています。 Hibernateはバージョンフィールドの代わりにタイムスタンプフィールドを使うこともできます。 更新する直前に行を再読み込みし、手動で変更していないことを確認することもできますが、これはやや面倒です。 – PaulNUK

関連する問題