thy*_*yzz 9 java mysql hibernate jpa database-deadlocks
警告!!!TL; DR
MySQL 5.6.39
mysql:mysql-connector-java:5.1.27
org.hibernate.common:hibernate-commons-annotations:4.0.5.Final
org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final
org.hibernate:hibernate-core:4.3.6.Final
org.hibernate:hibernate-entitymanager:4.3.6.Final
org.hibernate:hibernate-validator:5.0.3.Final
Run Code Online (Sandbox Code Playgroud)
HTTP方法:POST,API路径:/ reader
实体“ 读者 ”引擎:innoDB
id
name
total_pages_read
Run Code Online (Sandbox Code Playgroud)
类映射:
@Entity
@Table(name = "reader")
public class Reader{
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
@Column(name = "total_pages_read")
private Long total_pages_read;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "reader", orphanRemoval = true)
private Set<Book_read> book_reads;
...
}
Run Code Online (Sandbox Code Playgroud)
我在Reader写入服务类中使用方法createEntity()和recalculateTotalPageRead():
@Service
public class ReaderWritePlatformServiceJpaRepositoryImpl{
private final ReaderRepositoryWrapper readerRepositoryWrapper;
...
@Transactional
public Long createEntity(final Long id, final String name, final Long total_pages_read){
try {
final Reader reader = new Reader(id, name, total_pages_read);
this.readerRepositoryWrapper.saveAndFlush(reader);
return 1l;
} catch (final Exception e) {
return 0l;
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
HTTP方法:POST,API路径:/ bookread
实体“ book_read ”引擎:innoDB
id
reader_id
book_title
number_of_pages
Run Code Online (Sandbox Code Playgroud)
类映射:
@Entity
@Table(name = "book_read")
public class Book_read{
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reader_id")
private Reader reader;
@Column(name = "book_title")
private String book_title;
@Column(name = "number_of_pages")
private Long number_of_pages;
...
}
Run Code Online (Sandbox Code Playgroud)
我使用方法,createEntity()并recalculateTotalPageRead()在Book_read write服务类中:
@Service
public class Book_readWritePlatformServiceJpaRepositoryImpl{
private final ReaderRepositoryWrapper readerRepositoryWrapper;
private final Book_readRepositoryWrapper bookReadRepositoryWrapper;
...
@Transactional
public Long createEntity(final Long id, final Long reader_id, final String book_title, final Long number_of_pages){
try {
final Reader reader = this.readerRepositoryWrapper.findOneWithNotFoundDetection(reader_id);
final Book_read book_read = new Book_read(id, reader, book_title, number_of_pages);
this.bookReadRepositoryWrapper.saveAndFlush(book_read);
this.recalculateTotalPageRead(reader);
return 1l;
} catch (final Exception e) {
return 0l;
}
}
private void recalculateTotalPageRead(final Reader reader){
Long total_pages_read = Long.valueOf(0);
Set<Book_read> book_reads = reader.getBook_reads();
for (Book_read book_read : book_reads){
total_pages_read += book_read.getNumber_of_pages();
}
reader.setTotal_pages_read(total_pages_read);
this.readerRepositoryWrapper.saveAndFlush(reader);
}
...
}
Run Code Online (Sandbox Code Playgroud)
当我尝试创建两个实体时:
样本“ 读者 ”:
id | name | total_pages_read
-----------------------------------
1 | Foo Reader | 0(by default)
Run Code Online (Sandbox Code Playgroud)
示例“ book_read ”:2个单独的POST方法调用
id | reader_id | book_title | number_of_pages
---------------------------------------------
1 | 1 | Foo Book | 2
2 | 1 | Bar Book | 3
Run Code Online (Sandbox Code Playgroud)
如上例所示,在创建“ book_read ” -s 后,预期对实体“ reader ”的更改:
样本阅读器:
id | name | total_pages_read
-----------------------------------
1 | Foo Reader | 5
Run Code Online (Sandbox Code Playgroud)
但是,从什么我已经经历刚好有3案件同时创造者2“ book_read ”记录并发:
情况1(确定):
情况2(确定):
情况3(不确定):
如何解决案例3?
干杯,快乐编程:D
您所经历的称为丢失更新,这实际上不是 JPA 级别的问题,您可以在 MySQL shell 中轻松重现此问题。我假设您没有对数据库本身进行任何更改,因此您的默认事务隔离级别是REPEATABLE READ。
在 MySQL 中,REPEATABLE READ不会检测可能丢失的更新(尽管这是对此隔离级别的普遍理解)。您可以在 SO和评论线程上查看此答案以了解更多信息。
基本上通过使用 MVCC,MySQL 试图避免争用和死锁。在你的情况下,你将不得不做出权衡并选择牺牲一些速度来保持一致性。
您的选择是使用SELECT ... FOR UPDATE语句或设置更严格的隔离级别SERIALIZABLE(您可以对单个事务执行此操作)。这两个选项都会阻止读取,直到并发事务提交/回滚。因此,稍后(或很久以后,取决于应用程序的要求),您将看到数据的一致视图。
并发很难。:)
更新:考虑下面的评论后,实际上您还有另一个选择:为您的数据模型实现乐观锁定。JPA对此有支持,请查看此处和此处。您实现的效果基本相同,但采用了一些不同的方法(您将必须重新启动失败的事务以达到不匹配的版本),并且由于锁定较少而导致争用较少。