Resolva o problema de simultaneidade, as duas travas comumente usadas no banco de dados entram sem perdas

É necessário um bloqueio ao gravar no banco de dados. Por exemplo, quando os dados são perdidos ao gravar no banco de dados ao mesmo tempo, um mecanismo de bloqueio é necessário.

Os bloqueios de dados são divididos em bloqueios otimistas e bloqueios pessimistas

Os cenários que eles usam são os seguintes:

  • O bloqueio otimista é adequado para o cenário de escrever menos e ler mais. Como esse bloqueio otimista é equivalente ao CAS de JAVA, quando vários dados vêm ao mesmo tempo, você pode retornar imediatamente sem esperar.

  • O bloqueio pessimista é adequado para o cenário de mais gravações e menos leituras. Esta situação também é equivalente a JAVA's synchronized, reentrantLock, etc. Quando uma grande quantidade de dados chega, apenas uma parte dos dados pode ser gravada e outros dados precisam esperar. Após a conclusão da execução, os próximos dados podem continuar.

Eles são diferentes na maneira como o fazem.

O bloqueio otimista usa o método do número da versão, ou seja, se o número da versão atual corresponder aos dados podem ser gravados, se o número da versão atual for considerado inconsistente, a atualização não terá sucesso, por exemplo

update table set column = value

where version=${version} and otherKey = ${otherKey}

O mecanismo de implementação de bloqueio pessimista geralmente é usado para atualização ao executar instruções de atualização, como

update table set column='value' for update

Nesse caso, a condição where deve envolver o campo de índice correspondente ao banco de dados, para que seja um bloqueio em nível de linha, caso contrário, será um bloqueio de tabela, o que tornará a velocidade de execução mais lenta.

Em seguida, obterei um projeto de boot de primavera (springboot 2.1.1 + mysql + [lombok] e, então, gradualmente realizarei o bloqueio otimista e o bloqueio pessimista.

Suponha que haja uma cena, haja uma tabela de catálogo de produtos do catálogo e, em seguida, haja uma tabela de navegação de navegação, se um produto é navegado, é necessário registrar quem navegou pelo usuário e registrar o número total de visitas.

A estrutura da tabela é muito simples:

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;

As dependências de POM.XML são as seguintes:

<?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>

A estrutura do projeto é a seguinte:

Apresente o conteúdo da estrutura do projeto:

  • Pacote de entidades: pacote de classes de entidades.

  • pacote de repositório: repositório de banco de dados

  • Pacote de serviços: prestação de serviços

  • Pacote do controlador: a gravação do controlador é usada para escrever o requestMapping. Aula de entrada do pedido relacionado

  • pacote de anotações: anotações personalizadas para tentar novamente.

  • pacote de aspectos: usado para aspectos de anotações personalizadas.

  • DblockApplication: A classe de inicialização do springboot.

  • DblockApplicationTests: classe de teste.

Vamos dar uma olhada na implementação do código principal. A referência é a seguinte. É muito conveniente usar dataJpa. CRUD simples pode ser realizado integrando CrudRepository. É muito conveniente e os alunos interessados ​​podem estudar por conta própria.

Existem duas maneiras de implementar o bloqueio otimista:

  1. Ao atualizar, passe o campo de versão, e ao atualizar, você pode julgar a versão.Se a versão corresponder, então você pode atualizar (método: updateCatalogWithVersion).

  2. Ao adicionar versão ao campo de versão na classe de entidade, você pode corresponder e atualizar de acordo com a versão sem escrever instruções SQL. Não é muito simples?

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);

}

Existem também duas maneiras de obter bloqueio pessimista:

  1. Escreva o SQL nativo sozinho e, em seguida, escreva para a instrução de atualização. (Método: findCatalogsForUpdate)

  2. Use a anotação @Lock e defina o valor para LockModeType.PESSIMISTIC_WRITE para representar o bloqueio em nível de linha.

Também existe uma classe de teste que escrevi para facilitar seus testes:

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();

       }

   }

}

Chamado 100 vezes, ou seja, um produto pode ser navegado cem vezes, usando o bloqueio pessimista, os dados da tabela de catálogo são 100 e a tabela de navegação também é de 100 registros. Quando o bloqueio otimista é usado, alguns registros serão perdidos devido ao relacionamento de correspondência dos números de versão, mas os dados nessas duas tabelas podem ser correspondentes.

Após a falha do bloqueio otimista, ObjectOptimisticLockingFailureException será lançado, então vamos considerar uma nova tentativa para esta parte. Abaixo, personalizarei uma anotação para o aspecto.

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 {

}

Para as anotações, consulte o código a seguir. Defino o número máximo de tentativas como 5 e paro de tentar novamente depois de mais de 5 vezes.

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;

   }

}

A ideia geral é esta.

Acho que você gosta

Origin blog.csdn.net/doubututou/article/details/109209474
Recomendado
Clasificación