我有两个处理实体的方法(在Spring启动应用程序中)。该实体有两个字段,分别是布尔isDefault
和isPdfGenerated
。第一个方法(从控制器调用)在创建新实体时更改isDefault
标志,而第二个方法(从@Scheduled带注释的方法调用)在生成pdf后更改isPdfGenrated
该实体的文件。
我的问题是,有时第二种方法会找到isPdfGenerated
标志设置为false的实体,即使该文件已生成并保存在数据库中。
这两个方法都有@Transactional
注释,实体的存储库接口扩展为JpARepository
。
我的猜测是第一种方法在第二种方法执行之前从数据库加载实体,但在第二种方法完成其工作后保存实体,从而覆盖isPdfGenerated
标志。
这可能吗?如果答案是肯定的,那么应该如何处理这种情况呢?当实体从外部源更新时,不应该处理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);
}
实体:
@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()) +
"}";
}
}
我省略了其他类,因为它们是POJO(EntityForm
)或与其他域模型类(Document
)相同。
答案 0 :(得分:3)
如果您正在讨论数据库中的一行,在第一个进程读取之后但在更新之前由另一个进程更新,那么您需要采用某种乐观锁定策略。
这将由底层的ORM api(例如Hibernate或Eclipselink)处理,而不是Spring Data(它只处理ORM抛出的乐观锁定错误)。
看看这篇文章。请记住,如果您想要乐观锁定,则需要某种方法来确定行的版本。在JPA中,通常使用注释了@Version标记的列来完成。
https://vladmihalcea.com/hibernate-locking-patterns-how-does-optimistic-lock-mode-work/