How to properly handle JPA ObjectOptimisticLockException for multiple simultaneous transaction requests?

So, I was working on a simple Spring MVC + JPA (hibernate) project, where there are users who can post and comment on their friends. Posts (somewhat similar to a small social network). I am still relatively new using JPA Hibernate. Therefore, when I try to verify that the browser sends several requests for a certain task (containing transactions) very quickly 2-3 times, while the previous request is being processed, I get an OptimisticLockException. Here's the stack trace.

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [org.facebookjpa.persistance.entity.Post] with identifier [19]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.facebookjpa.persistance.entity.Post#19] org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973) org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852) javax.servlet.http.HttpServlet.service(HttpServlet.java:620) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837) javax.servlet.http.HttpServlet.service(HttpServlet.java:727) 

Now how do I fix this? How to handle this ObjectOptimisticLockException correctly when multiple transaction requests occur at the same time? Is there a good patten that I should follow? Do I need to use some kind of pessimistic blocking mechanism?

Here is the DAO I'm using right now .. Thanks in advance. :)

 @Repository @Transactional public class PostDAOImpl implements PostDAO { @Autowired UserDAO userDAO; @Autowired CommentDAO commentDAO; @Autowired LikeDAO likeDAO; @PersistenceContext private EntityManager entityManager; public PostDAOImpl() { } @Override public boolean insertPost(Post post) { entityManager.persist(post); return true; } @Override public boolean updatePost(Post post) { entityManager.merge(post); return true; } @Override public Post getPost(int postId) { TypedQuery<Post> query = entityManager.createQuery("SELECT p FROM Post AS p WHERE p.id=:postId", Post.class); query.setParameter("postId", postId); return getSingleResultOrNull(query); } @Override public List<Post> getAllPosts() { return entityManager.createQuery("SELECT p FROM Post AS p ORDER BY p.created DESC", Post.class).getResultList(); } @Override public List<Post> getNewsFeedPostsWithComments(int userId) { List<Post> newsFeedPosts = getUserPosts(userId); newsFeedPosts.addAll(getFriendsPost(userDAO.getUser(userId))); for (Post post : newsFeedPosts) { post.setComments(commentDAO.getPostComments(post.getId())); post.setLikes(likeDAO.getPostLikes(post.getId())); } return newsFeedPosts; } public List<Post> getFriendsPost(User user) { List<Post> friendsPosts = new ArrayList<Post>(); for (User u : user.getFriends()) { friendsPosts.addAll(getUserPosts(u.getId())); } return friendsPosts; } @Override public List<Post> getUserPosts(int userId) { TypedQuery<Post> query = entityManager.createQuery("SELECT p FROM Post AS p WHERE p.user.id = :userId ORDER BY p.created DESC", Post.class); query.setParameter("userId", userId); return query.getResultList(); } @Override public List<Post> getUserPostsWithComments(int userId) { List<Post> userPostsWithComments = getUserPosts(userId); for (Post post : userPostsWithComments) { post.setComments(commentDAO.getPostComments(post.getId())); post.setLikes(likeDAO.getPostLikes(post.getId())); } return userPostsWithComments; } @Override public boolean removePost(Post post) { entityManager.remove(post); return true; } @Override public boolean removePost(int postId) { entityManager.remove(getPost(postId)); return true; } private Post getSingleResultOrNull(TypedQuery<Post> query) { query.setMaxResults(1); List<Post> list = query.getResultList(); if (list.isEmpty()) { return null; } return list.get(0); } 

}

+8
source share
2 answers

The exclusion of optimistic blocking prevents the loss of updates, and you should not ignore this. You can simply intercept it in a regular exception handler and redirect the user to the current starting point of the workflow, indicating that a parallel change has occurred that he was not aware of.

  1. If you don't mind losing updates, you can remove the @Version annotation from your entities, which will result in the loss of any optimistic guarantees of data integrity .
  2. You can automatically repeat the deprecated query for a new snapshot of the entity database.
  3. In addition, you can use a pessimistic lock (for example, PESSIMISTIC_WRITE or PESSIMISTIC_READ ) so that after receiving a lock, no other transaction can change the locked records.
+15
source

Spring is a good solution for retries.

0
source

All Articles