@Version JPA 및 Hibernate로 데이터베이스 동시성 방지

17499 단어 javajpahibernate
대기업의 프로젝트를 진행할 때 가끔 누군가 "두 명의 사용자가 데이터베이스에서 동일한 레코드를 업데이트하면 어떻게 됩니까?"라고 묻습니다.

새로운 개발자에게는 혼란스러울 것 같지만 피하는 것은 매우 쉽습니다.

JPA(Java Persistence API)에는 데이터베이스 레코드가 업데이트된 횟수를 확인하는 데 도움이 되는 주석이 있습니다.

이 간단한 테이블과 엔터티를 살펴보겠습니다.

create table device
(
    id      integer not null constraint device_pk primary key,
    serial  integer,
    name    varchar(255),
    version integer
)



그리고 엔티티

package com.vominh.example.entity;

import javax.persistence.*;

@Entity
@Table(name = "device")
public class DeviceWithVersionEntity {
    @Id
    @Column(name = "id")
    private Integer id;
    @Column(name = "serial")
    private Integer serial;
    @Column(name = "name")
    private String name;
    @Version
    @Column(name = "version")
    private int version;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getSerial() {
        return serial;
    }
    public void setSerial(Integer serial) {
        this.serial = serial;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }
}


이 부분은 "누군가 업데이트 작업을 수행하면 이 필드가 1씩 증가합니다"와 같이 이해할 수 있습니다.

@Version
@Column(name = "version")
private int version;


메인 클래스

package com.vominh.example;

import com.vominh.example.entity.DeviceWithVersionEntity;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class ConcurrencyControl {
    private static final SessionFactory sessionFactory;
    private static final ServiceRegistry serviceRegistry;

    static {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");

        // Since Hibernate Version 4.x, ServiceRegistry Is Being Used
        serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);
    }

    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Query deleteAllDevice = session.createQuery("delete from DeviceWithVersionEntity");

        try {
            session.beginTransaction();
            deleteAllDevice.executeUpdate();

            DeviceWithVersionEntity device = new DeviceWithVersionEntity();
            device.setId(1);
            device.setSerial(8888);
            device.setName("Dell xps 99");

            session.save(device);

            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.getTransaction().rollback();
        } finally {
            session.close();
        }

        // open 50 session in 50 thread to update one record
        for (int i = 0; i < 50; i++) {
            CompletableFuture.runAsync(() -> {
                var s = sessionFactory.openSession();
                try {
                    s.beginTransaction();

                    DeviceWithVersionEntity d = (DeviceWithVersionEntity) s.load(DeviceWithVersionEntity.class, 1);
                    d.setName((new Random()).nextInt(500) + "");
                    s.save(d);

                    s.getTransaction().commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    s.getTransaction().rollback();
                } finally {
                    s.close();
                }
            });
        }
    }
}



  • 첫 번째 부분은 레코드를 데이터베이스에 저장합니다
  • .
  • 두 번째 부분에서 50개의 스레드 및 최대 절전 모드 세션을 생성한 다음 삽입된 레코드 업데이트를 시도합니다
  • .

    실행 결과는 많은 org.hibernate.StaleObjectStateException을 발생시킵니다.


    예외 메시지는 이미 어떤 일이 발생하는지 설명하고 있지만 언제 어떻게 발생합니까?

    Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)



    Entity(숫자)의 필드에 추가하면 생성/업데이트 작업이 실행될 때 Hibernate는 필드의 값을 설정/증가합니다.

    UPDATE device set name = 'new name', version = version + 1
    


    다음 UPDATE가 발생하면 Hibernate는 WHERE 절에 조건을 추가하여 버전이 일치하는지 확인합니다.

    UPDATE device SET name = 'new name', version = version + 1
    WHERE id = ? AND version = **CURRENT_VERSION**
    


    다른 세션이 이미 레코드를 업데이트하고 버전이 증가한 경우 WHERE 절이 일치하지 않고 예외가 발생합니다.

    이 방법은 낙관적 잠금이라고도 하며 JPA 및 Hibernate(무거운 논리를 처리함)로 구현하기 쉽습니다.

    소스 코드: https://github.com/docvominh/java-example/tree/master/hibernate-jpa
    나는 maven, hibernate 4.3 및 postgres SQL을 사용합니다.

    읽어주셔서 감사합니다!

    좋은 웹페이지 즐겨찾기