Spring3Webアプリケーションで問題が発生しました。
ユーザーは、ブラウザにドラッグアンドドロップしてマルチパートアップロードをトリガーすることにより、AttachmentParentに複数の添付ファイルをアップロードできます。Spring Controllerはファイルを取得し、Attachmentエンティティを作成してから、それをService Layerに送信し、AttachmentParentの添付ファイルのリストに追加します。
1つのファイルで正常に機能しますが、複数のファイルをドラッグしてほぼ同時にアップロードを完了すると、サービスレイヤーが複数回呼び出されます。各ファイルは個別のリクエストであり、サービスは@Transactionalとマークされているため、複数のトランザクションが開かれます。同時にAttachmentParentの周り。最初のものは正常に閉じますが、開いてから親が変更され、更新が行われないため、残りはすべて楽観的ロック例外をスローします。
関連するコードは次のとおりです。
FileUploadController.java
@RequestMapping(value = "/attach/{parentid}", method = RequestMethod.POST)
public View handleFormUpload(@RequestParam("file") MultipartFile file, @PathVariable("parentid") long parentId, Model model) {
AttachmentParent parent = em.find(AttachmentParent.class, parentId);
try {
StaticFile staticFile = fileUploader.uploadFile(file, parent.getUrl() + "/attachments/");
Attachment a = new Attachment(staticFile.getFilename(), "", staticFile);
attachmentService.addAttachment(parentId, a);
//add data for the json response to the client
model.addAttribute("name", file.getName());
model.addAttribute("size", file.getSize());
model.addAttribute("url", staticFile.getUrl());
model.addAttribute("thumbnail_url", staticFile.getThumbnail_url());
} catch (IOException e) {
log.error("Cannot upload file");
model.addAttribute("error", e.getMessage());
}
return new MappingJacksonJsonView();
}
AttachmentService.java
@Transactional
public class AttachmentService extends GenericService<Attachment> {
...
public void addAttachment(long parentId, Attachment attachment) {
log.debug("Starting to add an attachment for "+parentId);
AttachmentParent parent = em.find(AttachmentParent.class, parentId);
attachment.setParent(parent);
parent.addAttachment(attachment);
log.debug("About to merge "+parent.getId());
em.merge(parent);
}
ログメッセージの出力は、問題を示しています。
Starting to add an attachment for 38
Starting to add an attachment for 38
Starting to add an attachment for 38
About to merge 38
About to merge 38
About to merge 38
最初のトランザクションに続いて各トランザクションを閉じようとしているときのコンソールエラーメッセージ:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
スプリング管理のトランザクションマネージャー、休止状態、およびmysql5を使用しています。
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
ウェブ上の記事を読んで私が理解していることから、これは楽観的なロックです。私の質問は、この問題を解決するための最良の方法は何ですか?
私が思いついた3つの可能な解決策:
サービスメソッドを同期済みとしてマークします-これで問題が修正されます。ただし、必要以上に制限があります。唯一の問題は、複数のスレッドが同じ添付ファイルの親にアクセスする場合です。たぶん、私が知らないスプリングアノテーションがあります。これにより、スプリングelを使用して、メソッドを同期するタイミングを指定できますか?また、これが私が知らない他の効果をもたらすかどうかはわかりません:同期とマークされたサービスレイヤーメソッドを見たことがないので、何らかの理由で眉をひそめていると思います
同じリクエスト内のすべてのファイルを受け入れ、それらを繰り返し処理して、各トランザクションが閉じられてから別のトランザクションが開かれるようにします。これは可能だと思いますが、トランザクション境界に関する元の質問には答えていません
どういうわけか、単一のトランザクションを複数のリクエストに対して開いたままにします。これが可能かどうかはわかりませんが、現在のパターン(サービスレイヤーのトランザクション境界)とは大きく異なります。
更新:以下の完全なAttachmentParentクラス
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AttachmentParent extends BaseEntity {
private Set<Attachment> attachments = new HashSet<Attachment>();
@OneToMany(cascade = CascadeType.ALL)
//annotation added in response to gkamal's answer, to no effect
@org.hibernate.annotations.OptimisticLock(excluded = true)
public Set<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(Set<Attachment> attachments) {
this.attachments = attachments;
}
public void addAttachment(Attachment a) {
attachments.add(a);
}
public void removeAttachment(Attachment a) {
attachments.remove(a);
}
}