1. Cause of the incident.
When using JPA. In the same transaction, for a piece of data. First query, then update, then query. Because the second query gets the result of the first query from the cache. There are two situations:
- If the update is a JPA statement such as save(). The cache is updated. The second query is consistent with the database.
- If you use your own UPDATE statement. Because the cache was not updated. The second query gets data from the cache, resulting in the updated data not being retrieved for the second time. Results in inconsistent query data and database expectations.
Essentially, it should be clear that in the same transaction, the transaction is not committed. The second query gets data from the cache.
Second, prove that the data is obtained from the cache for the second time.
In the above code, the addresses of the two objects are the same as Organization@9556. So the second query is the data obtained from the cache.
3. Reference example 1:
@Transactional @Override public void update() { //query Organization organization = organizationRepo.findById(1803L).get(); organization.setName("Test step1ppp33"); //Update (because it is in a transaction, the cache is updated, but not saved to the database) organizationRepo.save(organization); ////Query data Organization org2 = organizationRepo.findById(1803L).get(); //The data obtained is org2. Getname() = = test step1pppp33. The proof is not taken from the database, but from the same transaction cache. System.out.println("org2.getName()==="+org2.getName()); }
In the above code, org2. Getname() = = test step1pppp33. The proof is not taken from the database, but from the same transaction cache.
4. Reference example 2:
@Transactional @Override public void update() { //query Organization organization = organizationRepo.findById(1803L).get(); /** * @Modifying * @Query("UPDATE Organization o set o.name='myUpdate' WHERE o.id=:id") *void myUpdate(@Param("id") Long id); */ // Only one update statement has been made, and the cached organization variable has not been updated. organizationRepo.myUpdate(1803L); //Query cache data Organization org2 = organizationRepo.findById(1803L).get(); //Org2. Getname() = = test step1pppp33 to prove the data obtained from the cache. The updated data was not found, System.out.println("org2.getName()==="+org2.getName()); }
I wrote an update statement and did not update the cached data, so the name value of the second query did not change.
5. High concurrency.
If we want to write a method, methodA(), in order to reduce the amount of a commodity, first query a commodity data. Then reduce the amount. There are two situations:
- Use the save() statement to update. As follows:
Goods goods = goodsRepo.findById(1); goods.setAmount(goods.getAmount() - quantity); goodsRepo.save(goods);
At this time, the cache is also updated. After external methodB() calls methodA(), use goodsRepo.findById(1) to query the inventory in methodB(). It can ensure that the number of cache queries is consistent with the database.
However, if another thread updates the Amount of goods to 0 during the high concurrent delivery, the number of goods after the execution of the current thread is the same as that of the current thread, resulting in the loss of updates of other threads.
2. Update with custom statement. As follows:
Number of custom statements checked.
@Modifying @Query("UPDATE Goods g SET g.amount=g.amount - :amount WHERE g.id=:id AND g.amount >=:amount") void myUpdate(@Param("id") Long id,@Param("amount") Long amount);
The logic of subtraction.
Goods goods = goodsRepo.findById(1); //Quantity reduction goodsRepo.myUpdate(1,amount);
At this time, the goodsRepo.findById(1) queries out the goodsrepo.findbyid object, and the cache is not updated. After methodB() calls methodA(), use goodsRepo.findById(1) to query the inventory in methodB(). There will be inconsistencies with expectations.
Summary: the expected number of databases must match the number of caches.