web-dev-qa-db-ja.com

複数の同時トランザクション要求に対して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);
}

}

16
adn.911

楽観的ロックの例外 失われた更新を防ぐ であり、無視しないでください。

これを一般的な例外ハンドラーでキャッチし、ユーザーを現在のワークフローの開始点にリダイレクトすることで、ユーザーが認識していない同時変更があったことを示すことができます。

更新が失われても構わない場合は、@Versionエンティティからのアノテーション、つまり 楽観的なロックデータの完全性の保証を失う

ここで、新しいエンティティデータベースのスナップショットに対して auto-retry を実行すると問題が解決すると考えるかもしれませんが、ロード時のバージョンがまだ低いため、同じ楽観的ロック例外が発生します。 DB内の現在のエンティティバージョン。

また、 悲観的ロックを使用できます(例:PESSIMISTIC_WRITEまたはPESSIMISTIC_READ 行レベルのロックが取得されると、他のトランザクションはロックされたレコードを変更できなくなります。

26
Vlad Mihalcea