0

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

2 に答える 2

0

親からリストを削除し、親への参照を子に保存するだけになりました。親に子のリストがあるという便利さは、それによって引き起こされた問題に見合ったものではありませんでした。

親に格納される添付ファイルの数をカウントしたいので、整数attachmentCount変数を親クラスに追加しました。メソッド内でインクリメントしようとするとaddAttachment、複数のトランザクションが同じ値を読み取り、インクリメントし、最初のトランザクションが終了するまで待機する必要があるときに同じ値に設定するため、同様の問題が発生しました。これを修正するために、サービスに別のメソッドを設定しました。このメソッドはrefreshParentAttachmentCount、クエリを実行して特定の親の添付ファイルの数を判断し、親の attachmentCount をその数に設定します。

私はこの問題を閉じますが、誰かがより良い答えを持っている場合は、コメントを残してください.

于 2012-07-31T05:50:24.440 に答える
0

添付ファイル コレクションへの変更によって親クラスのバージョン番号が更新されないように、AttachmentParent クラスを変更します。このようなもの (これを試すのに問題がある場合は、AttachmentParent クラスを見せてください。正確な構成を提供します)

class AttachmentParent {
...

@OneToMany
@org.hibernate.annotations.OptimisticLock(excluded = true)
Set<Attachment> attachments;

}
于 2012-07-20T07:04:15.707 に答える