Spring整合SpringDataJpa的乐观锁与悲观锁详情
一、概述
上一篇《Spring和SpringDataJpa整合详解》介绍了Spring如何结合Spring-data-jpa进行数据库访问操作。这一篇介绍下springmvc环境下spring-data-jpa如何进行乐观锁、悲观锁的使用。
悲观锁和乐观锁的概念:
- 悲观锁:就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
- 乐观锁:不上锁,读取的时候带版本号,写入的时候带着这个版本号,如果不一致就失败,乐观锁适用于多读的应用类型,因为写多的时候会经常失败。
代码可以在Spring组件化构建https://www.pomit.cn/java/spring/spring.html中的JpaLock组件中查看,并下载。
首发地址:
品茗IT-同步发布
品茗IT 提供在线支持:
一键快速构建Spring项目工具
一键快速构建SpringBoot项目工具
一键快速构建SpringCloud项目工具
一站式Springboot项目生成
如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以加入我们的java学习圈,点击即可加入,共同学习,节约学习时间,减少很多在学习中遇到的难题。
二、环境配置
本文假设你已经引入Spring必备的一切了,已经是个Spring项目了,如果不会搭建,可以打开这篇文章看一看《Spring和Spring Mvc 5整合详解》。
2.1 maven依赖
和上一篇《Spring和SpringDataJpa整合详解》的配置一样,
使用Spring-data-jpa需要引入spring-data-jpa,因为是非Springboot项目,我们不能通过starter引入,需要引入spring-data-jpa、javax.transaction-api、hibernate-core。
4.0.0
cn.pomit
SpringWork
0.0.1-SNAPSHOT
JpaLock
jar
JpaLock
http://maven.apache.org
org.springframework
spring-web
org.springframework
spring-context
org.springframework
spring-orm
mysql
mysql-connector-java
log4j
log4j
org.apache.commons
commons-dbcp2
org.springframework.data
spring-data-jpa
2.0.10.RELEASE
javax.transaction
javax.transaction-api
1.2
org.hibernate
hibernate-core
5.2.17.Final
compile
jboss-transaction-api_1.2_spec
org.jboss.spec.javax.transaction
JpaLock
父模块可以在https://www.pomit.cn/spring/SpringWork/pom.xml获取。
2.2 Spring配置
需要配置数据源、jdbcTemplate、entityManagerFactory、transactionManager和jpa:repositories。
classpath:db.properties
这里面,需要注意的是:
entityManagerFactory,是实体和数据库选择信息。
jpa:repositories,指明Spring-data-jpa的repositories地址。就是我们的数据库交互层。
transactionManager,事务处理器。
tx:annotation-driven:开启事务注解。
db.properties中存放数据库的地址端口等连接信息。
db.properties:
db.url=jdbc:mysql://127.0.0.1:3306/boot?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
db.username=cff
db.password=123456
db.dirverClass=com.mysql.cj.jdbc.Driver
三、悲观锁
悲观锁在数据库的访问中使用,表现为:前一次请求没执行完,后面一个请求就一直在等待。
3.1 Dao层
数据库要实现悲观锁,就是将sql语句带上for update即可。 for update 是行锁
在Jpa的Repository这一层,直接在方法上加上@Lock(LockModeType.PESSIMISTIC_WRITE),就实现了悲观锁。
UserInfoDao :
package cn.pomit.springwork.springdatajpa.dao;
import javax.persistence.LockModeType;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import cn.pomit.springwork.springdatajpa.domain.UserInfo;
@Repository
public interface UserInfoDao extends CrudRepository {
@Lock(LockModeType.PESSIMISTIC_WRITE)
UserInfo findByUserName(String userName);
}
注意加上@Repository注解。实体要加上@Entity和@Table注解。
3.2 Service层
更新数据库前,先调用findByUserName方法,使用上面的配置的悲观锁锁定表记录,然后再更新。
UserInfoService :
package cn.pomit.springwork.springdatajpa.service;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import cn.pomit.springwork.springdatajpa.dao.UserInfoDao;
import cn.pomit.springwork.springdatajpa.domain.UserInfo;
@Service
public class UserInfoService {
@Autowired
UserInfoDao userInfoDao;
public void delete(String userName) {
userInfoDao.deleteById(userName);
}
public void save(UserInfo entity) {
userInfoDao.save(entity);
}
@Transactional
public UserInfo getUserInfoByUserNamePessimistic(String userName) {
return userInfoDao.findByUserName(userName);
}
@Transactional
public void updateWithTimePessimistic(UserInfo entity, int time) throws InterruptedException {
UserInfo userInfo = userInfoDao.findByUserName(entity.getUserName());
if (userInfo == null)
return;
if (!StringUtils.isEmpty(entity.getMobile())) {
userInfo.setMobile(entity.getMobile());
}
if (!StringUtils.isEmpty(entity.getName())) {
userInfo.setName(entity.getName());
}
Thread.sleep(time * 1000L);
userInfoDao.save(userInfo);
}
@Transactional
public void updatePessimistic(UserInfo entity) {
UserInfo userInfo = userInfoDao.findByUserName(entity.getUserName());
if (userInfo == null)
return;
if (!StringUtils.isEmpty(entity.getMobile())) {
userInfo.setMobile(entity.getMobile());
}
if (!StringUtils.isEmpty(entity.getName())) {
userInfo.setName(entity.getName());
}
userInfoDao.save(userInfo);
}
}
3.3 测试Web层
可以先调用/update/{time}接口,延迟执行,然后马上调用/update接口,会发现,/update接口一直在等待/update/{time}接口执行完成。
JpaPessLockRest :
package cn.pomit.springwork.springdatajpa.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import cn.pomit.springwork.springdatajpa.domain.UserInfo;
import cn.pomit.springwork.springdatajpa.service.UserInfoService;
/**
* 测试悲观锁
*
* @author fufei
*
*/
@RestController
@RequestMapping("/jpapesslock")
public class JpaPessLockRest {
@Autowired
UserInfoService userInfoService;
@RequestMapping(value = "/detail/{name}", method = { RequestMethod.GET })
public UserInfo detail(@PathVariable("name") String name) {
return userInfoService.getUserInfoByUserNamePessimistic(name);
}
@RequestMapping(value = "/save")
public String save(@RequestBody UserInfo userInfo) throws InterruptedException {
userInfoService.save(userInfo);
return "0000";
}
@RequestMapping(value = "/update/{time}")
public String update(@RequestBody UserInfo userInfo, @PathVariable("time") int time) throws InterruptedException {
userInfoService.updateWithTimePessimistic(userInfo, time);
return "0000";
}
@RequestMapping(value = "/update")
public String update(@RequestBody UserInfo userInfo) throws InterruptedException {
userInfoService.updatePessimistic(userInfo);
return "0000";
}
}
四、乐观锁
数据库访问dao层还是3.1那个dao。
4.1 实体添加@Version
UserInfo实体增加字段version,并添加注解@Version。当然,数据库也要加上version字段,普通字段就行,别设置成主键自增啥的。
@Version
private Integer version;
4.2 Service层
service层我们做一下简单的调整。更新数据库前,先调用findById方法,查询出当前的版本号,然后再更新。
UserInfoService :
package cn.pomit.springwork.springdatajpa.service;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import cn.pomit.springwork.springdatajpa.dao.UserInfoDao;
import cn.pomit.springwork.springdatajpa.domain.UserInfo;
@Service
public class UserInfoService {
@Autowired
UserInfoDao userInfoDao;
public UserInfo getUserInfoByUserName(String userName) {
return userInfoDao.findById(userName).orElse(null);
}
public void save(UserInfo entity) {
userInfoDao.save(entity);
}
@Transactional
public void updateWithTimeOptimistic(UserInfo entity, int time) throws InterruptedException {
UserInfo userInfo = userInfoDao.findById(entity.getUserName()).orElse(null);
if (userInfo == null)
return;
if (!StringUtils.isEmpty(entity.getMobile())) {
userInfo.setMobile(entity.getMobile());
}
if (!StringUtils.isEmpty(entity.getName())) {
userInfo.setName(entity.getName());
}
Thread.sleep(time * 1000L);
userInfoDao.save(userInfo);
}
public void delete(String userName) {
userInfoDao.deleteById(userName);
}
@Transactional
public void updateOptimistic(UserInfo entity) {
UserInfo userInfo = userInfoDao.findById(entity.getUserName()).orElse(null);
if (userInfo == null)
return;
if (!StringUtils.isEmpty(entity.getMobile())) {
userInfo.setMobile(entity.getMobile());
}
if (!StringUtils.isEmpty(entity.getName())) {
userInfo.setName(entity.getName());
}
userInfoDao.save(userInfo);
}
}
4.2 测试Web层
可以先调用/update/{time}接口,延迟执行,然后马上调用/update接口,会发现,/update接口不会等待/update/{time}接口执行完成,读取完版本号能够成功更新数据,但是/update/{time}接口等待足够时间以后,更新的时候会报错,因为它的版本和数据库的已经不一致了。
JpaOptiLockRest :
package cn.pomit.springwork.springdatajpa.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import cn.pomit.springwork.springdatajpa.domain.UserInfo;
import cn.pomit.springwork.springdatajpa.service.UserInfoService;
/**
* 测试乐观锁
* @author fufei
*
*/
@RestController
@RequestMapping("/jpalock")
public class JpaOptiLockRest {
@Autowired
UserInfoService userInfoService;
@RequestMapping(value = "/detail/{name}", method = { RequestMethod.GET })
public UserInfo detail(@PathVariable("name") String name) {
return userInfoService.getUserInfoByUserName(name);
}
@RequestMapping(value = "/save")
public String save(@RequestBody UserInfo userInfo) throws InterruptedException {
userInfoService.save(userInfo);
return "0000";
}
@RequestMapping(value = "/update/{time}")
public String update(@RequestBody UserInfo userInfo, @PathVariable("time") int time) throws InterruptedException {
userInfoService.updateWithTimeOptimistic(userInfo, time);
return "0000";
}
@RequestMapping(value = "/update")
public String update(@RequestBody UserInfo userInfo) throws InterruptedException {
userInfoService.updateOptimistic(userInfo);
return "0000";
}
}
五、过程中用到的完整实体和Service
UserInfo:
UserInfoService :
详细完整的实体,可以访问品茗IT-博客《Spring和SpringDataJpa整合的乐观锁与悲观锁详情》进行查看
品茗IT-博客专题:https://www.pomit.cn/lecture.html汇总了Spring专题、Springboot专题、SpringCloud专题、web基础配置专题。
快速构建项目
Spring组件化构建
SpringBoot组件化构建
SpringCloud服务化构建
喜欢这篇文章么,喜欢就加入我们一起讨论Spring技术吧!