jpa의 기본적인 트랜잭션 락 전략은 낙관적 락 레벨인 read-commited이다. 이것은 commit한 데이터만 보여주게 된다. 그러므로 테스트 내에서(하나의 transactional)안에서 여러가지 쿼리를 날렸을때 어떻게 작동하는지 살펴보자.
다음은 연관관계에 놓여진 Study ,StudyMember, Member 도메인이 있다.
//1.스터디 도메인
@Entity
public class Study implements Serializable {
@Id
Long id;
@OneToMany(mappedBy = "study", cascade = CascadeType.ALL)
private Set<StudyMember> members = new HashSet<StudyMember>();
}
//2. StudyMember도메인
@Entity
public class StudyMember {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="study_id")
private Study study;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="member_id")
private Account member;
}
//3.Member도메인
public class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL
private Set<StudyMember> studies = new HashSet<>();
}
이때, Study 1개에 Member5명이 주입이 되어있다고 가정하자.
그렇다면 StudyMember의 record는 총5개이다.
그렇다면 Study를 제거하게 된다면 어떻게 될까?
관련된 StudyMember5개가 제거 될것이다. 그리고 다시 조회를 한다면 어떻게 될까?
@Test
@Transactional
void 스터디_삭제(){
//given 스터디 5개준비
study.addMember(member1);
study.addMember(member2);
study.addMember(member3);
study.addMember(member4);
study.addMember(member5);
studyRepository.save(study);
//when study도메인을 삭제한다면??
//beforestudy는 5개의 레코드가 존재한다.
List<StudyMember> beforeStudy=studyMemberRepository.findAll();
//삭제
studyRepository.delete(study);
//then 이때 studyMemberRepository에서 findAll()하게 된다면 어떻게 될까?
List<StudyMember> studyMembers=studyMemberRepository.findAll();
System.out.println(studyMembers);
}
영속성 컨텍스트의 경우 트랜잭션 안에 있는 경우, select의 경우는 바로 쿼리를 처리한다. 하지만 delete,update의 경우 인메모리에 dirty표시를 한후 commit시 한번에 처리하게 된다. 이를 염두해두고 다음과정을 보자.
1. beforestudy의 경우 조회후 5개의 레코드를 참조하고 있다.
2. study를 delete하는 순간 dirty 리스트에 삭제 목록으로 체크가 된다.(하지만 커밋은 되지 않았다.)
3. studyMemberRepsitory에서 findAll()하는 순간 데이터베이스에서 study_Member 테이블의 전체 클러스터링 인덱스(Primary key)를 조회한다. 그후 테이블의 모든 키가 영속성컨텍스트에 포함되어있는지 체크한다. 이때 영속성컨텍스트에는 지워진 키를 발견하고 이것에 대해서 쿼리를 날릴때 오류가 발생한다.( 제약성 위반 오류)
(혹시 내용이 잘못됐다면 저에게 알려주세요)
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FKXU4JDS4AB0MFYRVDXSU60IUT: PUBLIC.STUDY_MEMBER FOREIGN KEY(STUDY_ID) REFERENCES PUBLIC.STUDY(ID) (31)"; SQL statement:
delete from study where id=? [23503-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
즉 이러한 오류를 범하지 않을려면 delete와 update와 같은 쿼리들이 insert,select와 같은 쿼리들이 하나의 트랜잭션에서 일어나지 않게끔 설계하는것이 중요하다.