Spring Data JPA 사용 중 unique 제약조건이 있는 데이터를 삭제한 뒤 추가하는 경우 duplicate entry 에러가 발생했다.
🫠 나는 데이터를 삭제했는데 왜 에러가 나지?
1. 영속성 컨텍스트의 쓰기 지연 성질로 @Transactional 내부의 쿼리문들은 마지막에 한 번에 실행
2. Hibernate의 쿼리 실행 인터페이스(AbstractFlushingEventListener)의 구현 메서드(performExecutions)는 동작하는 SQL 순서가 정해져 있다.
따라서 delete 코드를 먼저 작성했더라도 insert 쿼리를 먼저 실행한 후에 delete를 실행한다.
엔티티 매니저는 트랜잭션을 커밋하기 직전까지 DB에 쿼리를 날리지 않고 내부 쿼리 저장소에 모아둔 후 트랜잭션을 커밋할 때 모아둔 쿼리를 보내는데 이것을 쓰기지연이라 한다.
hibernate의 레퍼런스를 찾아보니 performExecutions의 실행 순서가 나와있다.
🧐 어떻게 쓰기지연저장소에 저장되고 쿼리 순서 정렬을 하는 거야?
자 우선 ActionQueue 클래스는 쓰기지연저장소이다.
insert, update, delete 관련 필드가 있고 주석에 나와있듯이
Object insertions, updates, and deletions have list semantics because they must happen in the right order so as to respect referential integrity
참조 무결성을 유지하기 위해 올바른 순서로 수행되어야 한다고 나와있다.
구현된 코드를 보면 Insert, Update, Delete 순서대로 LinkedHashMap에 put을 한다.
LinkedHashMap은 입력된 순서대로 Key의 순서가 보장되는 자료구조이기 때문에 쿼리의 순서가 보장이 됐던 것이다.
쓰기지연저장소에서 정렬한 sql문들은 엔티티를 flush 할 때 AbstractFlushingEventListener 클래스의 performExecutions 메서드에서 사용된다.
🤔 왜 항상 Insert를 먼저 실행하도록 정해놓았을까?
공식 문서에 키(Foreign Key) 제약조건이 위배되는 일이 발생하지 않도록, SQL 쿼리(와 두 번째 레벨 캐시 업데이트)를 특정한 순서대로 실행한다고 한다.
Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot be violated:
그래 그럼 외래 키 제약조건은 뭘까? 하고 ChatGPT에 물어보았다.
데이터베이스의 외래 키(Foreign Key) 제약조건은 관계형 데이터베이스에서 사용되는 중요한 개념 중 하나입니다.
외래 키 제약조건은 한 테이블의 열(column)이 다른 테이블의 기본 키(primary key)나 고유키(unique key)와 연결된다는 것을 의미합니다.
외래 키 제약조건을 설정하면, 부모 테이블의 데이터와 일치하지 않는 값을 자식 테이블의 외래 키 열에 저장하는 것을 방지할 수 있습니다. 따라서, 데이터의 무결성을 보장할 수 있습니다.
외래 키 제약조건은 다음과 같은 특징을 가집니다.
자식 테이블의 외래 키 열 값은 부모 테이블의 기본 키나 고유키 값과 일치해야 합니다. 부모 테이블의 데이터를 삭제하거나 수정할 때, 해당 데이터를 참조하는 자식 테이블의 데이터를 삭제하거나 수정하도록 강제할 수 있습니다. 이를 참조 무결성(Referential Integrity)라고 합니다. 외래 키 제약조건은 여러 개의 열을 포함하는 복합 키(composite key)에도 적용될 수 있습니다.
외래 키 제약조건은 데이터의 무결성을 유지하는 데 매우 중요합니다. 따라서, 데이터베이스 설계 시에 외래 키 제약조건을 적절히 설정하는 것이 필수적입니다.
요약하자면 다른 테이블에 실제로 존재하는 데이터로 참조해야 하기 때문에 항상 Insert가 먼저 되도록 구현해 놓은 것 같다.
🤩 insert 실행보다 delete를 먼저 실행하려면 어떻게 해?
flush를 통해 쓰기 지연 SQL 저장소에 쌓여있는 쿼리들을 DB로 날린다.
그렇게 되면 delete 쿼리가 DB에 반영되어 실제 데이터가 삭제된다.
'Back-end > TroubleShooting' 카테고리의 다른 글
[Spring] 로그인 서비스 테스트 AuthenticationManagerBuilder NPE (0) | 2024.05.14 |
---|---|
[Spring Security] AuthenticationException과 JWTException 분리 (0) | 2024.05.10 |
[Spring Security] OncePerRequestFilter.shouldNotFilter 잘 사용하기 (0) | 2024.05.10 |
[Spring Security] SecurityConfig permitAll() 적용 안 되는 이유 (0) | 2024.05.10 |
[Spring Boot] JPA 동시성 이슈, 낙관적 락(Optimistic Lock)을 이용한 해결 (0) | 2023.03.27 |