电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Java中數據庫常用的兩把鎖之樂觀鎖和悲觀鎖

瀏覽:6日期:2022-08-28 11:53:46

在寫入數據庫的時候需要有鎖,比如同時寫入數據庫的時候會出現丟數據,那么就需要鎖機制。

數據鎖分為樂觀鎖和悲觀鎖,那么它們使用的場景如下:

1. 樂觀鎖適用于寫少讀多的情景,因為這種樂觀鎖相當于JAVA的CAS,所以多條數據同時過來的時候,不用等待,可以立即進行返回。

2. 悲觀鎖適用于寫多讀少的情景,這種情況也相當于JAVA的synchronized,reentrantLock等,大量數據過來的時候,只有一條數據可以被寫入,其他的數據需要等待。執行完成后下一條數據可以繼續。

他們實現的方式上有所不同,樂觀鎖采用版本號的方式,即當前版本號如果對應上了就可以寫入數據,如果判斷當前版本號不一致,那么就不會更新成功,比如 update table set column = value where version=${version} and otherKey = ${otherKey}。悲觀鎖實現的機制一般是在執行更新語句的時候采用for update方式,比如 update table set column=’value’ for update。這種情況where條件呢一定要涉及到數據庫對應的索引字段,這樣才會是行級鎖,否則會是表鎖,這樣執行速度會變慢。

下面我就弄一個spring boot(springboot 2.1.1 + mysql + lombok + aop + jpa)工程,然后逐漸的實現樂觀鎖和悲觀鎖。假設有一個場景,有一個catalog商品目錄表,然后還有一個browse瀏覽表,假如一個商品被瀏覽了,那么就需要記錄下瀏覽的user是誰,并且記錄訪問的總數。

表的結構非常簡單:

create table catalog (id int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT ’主鍵’,name varchar(50) NOT NULL DEFAULT ’’ COMMENT ’商品名稱’,browse_count int(11) NOT NULL DEFAULT 0 COMMENT ’瀏覽數’,version int(11) NOT NULL DEFAULT 0 COMMENT ’樂觀鎖,版本號’,PRIMARY KEY(id)) ENGINE=INNODB DEFAULT CHARSET=utf8;CREATE table browse (id int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT ’主鍵’,cata_id int(11) NOT NULL COMMENT ’商品ID’,user varchar(50) NOT NULL DEFAULT ’’ COMMENT ’’,create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ’創建時間’,PRIMARY KEY(id)) ENGINE=INNODB DEFAULT CHARSET=utf8;

 POM.XML的依賴如下:

<?xml version='1.0' encoding='UTF-8'?><project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd'> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hqs</groupId> <artifactId>dblock</artifactId> <version>1.0-SNAPSHOT</version> <name>dblock</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- aop --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>

項目的結構如下:

Java中數據庫常用的兩把鎖之樂觀鎖和悲觀鎖

介紹一下項目的結構的內容:

entity包: 實體類包。

repository包:數據庫repository

service包: 提供服務的service

controller包: 控制器寫入用于編寫requestMapping。相關請求的入口類

annotation包: 自定義注解,用于重試。

aspect包: 用于對自定義注解進行切面。

DblockApplication: springboot的啟動類。

DblockApplicationTests: 測試類。

咱們看一下核心代碼的實現,參考如下,使用dataJpa非常方便,集成了CrudRepository就可以實現簡單的CRUD,非常方便,有興趣的同學可以自行研究。

實現樂觀鎖的方式有兩種:

1. 更新的時候將version字段傳過來,然后更新的時候就可以進行version判斷,如果version可以匹配上,那么就可以更新(方法:updateCatalogWithVersion)。

2. 在實體類上的version字段上加入version,可以不用自己寫SQL語句就可以它就可以自行的按照version匹配和更新,是不是很簡單。 

public interface CatalogRepository extends CrudRepository<Catalog, Long> { @Query(value = 'select * from Catalog a where a.id = :id for update', nativeQuery = true) Optional<Catalog> findCatalogsForUpdate(@Param('id') Long id); @Lock(value = LockModeType.PESSIMISTIC_WRITE) //代表行級鎖 @Query('select a from Catalog a where a.id = :id') Optional<Catalog> findCatalogWithPessimisticLock(@Param('id') Long id); @Modifying(clearAutomatically = true) //修改時需要帶上 @Query(value = 'update Catalog set browse_count = :browseCount, version = version + 1 where id = :id ' + 'and version = :version', nativeQuery = true) int updateCatalogWithVersion(@Param('id') Long id, @Param('browseCount') Long browseCount, @Param('version') Long version);}

實現悲觀鎖的時候也有兩種方式:

1. 自行寫原生SQL,然后寫上for update語句。(方法:findCatalogsForUpdate)

2. 使用@Lock注解,并且設置值為LockModeType.PESSIMISTIC_WRITE即可代表行級鎖。

還有我寫的測試類,方便大家進行測試:

package com.hqs.dblock;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.web.client.TestRestTemplate;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.util.LinkedMultiValueMap;import org.springframework.util.MultiValueMap;@RunWith(SpringRunner.class)@SpringBootTest(classes = DblockApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)public class DblockApplicationTests { @Autowired private TestRestTemplate testRestTemplate; @Test public void browseCatalogTest() { String url = 'http://localhost:8888/catalog'; for(int i = 0; i < 100; i++) { final int num = i; new Thread(() -> { MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add('catalogId', '1'); params.add('user', 'user' + num); String result = testRestTemplate.postForObject(url, params, String.class); System.out.println('-------------' + result); } ).start(); } } @Test public void browseCatalogTestRetry() { String url = 'http://localhost:8888/catalogRetry'; for(int i = 0; i < 100; i++) { final int num = i; new Thread(() -> { MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add('catalogId', '1'); params.add('user', 'user' + num); String result = testRestTemplate.postForObject(url, params, String.class); System.out.println('-------------' + result); } ).start(); } }}

調用100次,即一個商品可以瀏覽一百次,采用悲觀鎖,catalog表的數據都是100,并且browse表也是100條記錄。采用樂觀鎖的時候,因為版本號的匹配關系,那么會有一些記錄丟失,但是這兩個表的數據是可以對應上的。

樂觀鎖失敗后會拋出ObjectOptimisticLockingFailureException,那么我們就針對這塊考慮一下重試,下面我就自定義了一個注解,用于做切面。

package com.hqs.dblock.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RetryOnFailure {}

針對注解進行切面,見如下代碼。我設置了最大重試次數5,然后超過5次后就不再重試。

package com.hqs.dblock.aspect;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.hibernate.StaleObjectStateException;import org.springframework.orm.ObjectOptimisticLockingFailureException;import org.springframework.stereotype.Component;@Slf4j@Aspect@Componentpublic class RetryAspect { public static final int MAX_RETRY_TIMES = 5;//max retry times @Pointcut('@annotation(com.hqs.dblock.annotation.RetryOnFailure)') //self-defined pointcount for RetryOnFailure public void retryOnFailure(){} @Around('retryOnFailure()') //around can be execute before and after the point public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int attempts = 0; do { attempts++; try { pjp.proceed(); } catch (Exception e) { if(e instanceof ObjectOptimisticLockingFailureException || e instanceof StaleObjectStateException) { log.info('retrying....times:{}', attempts); if(attempts > MAX_RETRY_TIMES) { log.info('retry excceed the max times..'); throw e; } } } } while (attempts < MAX_RETRY_TIMES); return null; }}

大致思路是這樣了,歡迎拍磚。

GIT代碼:https://github.com/stonehqs/dblock

到此這篇關于數據庫常用的兩把鎖之樂觀鎖和悲觀鎖的文章就介紹到這了,更多相關數據庫鎖 樂觀鎖 悲觀鎖內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
主站蜘蛛池模板: 卫生纸复卷机|抽纸机|卫生纸加工设备|做卫生纸机器|小型卫生纸加工需要什么设备|卫生纸机器设备多少钱一台|许昌恒源纸品机械有限公司 | 聚氨酯催化剂K15,延迟催化剂SA-1,叔胺延迟催化剂,DBU,二甲基哌嗪,催化剂TMR-2,-聚氨酯催化剂生产厂家 | 南汇8424西瓜_南汇玉菇甜瓜-南汇水蜜桃价格| 大型果蔬切片机-水果冬瓜削皮机-洗菜机切菜机-肇庆市凤翔餐饮设备有限公司 | 电气控制系统集成商-PLC控制柜变频控制柜-非标自动化定制-电气控制柜成套-NIDEC CT变频器-威肯自动化控制 | 薄壁轴承-等截面薄壁轴承生产厂家-洛阳薄壁精密轴承有限公司 | 硬齿面减速机_厂家-山东安吉富传动设备股份有限公司 | 自清洗过滤器-全自动自清洗过反冲洗过滤器 - 中乂(北京)科技有限公司 | 浙江栓钉_焊钉_剪力钉厂家批发_杭州八建五金制造有限公司 | 恒温槽_恒温水槽_恒温水浴槽-上海方瑞仪器有限公司 | 神超官网_焊接圆锯片_高速钢锯片_硬质合金锯片_浙江神超锯业制造有限公司 | 真空粉体取样阀,电动楔式闸阀,电动针型阀-耐苛尔(上海)自动化仪表有限公司 | 一航网络-软件测评官网 | 农产品溯源系统_农产品质量安全追溯系统_溯源系统 | 恒温恒湿箱(药品/保健品/食品/半导体/细菌)-兰贝石(北京)科技有限公司 | 天长市晶耀仪表有限公司| 温控器生产厂家-提供温度开关/热保护器定制与批发-惠州市华恺威电子科技有限公司 | 国际学校_国际学校哪个好_国际课程学校-国际学校择校网 | 自动化生产线-自动化装配线-直流电机自动化生产线-东莞市慧百自动化有限公司 | 微波消解仪器_智能微波消解仪报价_高压微波消解仪厂家_那艾 | 中空玻璃生产线,玻璃加工设备,全自动封胶线,铝条折弯机,双组份打胶机,丁基胶/卧式/立式全自动涂布机,玻璃设备-山东昌盛数控设备有限公司 | 手持气象站_便携式气象站_农业气象站_负氧离子监测站-山东万象环境 | 广州小程序开发_APP开发公司_分销商城系统定制_小跑科技 | 众品家具网-家具品牌招商_家具代理加盟_家具门户的首选网络媒体。 | Q361F全焊接球阀,200X减压稳压阀,ZJHP气动单座调节阀-上海戎钛 | BOE画框屏-触摸一体机-触控查询一体机-触摸屏一体机价格-厂家直销-触发电子 | 成都离婚律师|成都结婚律师|成都离婚财产分割律师|成都律师-成都离婚律师网 | 济南品牌设计-济南品牌策划-即合品牌策划设计-山东即合官网 | 手表腕表维修保养鉴定售后服务中心网点 - 名表维修保养 | 乐泰胶水_loctite_乐泰胶_汉高乐泰授权(中国)总代理-鑫华良供应链 | 质检报告_CE认证_FCC认证_SRRC认证_PSE认证_第三方检测机构-深圳市环测威检测技术有限公司 | 磁力抛光机_磁力研磨机_磁力去毛刺机_精密五金零件抛光设备厂家-冠古科技 | 聚天冬氨酸,亚氨基二琥珀酸四钠,PASP,IDS - 远联化工 | B2B网站_B2B免费发布信息网站_B2B企业贸易平台 - 企资网 | 粉末冶金-粉末冶金齿轮-粉末冶金零件厂家-东莞市正朗精密金属零件有限公司 | 特材真空腔体_哈氏合金/镍基合金/纯镍腔体-无锡国德机械制造有限公司 | 节流截止放空阀-不锈钢阀门-气动|电动截止阀-鸿华阀门有限公司 | 二手电脑回收_二手打印机回收_二手复印机回_硒鼓墨盒回收-广州益美二手电脑回收公司 | 乐之康护 - 专业护工服务平台,提供医院陪护-居家照护-居家康复 | 驾驶式洗地机/扫地机_全自动洗地机_工业洗地机_荣事达工厂官网 | BESWICK球阀,BESWICK接头,BURKERT膜片阀,美国SEL继电器-东莞市广联自动化科技有限公司 |