Spring整合SpringDataJpa的乐观锁与悲观锁详情

Spring整合SpringDataJpa的乐观锁与悲观锁详情Spring整合SpringDataJpa的乐观锁与悲观锁详情一、概述上一篇《Spring和SpringDataJpa整合详解》介绍了Spring如何结合Spring-data-jpa进行数据库访问操作。这一篇介绍下springmvc环境下spring-data-jpa如何进行乐观锁、悲观锁的使用。悲观锁和乐观锁的概念:悲观锁:就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到…

大家好,又见面了,我是你们的朋友全栈君。

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组件中查看,并下载。

首发地址:

如果大家正在寻找一个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。

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>cn.pomit</groupId>
		<artifactId>SpringWork</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>JpaLock</artifactId>
	<packaging>jar</packaging>
	<name>JpaLock</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>2.0.10.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>javax.transaction</groupId>
			<artifactId>javax.transaction-api</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>5.2.17.Final</version>
			<scope>compile</scope>
			<exclusions>
				<exclusion>
					<artifactId>jboss-transaction-api_1.2_spec</artifactId>
					<groupId>org.jboss.spec.javax.transaction</groupId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<build>
		<finalName>JpaLock</finalName>
	</build>
</project>

父模块可以在https://www.pomit.cn/spring/SpringWork/pom.xml获取。

2.2 Spring配置

需要配置数据源、jdbcTemplate、entityManagerFactory、transactionManager和jpa:repositories。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

	<context:annotation-config />
	<context:component-scan base-package="cn.pomit.springwork.springdatajpa">
	</context:component-scan>

	<bean id="annotationPropertyConfigurerJpaLock" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="order" value="1" />
		<property name="ignoreUnresolvablePlaceholders" value="true" />
		<property name="locations">
			<list>
				<value>classpath:db.properties</value>
			</list>
		</property>
	</bean>


	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="${db.dirverClass}"></property>
		<property name="url" value="${db.url}" />
		<property name="username" value="${db.username}" />
		<property name="password" value="${db.password}" />

		<property name="initialSize" value="1" />
		<property name="minIdle" value="1" />
		<property name="maxTotal" value="20" />

		<property name="validationQuery" value="SELECT 1" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
	</bean>

	<!-- jdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<property name="packagesToScan" value="cn.pomit.springwork.springdatajpa.domain"></property>
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect">
			</property>
			</bean>
		</property>
	</bean>
	<jpa:repositories base-package="cn.pomit.springwork.springdatajpa.dao" />
	
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<!-- 使用annotation定义事务 -->
	<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
</beans>

这里面,需要注意的是:

  • 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<UserInfo, String> { 
   
	@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技术吧!
品茗IT交流群

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/130987.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • pycharm基本操作_有哪些简单的电脑运行

    pycharm基本操作_有哪些简单的电脑运行很多初学者一上来就开始使用Pycharm写代码,我个人不太建议这么做,因为IDE强大的功能背后隐藏了很多技术细节,以至于脱离了IDE就不会写代码了,所以在初级阶段还是老老实实用一些简单的编辑器来写,比如sublime或者Vim,这样有利用加深对Python标准库中常用模块和方法的记忆,也有易于对问题的定位和排查。当熟悉一门语言后,我们就应该找到更高效的工具来提高生产效率,Pycharm是

    2022年8月27日
    2
  • Loadrunner教程–常用操做流程

    Loadrunner教程–常用操做流程Loadrunner教程–常用操做流程作者博客:vps精选网1loadrunner压力测试一般使用流程1.1loadrunner压力测试原理本质就是在loadrunner上模拟多个用户同时按固定行为访问web站点。其中固定行为在loadrunner中是通过录制脚本定义的,多个用户同时访问的策略是在loadrunner的场景中定义的。1.2压测思路…

    2022年5月23日
    73
  • linux中挂载新的数据盘「建议收藏」

    linux中挂载新的数据盘「建议收藏」linux中挂载新的数据盘

    2022年4月24日
    40
  • Linux安装Composer

    Linux安装Composer

    2021年10月26日
    51
  • java购物商城_基于javaweb实现的简单购物商城[通俗易懂]

    java购物商城_基于javaweb实现的简单购物商城[通俗易懂]【实例简介】基于javaweb的购物商城,简单,适合新手练手,导入项目即可运行【实例截图】【核心代码】shop(1)└──shop├──rcm│├──src││├──META-INF│││└──MANIFEST.MF││└──rcm││├──dao│││├──DataBaseUtils.j…

    2022年7月8日
    19
  • Python爬虫常用:谷歌浏览器驱动——Chromedriver 插件安装教程

    Python爬虫常用:谷歌浏览器驱动——Chromedriver 插件安装教程我们在做爬虫的时候经常要使用谷歌浏览器驱动,今天分享下这个Chromedriver插件的安装方法。第一步、打开谷歌浏览器打开设置面板第二步、查看当前谷歌浏览器版本号第三步、点击插件下载,进去这个界面,找到跟自己谷歌浏览器版本号最相近的那一个。下载地址:插件下载这里有许多的版本,注意icons/向下的版本是无用的。选择icons/以上的版本,越靠近icons/的版本越新。第四步、找到对应版本后点击它计进入这个页面,点击notes.txt查看与Chrome版本是否对应。第五步、回

    2022年5月11日
    62

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号