WARNING!!! 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
HTTP Method: POST, API path: /reader
Entity "reader" Engine: innoDB
id
name
total_pages_read
Class Mapping:
@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;
...
}
I use method createEntity() and recalculateTotalPageRead() in a Reader write service class:
@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;
}
}
...
}
HTTP Method: POST, API path: /bookread
Entity "book_read" Engine: innoDB
id
reader_id
book_title
number_of_pages
Class Mapping:
@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;
...
}
I use method createEntity()
and recalculateTotalPageRead()
in a Book_read write service class:
@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);
}
...
}
When I try to create both entities:
Sample "reader" :
id | name | total_pages_read
-----------------------------------
1 | Foo Reader | 0(by default)
Sample "book_read": 2 separated POST method calls
id | reader_id | book_title | number_of_pages
---------------------------------------------
1 | 1 | Foo Book | 2
2 | 1 | Bar Book | 3
Expecting change on entity "reader" after creating "book_read"-s as above sample:
Sample Reader:
id | name | total_pages_read
-----------------------------------
1 | Foo Reader | 5
But from what I've been experiencing there happens to be 3 cases while creating those 2 "book_read" records concurrently:
Case 1 (OK):
- 1st "book_read" finished creating
- Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 1.
- Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1. Current total_pages_read = 2.
- Start creating 2nd "book_read"
- 2nd "book_read" finished creating
- Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 2.
- Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1.
- Final result: total_pages_read = 5.
Case 2 (OK):
- (Transaction 1) Start creating 1st "book_read"
- (Transaction 2) Start creating 2nd "book_read"
- (Transaction 1) 1st "book_read" finished creating
- (Transaction 2) 2nd "book_read" finished creating
- (Transaction 1) Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 1.
- (Transaction 2) Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 1.
- (Transaction 1) Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1. Current total_pages_read = 2.
- (Transaction 2) Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1. Deadlock Exception Thrown.
- Retry (Transaction 2) Start creating 2nd "book_read"
- (Transaction 2) 2nd "book_read" finished creating
- (Transaction 2) Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 2.
- Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1.
- Final result: total_pages_read = 5.
Case 3 (NOT OK):
- 1st "book_read" finished creating
- Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 1.
- Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1. Current total_pages_read = 2.
- Start creating 2nd "book_read"
- 2nd "book_read" finished creating
- Get any existing "book_read" of "reader" id 1 into a list "book_reads". "book_reads" size = 1.
- Add number_of_pages of each "book_read" in the list to total_pages_read of "reader" id 1. Current total_pages_read = 3. Not Detecting Deadlock.
- Final result: total_pages_read = 3.
How do I fix case 3?
Cheers, Happy programming :D
SOLVED!
Optimistic Locking Implementation on data model
@Entity
@Table(name = "reader")
public class Reader{
@Version
@Column(name = "version")
private int version;
@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;
...
}
@Transactional
annotation above mycreateEntity()
methods. I've jst added them into the code snippet. Thanks for pointing out. By the way, I don really get your last question about the updating mechanism. P.S: m still new to Java EE, thanks for tolerating :D – Tman