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