0

我的 Spring 3 Web 应用程序遇到了问题:

用户可以通过拖放到浏览器中来将多个附件上传到 AttachmentParent,这会触发分段上传。spring 控制器获取文件,创建一个 Attachment 实体,然后将其发送到服务层,在那里它被添加到 AttachmentParent 上的附件列表中。

它适用于一个文件,但如果他们拖动多个文件并且几乎同时完成上传,则服务层会被多次调用,并且由于每个文件都是一个单独的请求并且服务标记为@Transactional,因此会打开多个事务同时围绕 AttachmentParent。第一个成功关闭,但其余所有都抛出乐观锁定异常,因为父级在打开后发生了变化,并且没有发生更新。

以下是相关代码:

文件上传控制器.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();
}

附件服务.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)

我正在使用 spring 管理的事务管理器、hibernate 和 mysql5:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

根据我在网上阅读文章的理解,这是工作中的乐观锁定。我的问题是,解决这个问题的最佳方法是什么?

我提出了三种可能的解决方案:

  • 将服务方法标记为同步- 这解决了问题。但它比它需要的限制更多:唯一的问题是当多个线程访问同一个附件父级时。也许有一个我不知道的 spring 注释允许您使用 spring 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

修改 AttachmentParent 类,以便对附件集合的更改不会导致更新父类的版本号。像这样的东西(如果您在尝试时遇到问题,请向我们展示您的 AttachmentParent 课程,我会给您确切的配置)

class AttachmentParent {
...

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

}
于 2012-07-20T07:04:15.707 回答
0

我最终只是从父级中删除了列表并将对父级的引用存储在子级中。在父母中拥有孩子名单的便利不值得它造成的麻烦。

由于我仍然想要计算要存储在父类中的附件数量,因此我向attachmentCount父类添加了一个整数变量。尝试在addAttachment方法内部增加它会导致类似的问题,因为多个事务会读取相同的值,增加它,然后在它们应该等待第一个完成时将其设置为相同的值。为了解决这个问题,我在服务上设置了另一个方法refreshParentAttachmentCount,它运行一个查询来确定给定父级的附件数量,并将父级的 attachmentCount 设置为该数字。

我正在关闭这个问题,但如果有人有更好的答案,请随时发表评论。

于 2012-07-31T05:50:24.440 回答