在我们开发的项目中,大量的请求,或者同时的操作,很容易导致系统在业务上发生并发的问题。通常讲到并发,解决方案无非就是前端限制重复提交,后台进行悲观锁或者乐观锁限制。
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Query(value = "select * from article a where a.id = :id for update", nativeQuery = true)
Optional<Article> findArticleForUpdate(Long id);
}
利用JPA的@Lock行锁注解解决并发问题
NONE: No lock.
OPTIMISTIC: Optimistic lock.
OPTIMISTIC_FORCE_INCREMENT: Optimistic lock, with version update.
PESSIMISTIC_FORCE_INCREMENT: Pessimistic write lock, with version update.
PESSIMISTIC_READ: Pessimistic read lock.
PESSIMISTIC_WRITE: Pessimistic write lock.
READ: Synonymous with OPTIMISTIC.
WRITE: Synonymous with OPTIMISTIC_FORCE_INCREMENT.
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Article a where a.id = :id")
Optional<Article> findArticleWithPessimisticLock(Long id);
}
如果是@NameQuery,则可以
@NamedQuery(name="lockArticle",query="select a from Article a where a.id = :id",lockMode = PESSIMISTIC_READ)
public class Article
如果用entityManager的方式,则可以设置LocakMode:
Query query = entityManager.createQuery("from Article where articleId = :id");
query.setParameter("id", id);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.getResultList();
乐观锁与并发
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去修改。所以悲观锁是限制其他线程,而乐观锁是限制自己,虽然他的名字有锁,但是实际上不算上锁,通常为version版本号机制,还有CAS算法。
利用version字段解决并发问题
版本号机制就是在数据库中加一个字段version当作版本号。那么获取Article的时候就会带一个版本号,比如version=1,然后你对这个Article一波操作,操作完之后要插入到数据库了。校验一下version版本号,发现在数据库里对应Article记录的version=2,这和我手里的版本不一样啊,说明提交的Article不是最新的,那么就不能update到数据库了,进行报错把,这样就避免了并发时数据冲突的问题。
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Modifying
@Query(value = "update article set content= :content, version = version + 1 where id = :id and version = :version", nativeQuery = true)
int updateArticleWithVersion(Long id, String content, Long version);
}
public void postComment(Long articleId, String content) {
//get article
Optional<Article> articleOptional = articleRepository.findById(articleId);
//update with Optimistic Lock
int count = articleRepository.updateArticleWithVersion(article.getId(), content, article.getVersion());
if (count == 0) {
throw new RuntimeException("更新数据失败,请刷新重试");
}else{
articleRepository.save(article);
}
}
each entity class must have only one version attribute。每个实体类只能有一个@Version字段,不能多
it must be placed in the primary table for an entity mapped to several tables。对于映射到多个表的实体,必须将其放置在主表中
type of a version attribute must be one of the following: int, Integer, long, Long, short, Short, java.sql.Timestamp
int
Integer
long
Long
short
Short
java.sql.Timestamp
@Data
@Entity
public class Article{
@Id
private Long id;
//......
@Version
private Integer version;
}
Article article = entityManager.find(Article.class, id);
entityManager.lock(article , LockModeType.OPTIMISTIC);
entityManager.refresh(article , LockModeType.READ);
什么时候用悲观锁或者乐观锁
所以悲观锁和乐观锁没有绝对的好坏,必须结合具体的业务情况来决定使用哪一种方式。另外在阿里巴巴开发手册里也有提到:
如果每次访问冲突概率小于
20%
,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3
次。
阿里巴巴建议以冲突概率20%这个数值作为分界线来决定使用乐观锁和悲观锁,虽然说这个数值不是绝对的,但是作为阿里巴巴各个大佬总结出来的也是一个很好的参考。
BLOG地址:www.liangsonghua.com
关注微信公众号:松花皮蛋的黑板报,获取更多精彩!
公众号介绍:分享在京东工作的技术感悟,还有JAVA技术和业内最佳实践,大部分都是务实的、能看懂的、可复现的