자바 에서 데이터베이스 에 자주 사용 되 는 두 개의 자물쇠 의 낙관적 인 자물쇠 와 비관 적 인 자물쇠

데이터 베 이 스 를 기록 할 때 자물쇠 가 필요 합 니 다.예 를 들 어 데이터 베 이 스 를 동시에 기록 할 때 데 이 터 를 잃 어 버 리 면 잠 금 메커니즘 이 필요 합 니 다.
데이터 자 물 쇠 는 낙관적 인 자물쇠 와 비관 적 인 자물쇠 로 나 뉘 는데 그들 이 사용 하 는 장면 은 다음 과 같다.
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
데이터베이스 에 자주 사용 되 는 두 개의 자물쇠 에 대한 낙관적 자물쇠 와 비관 적 자물쇠 에 관 한 글 은 여기까지 소개 되 었 습 니 다.더 많은 관련 데이터베이스 자물쇠 낙관적 자물쇠 비관 적 자물쇠 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

좋은 웹페이지 즐겨찾기