Как правильно обработать JPA ObjectOptimisticLockException для нескольких одновременных запросов транзакций?

Итак, я работал над простым проектом Spring MVC + JPA (hibernate), в котором есть пользователи, которые могут создавать сообщения и комментировать сообщения своих друзей (что-то вроде небольшой социальной сети) . Я все еще относительно нов, используя JPA Hibernate. Поэтому, когда я пытаюсь протестировать из браузера, отправляющего несколько запросов на какую-либо задачу (содержащую транзакции) очень быстро 2-3 раза, пока обрабатывается предыдущий запрос, я получаю исключение OptimisticLockException . Вот трассировка стека..

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)

Теперь, как мне это исправить? Как правильно обработать это исключение ObjectOptimisticLockException, когда несколько запросов транзакций происходят одновременно? Есть ли хороший патен, которому я должен следовать? Нужно ли использовать какой-то механизм пессимистической блокировки?

Вот DAO, который я сейчас использую.. Заранее спасибо.:)

@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);
}

}

1 ответ

Решение

Исключение оптимистической блокировки предотвращает потерю обновлений, и вы не должны игнорировать это. Вы можете просто перехватить его в обычном обработчике исключений и перенаправить пользователя в текущую начальную точку рабочего процесса, указывая, что произошло параллельное изменение, о котором он не знал.

  1. Если вы не против потерять обновления, вы можете удалить @Version аннотации от ваших сущностей, поэтому потеря любой оптимистической блокировки гарантирует целостность данных.
  2. Вы можете автоматически повторить устаревший запрос для нового снимка базы данных сущностей.
  3. Также вы можете использовать пессимистическую блокировку (например, PESSIMISTIC_WRITE или же PESSIMISTIC_READ ), чтобы после получения блокировки никакая другая транзакция не могла изменить заблокированные записи.
Другие вопросы по тегам