Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

So, I was working on a simple Spring MVC + JPA (hibernate) project where there are Users who can makes Posts and make Comments on their friends Posts (somewhat like a small social network) . I am still relatively new using JPA Hibernate. So, when I try to test from browser sending multiple requests for some task ( containing transactions) very quickly 2-3 times while a 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 do i handle this ObjectOptimisticLockException properly when multiple transaction requests occurs simultaneously ? Is there any good patten that i should follow ? Do i need to use some sort of Pessimistic Locking mechanism ?

Here's the DAO that i am currently using .. 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);
}

}

like image 406
adn.911 Avatar asked Apr 11 '15 06:04

adn.911


People also ask

How do you handle optimistic lock exception?

To resolve this error we have two ways: Get the latest object from the database and set the old object values if you need those values to be persisted to the new object and merge it. For the old object set the latest version from Database.

How do you implement optimistic locking?

In order to use optimistic locking, we need to have an entity including a property with @Version annotation. While using it, each transaction that reads data holds the value of the version property. Before the transaction wants to make an update, it checks the version property again.

What is optimistic locking and pessimistic locking in hibernate?

There are two models for locking data in a database: Optimistic locking , where a record is locked only when changes are committed to the database. Pessimistic locking , where a record is locked while it is edited.

What is optimistic locking in hibernate?

Optimistic locking ssumes that multiple transactions can complete without affecting each other, and that therefore transactions can proceed without locking the data resources that they affect. Before committing, each transaction verifies that no other transaction has modified its data.


1 Answers

The JPA OptimisticLockException prevents lost updates, and you shouldn't ignore it.

You can simply catch it in a common exception handler and redirect the user to the starting point of the currently executing workflow, indicating that the flow has to be restarted since it was operating with stale data.

However, if your application requirements don't need to prevent the lost update anomaly, then you can simply remove the @Version annotation from your entities and, therefore, break Serializability.

Now, you might think that an auto-retry against a fresh entity database snapshot will fix the problem, but you will end up with the same optimistic locking exception since the load-time version is still lower than the current entity version in the DB.

Also, you can use pessimistic locking (e.g. PESSIMISTIC_WRITE or PESSIMISTIC_READ) so that, once a row-level lock is acquired, no other transaction can modify the locked record.

like image 53
Vlad Mihalcea Avatar answered Oct 25 '22 16:10

Vlad Mihalcea