前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot入门建站全系列(二十)SpringDataJpa使用乐观锁与悲观锁

SpringBoot入门建站全系列(二十)SpringDataJpa使用乐观锁与悲观锁

原创
作者头像
品茗IT
修改2019-08-26 11:16:29
1.3K0
修改2019-08-26 11:16:29
举报
文章被收录于专栏:品茗IT品茗IT

SpringBoot入门建站全系列(二十)SpringDataJpa使用乐观锁与悲观锁

一、概述

之前有两篇《SpringBoot入门建站全系列(五)使用Spring-data-jpa操作数据库CRUD》《SpringBoot入门建站全系列(六)Spring-data-jpa进阶使用》介绍了Spring如何结合Spring-data-jpa进行数据库访问操作。这一篇介绍下springboot环境下spring-data-jpa如何进行乐观锁、悲观锁的使用。

悲观锁和乐观锁的概念:

  • 悲观锁:就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
  • 乐观锁:不上锁,读取的时候带版本号,写入的时候带着这个版本号,如果不一致就失败,乐观锁适用于多读的应用类型,因为写多的时候会经常失败。

代码可以在Springboot组件化构建https://www.pomit.cn/java/spring/springboot.html中的JpaLock组件中查看,并下载。

二、配置

本文假设你已经引入spring-boot-starter-web。已经是个SpringBoot项目了,如果不会搭建,可以打开这篇文章看一看《SpringBoot入门建站全系列(一)项目建立》

2.1 Maven依赖

需要引入spring-boot-starter-data-jpa,这里要访问数据库,所以要依赖数据库相关jar包。

代码语言:txt
复制
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
 <dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-dbcp2</artifactId>
</dependency>
2.2 配置文件

在application.properties 中需要添加下面的配置:

代码语言:txt
复制
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.dbcp2.max-wait-millis=60000
spring.datasource.dbcp2.min-idle=20
spring.datasource.dbcp2.initial-size=2
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.connection-properties=characterEncoding=utf8
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.test-while-idle=true
spring.datasource.dbcp2.test-on-borrow=true
spring.datasource.dbcp2.test-on-return=false

spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boot?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=cff
spring.datasource.password=123456

#JPA Configuration:  
spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.implicit-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

这里面,包含了数据库连接信息、数据源的连接池配置信息、jpa配置信息。

spring.jpa.hibernate.ddl-auto属性,是对表的操作:

  • create 启动时删数据库中的表,然后创建,退出时不删除数据表
  • create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
  • update 如果启动时表格式不一致则更新表,原有数据保留
  • validate 项目启动表结构进行校验 如果不一致则报错
  • none 啥都不做

spring.jpa.hibernate.naming.implicit-strategy和spring.jpa.hibernate.naming.physical-strategy是对表和实体字段映射的默认处理方式。

实体名称映射到数据库中时,分成两个步骤:

  • 第一个阶段是从对象模型中提取一个合适的逻辑名称,这个逻辑名称可以由用户指定,通过@Column和@Table等注解完成,也可以通过被Hibernate的ImplicitNamingStrategy指定;
  • 第二个阶段是将上述的逻辑名称解析成物理名称,物理名称是由Hibernate中的PhysicalNamingStrategy决定;

PhysicalNamingStrategy和ImplicitNamingStrategy的区别:

  • 从处理的效果上来看,其实没有什么区别,但是从程序设计分层的角度来看,类似于MVC的分层,ImplicitNamingStrategy只管模型对象层次的处理,PhysicalNamingStrategy只管映射成真实的数据名称的处理,但是为了达到相同的效果,比如将userName映射城数据列时,在PhysicalNamingStrategy决定映射成user_name,但是在ImplicitNamingStrategy也可以做到;
  • 从处理的场景来看, 无论对象模型中是否显式地指定列名或者已经被隐式决定,PhysicalNamingStrategy都会应用; 但是对于ImplicitNamingStrategy,仅仅只有当没有显式地提供名称时才会使用,也就是说当对象模型中已经指定了@Table或者@Entity等name时,设置的ImplicitNamingStrategy并不会起作用。

所以,这里的配置,映射到表字段时,所有点都被下划线替换,骆驼情况也被下划线替换。默认情况下,所有表名都以小写生成

三、悲观锁

配置完成后,就可以拿来测试悲观锁和乐观锁了。

悲观锁在数据库的访问中使用,表现为:前一次请求没执行完,后面一个请求就一直在等待。

3.1 Dao层

数据库要实现悲观锁,就是将sql语句带上for update即可。 for update 是行锁

在Jpa的Repository这一层,直接在方法上加上@Lock(LockModeType.PESSIMISTIC_WRITE),就实现了悲观锁。

UserInfoDao :

代码语言:txt
复制
package com.cff.springbootwork.jpalock.dao;

import javax.persistence.LockModeType;

import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.repository.CrudRepository;

import com.cff.springbootwork.jpalock.domain.UserInfo;

public interface UserInfoDao extends CrudRepository<UserInfo, String> {
	@Lock(LockModeType.PESSIMISTIC_WRITE)
	UserInfo findByUserName(String userName);
}

注意加上@Repository注解。实体要加上@Entity和@Table注解。

3.2 Service层

更新数据库前,先调用findByUserName方法,使用上面的配置的悲观锁锁定表记录,然后再更新。

UserInfoService :

代码语言:txt
复制
package com.cff.springbootwork.jpalock.service;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.cff.springbootwork.jpalock.dao.UserInfoDao;
import com.cff.springbootwork.jpalock.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 :

代码语言:txt
复制
package com.cff.springbootwork.jpalock.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 com.cff.springbootwork.jpalock.domain.UserInfo;
import com.cff.springbootwork.jpalock.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字段,普通字段就行,别设置成主键自增啥的。

代码语言:txt
复制
@Version
private Integer version;
4.2 Service层

service层我们做一下简单的调整。更新数据库前,先调用findById方法,查询出当前的版本号,然后再更新。

UserInfoService :

代码语言:txt
复制
package com.cff.springbootwork.jpalock.service;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.cff.springbootwork.jpalock.dao.UserInfoDao;
import com.cff.springbootwork.jpalock.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 :

代码语言:txt
复制
package com.cff.springbootwork.jpalock.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 com.cff.springbootwork.jpalock.domain.UserInfo;
import com.cff.springbootwork.jpalock.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:

代码语言:txt
复制
package com.cff.springbootwork.jpalock.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name = "user_info_test")
public class UserInfo {
	@Id
	@Column(name = "user_name")
	private String userName;
	private String passwd;
	private String name;
	private String mobile;
	private String valid;
	@Version
	private Integer version;

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getUserName() {
		return userName;
	}

	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}

	public String getPasswd() {
		return passwd;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setMobile(String mobile) {
		this.mobile = mobile;
	}

	public String getMobile() {
		return mobile;
	}

	public void setValid(String valid) {
		this.valid = valid;
	}

	public String getValid() {
		return valid;
	}

}

UserInfoService :

代码语言:txt
复制
package com.cff.springbootwork.jpalock.service;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.cff.springbootwork.jpalock.dao.UserInfoDao;
import com.cff.springbootwork.jpalock.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);
	}

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

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot入门建站全系列(二十)SpringDataJpa使用乐观锁与悲观锁
    • 一、概述
      • 二、配置
        • 2.1 Maven依赖
        • 2.2 配置文件
      • 三、悲观锁
        • 3.1 Dao层
        • 3.2 Service层
        • 3.3 测试Web层
      • 四、乐观锁
        • 4.1 实体添加@Version
        • 4.2 Service层
        • 4.2 测试Web层
      • 五、过程中用到的完整实体和Service
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档