자바 에서 데이터베이스 에 자주 사용 되 는 두 개의 자물쇠 의 낙관적 인 자물쇠 와 비관 적 인 자물쇠
데이터 자 물 쇠 는 낙관적 인 자물쇠 와 비관 적 인 자물쇠 로 나 뉘 는데 그들 이 사용 하 는 장면 은 다음 과 같다.
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 조건 은 데이터베이스 에 대응 하 는 색인 필드 와 관련 되 어야 줄 잠 금 입 니 다.그렇지 않 으 면 표 잠 금 입 니 다.그러면 실행 속도 가 느 려 집 니 다.
다음은 springboot(springboot 2.1.1+my sql+lombok+op+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>
프로젝트 의 구 조 는 다음 과 같다.프로젝트 의 구조 내용 을 소개 합 니 다.
entity 패키지:실체 패키지.
reposcory 패키지:데이터베이스 reposcory
서비스 패키지:서 비 스 를 제공 하 는 서비스
controller 패키지:컨트롤 러 기록 은 requestMapping 을 작성 하 는 데 사 용 됩 니 다.요청 한 입구 클래스
annotation 패키지:사용자 정의 설명,재 시도 에 사용 합 니 다.
aspect 패키지:사용자 정의 주석 을 자 르 는 데 사용 합 니 다.
DblockApplication:springboot 의 시작 클래스 입 니 다.
Dblock Applications Tests:테스트 클래스.
핵심 코드 의 실현 을 살 펴 보 자.참고 로 다음 과 같다.dataJpa 를 사용 하 는 것 이 매우 편리 하 다.Crud Repository 를 통합 하면 간단 한 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 회 호출,즉 한 상품 을 100 번 조회 할 수 있 고 비관 적 인 자 물 쇠 를 사용 할 수 있 습 니 다.catalog 표 의 데 이 터 는 모두 100 이 며 browse 표 도 100 개의 기록 입 니 다.낙관적 인 자 물 쇠 를 사용 할 때 버 전 번호 의 일치 관계 로 인해 일부 기록 을 잃 어 버 릴 수 있 지만 이 두 표 의 데 이 터 는 대응 할 수 있 습 니 다.낙관적 인 자물쇠 가 실패 하면 Object Optimistic Locking Failure Exception 을 던 집 니 다.이 를 고려 하여 다시 시도 해 보 겠 습 니 다.다음은 절단면 을 만 드 는 데 사용 할 주 해 를 사용자 정의 하 였 습 니 다.
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
@Component
public 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
데이터베이스 에 자주 사용 되 는 두 개의 자물쇠 에 대한 낙관적 자물쇠 와 비관 적 자물쇠 에 관 한 글 은 여기까지 소개 되 었 습 니 다.더 많은 관련 데이터베이스 자물쇠 낙관적 자물쇠 비관 적 자물쇠 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
JPA + QueryDSL 계층형 댓글, 대댓글 구현(2)이번엔 전편에 이어서 계층형 댓글, 대댓글을 다시 리팩토링해볼 예정이다. 이전 게시글에서는 계층형 댓글, 대댓글을 구현은 되었지만 N+1 문제가 있었다. 이번에는 그 N+1 문제를 해결해 볼 것이다. 위의 로직은 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.