티스토리 뷰
service 계층에서 @Transactional(readOnly = true)
임에도 불구하고 insert 쿼리가 나가는 것을 보게 되었다. @Transactional(readOnly = true)
의 정의대로라면 DB에 변경을 가하는 CUD 작업 즉 insert, update, delete는 불가능하다. 이번 글에서는 왜 readOnly 트랜잭션에서 insert 쿼리가 나갈 수 밖에 없었는지 알아보고자 한다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "team_id")
private Long id;
@Column(nullable = false)
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
먼저 답을 말해보자면 엔티티의 키 매핑 전략이 IDENTITY로 설정되어 있기 때문이다. 그렇다면 왜 해당 전략이 이러한 상황을 만들게 되었는지 자세히 알아보기에 앞서, 영속성 컨텍스트에서의 엔티티 관리 방법에 대해 간략히 정리해보자.
영속성 컨텍스트에서는 엔티티를 Map 형태로 관리한다. 즉 key에는 @Id
값이, value에는 엔티티 인스턴스가 대응된다. 따라서 영속성 컨텍스트에서 엔티티를 영속화하기 위해서는 DB의 PK 필드에 대응되는 값인 id가 필요하다.
(참고로 영속화된 엔티티를 id가 아닌 다른 필드로 다시 조회하면 쿼리가 2번 나가는 것을 알 수 있다. 영속성 컨텍스트에서 조회하기 위해서는 Map의 key인 id값이 필요하다.)
Team team = teamRepository.findById(teamId).orElseThrow(RuntimeException::new); //쿼리 나감
teamRepository.findByName(team.getName()).orElseThrow(RuntimeException::new); //쿼리 나감
//---------------------------------------------------------------------------
Team team = teamRepository.findById(teamId).orElseThrow(RuntimeException::new); //쿼리 나감
teamRepository.findById(teamId).orElseThrow(RuntimeException::new); //쿼리 나가지 않음(영속성 컨텍스트에서 조회)
위 예제의 Team
엔티티의 키 매핑 전략으로는 IDENTITY를 사용하였는데 IDENTITY의 동작 방식은 다음과 같다.
- 기본키(PK) 생성을 DB에 위임 하는 것으로 MySQL의 AUTO_INCREMENT와 비슷하다.
- JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL을 실행하나, AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에야 ID 값을 알 수 있다. 따라서 IDENTITY 전략은em.persist()
시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회한다.
여기서 teamRepository.save(team)
의 인자로 전달된 team
객체의 id
값은 null이고 위와 같은 이유로 읽기 전용 트랜잭션임에도 불구하고 해당 team
엔티티를 영속화하기 위해(id
값을 알기 위해) save
메소드 호출 시 바로 insert 쿼리가 실행되는 것이다.
그렇다면 IDENTITY 전략이 아닌 SEQUENCE 전략을 사용한다면 어떻게 될까? (IDENTITY와 SEQUENCE 전략에 대한 자세한 내용은 링크 참고)
간단하게 IDENTITY와 SEQUENCE를 비교하자면 IDENTITY는 엔티티를 영속화할 때마다 매번 DB에게 엔티티의 id가 무엇인지 물어보는 것 (따라서 insert 쿼리가 나감), SEQUENCE는 예를 들어 id 값 1~50까지는 DB에게 물어보지 않고 영속성 컨텍스트가 알아서 id를 관리하다가 id 값을 50까지 다 썼을 때 그때서야 DB에게 물어보는 것 (insert 쿼리가 나가는 것이 아닌 유효한 id 범위만을 다시 받아옴) 이라고 할 수 있겠다.
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SequenceGenerator(name = "TEAM_SEQ_GENERATOR", sequenceName = "TEAM_SEQ")
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEAM_SEQ_GENERATOR")
@Column(name = "team_id")
private Long id;
//이하 동일
}
Team
엔티티에서 위와 같이 Sequence 전략으로 변경한 뒤 아래 메소드를 실행하면 insert 쿼리는 save
메소드 호출 시점이 아닌 트랜잭션이 커밋되기 직전에 실행된다.
@Transactional
public void saveTeam(String name) {
Team team = new Team(name);
team = teamRepository.save(team);
System.out.println("team = " + team);
}
team = Team(id=3, name=teamC, members=[])
Hibernate:
insert
into
team
(name, team_id)
values
(?, ?)
위 실행 결과를 통해 insert 쿼리 실행 전임에도 Team
엔티티의 id
값이 할당되어 있는 것을 알 수 있다.
또한 Sequence 전략을 택하면 @Transactional(readOnly = true)
읽기 전용 트랜잭션 내에서 insert 쿼리가 실행되지 않게 된다. 즉 DB에 insert 쿼리를 날리지 않고도 영속성 컨텍스트는 엔티티의 id에 값을 할당할 수 있고, 읽기 전용 트랜잭션에서는 커밋 직전 flush하지 않으므로 insert 쿼리가 나가지 않는 것이다.
'트랜잭션을 commit 하기 전 flush 하지 않아 의도치 않게 데이터가 변경되는 것을 막아준다' 라는 @Transactional(readOnly = true)
의 장점이 엔티티 키 매핑 전략이 IDENTITY일 때에는 온전히 발휘되지 못한다는 것을 염두에 두면 될 것 같다.
끝.
'Spring > Spring' 카테고리의 다른 글
[Querydsl] Spring Boot 3.x with Gradle 설정 (1) | 2023.10.20 |
---|---|
[개념] FrontController 패턴 (with 스프링) (0) | 2023.09.05 |
Web Server vs WAS vs Web Container(Servlet Container) (0) | 2023.09.02 |
[개념] Service 계층 메소드에서 @Transactional, @Transactional(readOnly = true), 트랜잭션 어노테이션 없음 간 동작방식의 차이 (0) | 2023.01.17 |
[개념] Transaction, DB connection, OSIV 간의 관계 (1) | 2023.01.09 |
- Total
- Today
- Yesterday
- 모두의 리눅스
- Java
- Front Controller
- 전략 패턴
- SSE
- servlet filter
- Spring Security
- Assertions
- ParameterizedTest
- spring aop
- 단위 테스트
- C++
- 디자인 패턴
- vscode
- spring boot
- Gitflow
- JPA
- rest api
- 서블릿 컨테이너
- facade 패턴
- junit5
- spring
- FrontController
- Git
- QueryDSL
- 템플릿 콜백 패턴
- github
- mockito
- Linux
- Transaction
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |