티스토리 뷰

728x90

토이 프로젝트 게시판을 구현하면서 CRUD의 Update를 구현할 일이 있었다.
update를 구현하면서 내가 원하는 동작은 테이블에서 하나의 컬럼만(이름) 수정하는 것이었지만, 
update query가 나갈 경우 이름을 제외한 다른 값들이 null처리가 되었다.
이에 따라 공부를 하던 중 더티체킹(변경감지), 벌크연산의 차이에 의해 발생한 부분이었고, 
이 둘의 차이점과 더 나은 코드 퀄리티를 유지하는 방법에 대해 작성하게 되었다.
 
[들어가기에 앞서]

update 할 데이터를 설정하는 경우, builder 패턴과 setter 사용법 중 
나의 경우 @Builder 패턴을 즐겨 사용했지만, 변경감지일 경우만 @Setter를 사용했다.
 
 
다음은 Builder와 Setter에 대한 인프런 서포터즈님의 의견이다.

인프런 서포터즈님이 남겨주신 댓글

728x90

JPA update가 헷갈리는 이유: JPA에는 수정 메서드가 없다.!


JPA의 Dirty Check

JPA의 수정 기능을 구현하던 중 어려움에 빠지는 이유는 JPA에는 수정과 관련된 메서드가 없기 때문이다.
그럼 JPA를 이용해서는 데이터를 수정할 수 없는 것일까?
당연히 JPA를 이용해서 DB 데이터를 수정할 수 있다.
 
JPA를 사용하여 데이터를 수정하려면 다음과 같은 방법을 따를 수 있는데,
Entity를 조회하여 조회된 Entity 데이터를 변경만 하면
데이터 베이스에 자동으로 반영이 되도록 하는 기능을 바로 Dirty Checking이라고 한다.
 

Dirty checking 동작 방법


JPA는 영속성 콘텍스트를 생성하여 DB와 유사하게(가상의 DB처럼) Entity의 Life Cycle을 관리한다.
2022.07.09 - [JPA] 영속성 컨텍스트 | JPA 내부에서 어떻게 동작하는가?
 
JPA는 영속성 콘텍스트에 Entity를 보관할 때 최초의 상태를 저장한다는 것을 기억해야한다.
이것을 스냅샷이라고 하며
영속성 컨텍스트가 Flush(영속성 컨텍스트의 변경 내용을 DB에 반영하는 것)되는 시점에
스냅샷과 Entity의 현재 상태를 비교하여 달라진 Entity를 찾는다.
 
이후 변경된 필드들을 이용하여 쓰기 지연 SQL 저장소에 Update 쿼리를 생성하여 쌓아 둔다.
모든 작업이 끝나고 트랜잭션을 커밋을 하면
'이때' 쓰기 지연 SQL 저장소에 있는 쿼리들을 DB에 전달하여 Update를 진행한다.

 

JPA의 더티체킹과 Querydsl벌크연산 비교


아래 코드와 설명을 통해 JPA와 Querydsl의 업데이트 방법 중 어떤 방법이 이점이 있는지 알아보자.

나의 경우는 JPA와 QueryDsl을 이용하여 update 로직을 구현했다.
아래의 예제를 살펴보며 어떤게 더 편하고, 자기 자신의 로직에 적당한지는 각자 판단하에 사용하길 바란다.
정답은 없다!

 

JPA와 Querydsl 코드 비교

가상의 Team Entity에서 팀 이름과 상세정보를 수정하는 로직을 구현하고 있다.
 
 
JPA를 활용한 업데이트 방법(변경감지) - Service Layer

@RequiredArgsConstructor
@Transactional
public class BoardService {
	private final EntityManager em; 	//엔티티매니저 주입

public Team updateTeamInfo(TeamInfoUpdateDto teamInfoUpdateDto) {
    Team team = em.find(Team.class, teamInfoUpdateDto.getTeamId());

    team.setTeamName(teamInfoUpdateDto.getChangeTeamName());
    team.setDetailIntro(teamInfoUpdateDto.getChangeDetailIntro());

    return team;
	}
}

 
 
Querydsl를 활용한 업데이트 방법(벌크연산) - Repository Layer

public long updateTeamInfo(TeamInfoUpdateDto teamInfoUpdateDto) {
    return queryFactory
            .update(team)
            .set(team.teamName, teamInfoUpdateDto.getTeamName())
            .where(team.id.eq(teamInfoUpdateDto.getTeamId()))
            .execute();
}

 
 
다음 글을 보기전에 JPA와 Query의 차이점에 대해 고민해보자.
아래는 JPA와 Querydsl의 차이점 및 설명에 관한 글이다.
 

JPA 살펴보기


특징

- @Setter로 지정되지 않은 값은 기존의 값을 유지하고, 수정된 값은 변경된다.
즉  다른 값들 null로 변경 x 
 

엔티티 매니저 사용

Team team = em.find(Team.class, teamInfoUpdateDto.getTeamId());

⬆  주입받은 엔티티매니저를 통해  영속성 콘텍스트에서 id값을 통해 Team 엔티티를 찾는다.
 

Setter 이용

    team.setTeamName(teamInfoUpdateDto.getChangeTeamName());
    team.setDetailIntro(teamInfoUpdateDto.getChangeDetailIntro());

⬆ 엔티티매니저로 찾은 Team엔티티의 값에 Dto로 전달받은 값을 set 해준다.
 
 
* 이때 @Setter 사용, 우리 팀의 경우 더티체킹 해줄 때만 @Setter를 사용하기로 약속했다.
*tip: DTO의 변경 의도를 나타내는 네이밍을 만들자, EX: OOOChange
 

JPA 실행 결과

값을 @Setter로 "수정"만 했는데 변경을 감지하여 수정 쿼리가 나간다.
 

정리

1. JPA에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터베이스에 자동으로 반영해 준다.
(변경을 감지해서 DB에 반영한다 해서 변경감지라고 이해하자)
2. 이때 변화가 있다의 기준은 최초 조회 상태
<코드를 보면 별도로 데이터베이스에 save 가 없음>
3. JPA에서는 엔티티를 조회하면 해당 엔티티의 조회 상태 그대로 스냅샷을 생성
4. 그리고 트랜잭션이 끝나는 시점에는 이 스냅샷과 비교해서 다른 점이 있다면 Update Query를 데이터베이스로 전달한다.
2022.06.23 - @Transactional에 대해 / @Transactional(readOnly = false) @Transactional(readOnly = true) 차이
 
당연히 이런 상태 변경 검사의 대상은 영속성 컨텍스트가 관리하는 엔티티에만 적용된다.

 

Querydsl 살펴보기


public long updateTeamInfo(TeamInfoUpdateDto teamInfoUpdateDto) {
    return queryFactory
            .update(team)
            .set(team.teamName, teamInfoUpdateDto.getTeamName())
            .where(team.id.eq(teamInfoUpdateDto.getTeamId()))
            .execute();
}

1. Repository단에서 수정할 team을 update 쿼리에 선언한다.
2. .set을 이용하여 Dto로 전달받은 값을 꺼낸다.
3. .where 절을 이용하여 조건을 건다(선택)
 

만약 즉시 반영을 하려면?

QueryDsl로 해당 쿼리를 날리고 update한다음 select 해보면 수정된 값이 반영되어 나오지 않는다.
이는 영속성 컨텍스트에 값이 남아있기 때문인데
EntityManager를 추가해주고 
em.clear();
em.flush(); 를 해주면 된다.

public OOO OOO OO () {    <- 클래스
	private final EntityManger em;

public long updateTeamInfo(TeamInfoUpdateDto teamInfoUpdateDto) {
    return queryFactory
            .update(team)
            .set(team.teamName, teamInfoUpdateDto.getTeamName())
            .where(team.id.eq(teamInfoUpdateDto.getTeamId()))
            .execute();
            
    em.clear();
    em.flush();
	}
}

 

특징

 

- Querydsl에서 update쿼리를 사용할 경우 리턴 값으로 숫자만 리턴할 수 있다.
- 따라서 조회 쿼리를 한 번더 만들어야한다.
- 지정되지 않은 값은 null로 변경된다.
 

 

JPA와 Querydsl의 차이점


인프런 서포터즈님의 한줄평:
- 먼저 JPA스럽게 사용하는 방법은 더티체킹을 이용하는 방법이라고 생각합니다.

 

JPA(더티체킹) 특징

- 트랜잭션 커밋 시점에 스냅샷 비교를 통한 update 쿼리가 만들어진다.
- 쓰기 지연 SQL 저장소에 있던 다른 쿼리들과 같이 DB로 전송 -> 성능 향상
- DB에 존재하는 모든 값들이 채워지지 않았을 경우 Null 값으로 치환되지 않음 
 
 

Querydsl(벌크연산) 특징 

- Querydsl의 update는 벌크 연산용으로 영속성 컨텍스트에 반영되지 않는 문제점
- 혹은 다량의 데이터를 수정하고 반환해야 할 경우 사용
- DB에 존재하는 모든 값들이 채워지지 않았을 경우 Null 값으로 치환됨 
- 후속 처리가 필요할 수도 있음(추가적인 조회 쿼리)

 


댓글과 좋아요는 포스팅에 큰 힘이 됩니다😊

 
 
 
 
 

728x90
댓글
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday