作者:黄青石
来源:cnblogs.com/huangqingshi/p/10165409.html
在写入数据库的时候需要有锁,比如同时写入数据库的时候会出现丢数据,那么就需要锁机制。
数据锁分为乐观锁和悲观锁
它们使用的场景如下:
他们实现的方式上有所不同。
乐观锁采用版本号的方式,即当前版本号如果对应上了就可以写入数据,如果判断当前版本号不一致,那么就不会更新成功,比如
-
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"
?>
-
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"
>
-
4.0.0
-
-
org.springframework.boot
-
spring-boot-starter-parent
-
2.1.1.RELEASE
-
-
-
com.hqs
-
dblock
-
1.0-SNAPSHOT
-
dblock
-
Demo project for Spring Boot
-
-
-
1.8
-
-
-
-
-
org.springframework.boot
-
spring-boot-starter-web
-
-
-
-
org.springframework.boot
-
spring-boot-devtools
-
runtime
-
-
-
mysql
-
mysql-connector-java
-
runtime
-
-
-
org.springframework.boot
-
spring-boot-starter-test
-
test
-
-
-
org.springframework.boot
-
spring-boot-starter-data-jpa
-
-
-
mysql
-
mysql-connector-java
-
-
-
org.projectlombok
-
lombok
-
true
-
-
-
-
-
org.aspectj
-
aspectjweaver
-
1.8.4
-
-
-
-
-
-
-
-
org.springframework.boot
-
spring-boot-maven-plugin
-
-
-
-
-
项目的结构如下:
介绍一下项目的结构的内容:
-
entity包: 实体类包。
-
repository包:数据库repository
-
service包: 提供服务的service
-
controller包: 控制器写入用于编写requestMapping。相关请求的入口类
-
annotation包: 自定义注解,用于重试。
-
aspect包: 用于对自定义注解进行切面。
-
DblockApplication: springboot的启动类。
-
DblockApplicationTests: 测试类。
咱们看一下核心代码的实现,参考如下,使用dataJpa非常方便,集成了CrudRepository就可以实现简单的CRUD,非常方便,有兴趣的同学可以自行研究。
实现乐观锁的方式有两种:
-
更新的时候将version字段传过来,然后更新的时候就可以进行version判断,如果version可以匹配上,那么就可以更新(方法:updateCatalogWithVersion)。
-
在实体类上的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);
-
-
}
实现悲观锁的时候也有两种方式:
-
自行写原生SQL,然后写上for update语句。(方法:findCatalogsForUpdate)
-
使用@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(
"-------------"