如何正确处理多个并发事务请求的 JPA ObjectOptimisticLockException?

23
所以,我正在开发一个简单的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
如果用户提交的内容已经发生了变化或者消失,那么你需要向用户显示一个错误信息,并刷新页面,让他们再次尝试。这种异常的目的是为了告诉用户,帖子已经发生了变化或者消失了,用户的修改可能不再有意义。因此,用户应该选择下一步该怎么做。 - JB Nizet
1个回答

39

JPA的OptimisticLockException可以避免丢失更新,你不应该忽略它。

你可以在通用异常处理程序中简单地捕获它,并将用户重定向到当前执行工作流的起始点,指示流必须重新启动,因为它正在使用过时数据进行操作。

然而,如果你的应用程序需求不需要防止丢失更新现象,则可以从实体中简单地删除@Version注释,从而破坏可串行性。

现在,你可能认为针对新的实体数据库快照的自动重试可以解决问题,但你最终会遇到相同的乐观锁异常,因为加载时版本仍低于DB中的当前实体版本。

此外,你可以使用悲观锁(例如,PESSIMISTIC_WRITEPESSIMISTIC_READ),这样一旦获取了行级锁,就没有其他事务可以修改已锁定的记录。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接