
변경감지란(Dirty Checking)?
트랜잭션 Commit시에 영속화 되있는 Entity에서 가지고 있던 최초의 정보와 바뀐 Entity정보를 비교해서 바뀐 부분을 자동으로 UPDATE 해준다고 생각하면 됩니다 !
처음에는 당연히 이해가 잘 안갈겁니다...코드로 직접 보는게 BEST죠?
전체 코드 보기 : https://github.com/WOOOOJI/jpa_test_code
GitHub - WOOOOJI/jpa_test_code: 기본적인 JPA 사용법
기본적인 JPA 사용법. Contribute to WOOOOJI/jpa_test_code development by creating an account on GitHub.
github.com
1. Dirty Checking (변경 감지)
Dirty = 변경 사항
Checking = 확인하다
@Service
public class DirtyChecking {
@Autowired
UserRepository userRepository;
@Transactional // 트랙잭션으로 가둬줘야 변경감지가 가능하다.
public void updateUser(Long id, String name){
// DB에서 id값을 기준으로 데이터를 찾는다 (영속화)
Optional<User> user = userRepository.findById(id);
// 만약 해당 값이 존재한다면 전달받은 name으로 set을 해준다.
user.ifPresent(value -> value.setName(name));
}
- @Transactional
- 비지니스 로직에서 쪼개질 수 없는 하나의 작업 단위 입니다.
- 핵심은 트랜잭션이 성공적이면 결과가 항상 영속적으로 보관 되며, 일관성 있는 DB상태를 유지합니다.
- 사용하는 이유는 JPA가 트랜잭션안에서 커밋된 시점에 flush를 할때, Entity의 변경된 점을 감지하고 UPDATE를 해주기 때문이다.
@Test
void jpaDirtyChecking(){
dirtyChecking.updateUser(2L, "바뀌는 값");
}
TMI. 처음에 Test파일에서 모든 코드를 다 썼다...@Transactional에서 에러는 안나는데 결과를 도출하지 않는겨......왜그런가 생각을 해봤는데, "테스트 메소드에 @Transactional을 사용하면 뭔가 달라지나?" 그렇다... 정답이였다....생성된 데이터를 rollback을 하는 기능으로 적용이 된거였다.

위 코드만 보면. "??? save()를 안했는데 이게 자동으로 저장이 된다고???"
정말 간편하죠 ! 하지만 이렇게 사용하기 위해선 주의사항이 있습니다.
영속성이 있어야 한다. 무조건.
Dirty Checking은 영속 상태를 대상으로 합니다. 고로 비영속, 준영속 상태는 Dirty Checking의 대상이 아닙니다.
영속성을 잘 모르신다면...JPA에서 P에 대한 글자를 모르는것과 같습니다.
영속성(Persistence) 이란. 프로그램이 종료되도 사라지지 않는 데이터의 특성을 의미합니다. (DB처럼)
- 비영속
- 말 그대로 영속성 상태가 아닙니다. (프로그램 종료시 데이터가 날라간다....)
- Entity객체가 아닌 임의로 생성한 객체들이 비영속성을 가지고 있다고 말할 수 있습니다.
- 준영속 (Detached)
- 영속 상태였던것 ! 현재는 연결이 끊긴 상태를 의미합니다. (첫 실행시 영속성을 가지지만 실행이 완료되면 준영속성을 가지게 된다고 표현한다)
- 준영속 상태 역시 Dirty Checking이 안되기에 영속성이 있는 객체를 불러와 영속 상태로 변경해줘야 합니다.
@Transactional
public void updateUser(Long id, String name){
// DB로 부터 데이터를 가져온다 (영속성)
Optional<User> user = userRepository.findById(id);
user.ifPresent(value -> value.setName(name));
}
위 코드와 같이 find() 메소드를 사용하여 영속 상태의 객체를 가져온 다음 해당 객체에 데이터값을 변경 시키면 됩니다. 그래야지 영속 상태의 객체에서 변화가 일어났음을 Dirty Checking이 탐지를 하고 UPDATE를 해줍니다. 이 때 변경
변경되지 않은 컬럼도 UPDATE를 한다.
별도의 설정을 하지 않으면 하나라도 변경사항이 있다면 모든 컬럼을 UPDATE합니다. 그렇다면 변경사항이 있는 컬럼만 바꾸고 싶다면?
@Getter
@NoArgsConstructor( access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Entity
@DynamicUpdate // 변경사항이 있는 것만 !
@Table(name = "user")
public class User {
Entity에 @DynamicUpdate 어노테이션을 사용하면 끝.
하지만 성능상 오버헤드 문제가 발생할 수도 있습니다 (캐싱을 하지 않고 그때마다 변경된 컬럼에 맞는 쿼리를 생성하기에)
그래서 테이블에 컬럼이 많거나, 이 컬럼들 중에서 일부 컬럼만 자주 바뀐다면 사용하는걸 고려해보는게 좋을거 같다고 합니다 :)
2. Merge (병합)
Save() 쓰면 그게 Merge 입니다. 끝.
Save() 메소드를 실행 시킬 때 해당 객체가 만약 DB에 존재하는 ID를 가지고 있는데 변경된 값이 있다면 Merge를 시도합니다.
당연히 null값으로 초기화가 안되있는 필드가 있을경우에 null로 merge가 되기에 위험합니다 !.......
@Test
void jpaMerge(){
User user = User.builder().id(3L).name("병합하기").build();
userRepository.save(user);
}
사실 Merge 코드를 직접 짤수도 있습니다. ex) EntityManager 인터페이스를 EntityFactory~~로 실체화 하여 .merge() 메소드를 활용 하는 방법도 있슴.... 하지만 사실 귀찮게 누가 그렇게 짜나 !!!
영속성에 대한 개념이 없다면 해당 글은 도움이 많이 안될겁니다.
실제로 어떻게 작동을 하는지. 작동 조건은 뭐가 있는지...
영속성이란 개념을 알아야 사용 하면서 에러가 났을때 대처하실수 있을겁니다.
다음은 직접 쿼리문을 작성할 수 있는 @Query 어노테이션에 대해 알아보겠습니다 !
궁금하신점이나 잘못 기재된 점에 대한 피드백은 언제나 환영입니다 :)
'Spring & Spring Boot' 카테고리의 다른 글
[Spring] @Query 사용법 및 예시코드 (JPA에서 쿼리를 직접쓰는 방법 !) (0) | 2023.03.16 |
---|---|
[Spring] JPA의 기본 CRUD를 사용해보자 ! (0) | 2023.03.14 |
[Spring] Mybatis와 JPA의 차이점 feat. ORM이란? (0) | 2023.03.12 |
[Spring] @Autowired, @Inject, @Resource 차이점 정리 (1) | 2023.03.06 |
Spring JPA? Java Presistence API 가 뭘까요 ! (0) | 2023.02.01 |

변경감지란(Dirty Checking)?
트랜잭션 Commit시에 영속화 되있는 Entity에서 가지고 있던 최초의 정보와 바뀐 Entity정보를 비교해서 바뀐 부분을 자동으로 UPDATE 해준다고 생각하면 됩니다 !
처음에는 당연히 이해가 잘 안갈겁니다...코드로 직접 보는게 BEST죠?
전체 코드 보기 : https://github.com/WOOOOJI/jpa_test_code
GitHub - WOOOOJI/jpa_test_code: 기본적인 JPA 사용법
기본적인 JPA 사용법. Contribute to WOOOOJI/jpa_test_code development by creating an account on GitHub.
github.com
1. Dirty Checking (변경 감지)
Dirty = 변경 사항
Checking = 확인하다
@Service
public class DirtyChecking {
@Autowired
UserRepository userRepository;
@Transactional // 트랙잭션으로 가둬줘야 변경감지가 가능하다.
public void updateUser(Long id, String name){
// DB에서 id값을 기준으로 데이터를 찾는다 (영속화)
Optional<User> user = userRepository.findById(id);
// 만약 해당 값이 존재한다면 전달받은 name으로 set을 해준다.
user.ifPresent(value -> value.setName(name));
}
- @Transactional
- 비지니스 로직에서 쪼개질 수 없는 하나의 작업 단위 입니다.
- 핵심은 트랜잭션이 성공적이면 결과가 항상 영속적으로 보관 되며, 일관성 있는 DB상태를 유지합니다.
- 사용하는 이유는 JPA가 트랜잭션안에서 커밋된 시점에 flush를 할때, Entity의 변경된 점을 감지하고 UPDATE를 해주기 때문이다.
@Test
void jpaDirtyChecking(){
dirtyChecking.updateUser(2L, "바뀌는 값");
}
TMI. 처음에 Test파일에서 모든 코드를 다 썼다...@Transactional에서 에러는 안나는데 결과를 도출하지 않는겨......왜그런가 생각을 해봤는데, "테스트 메소드에 @Transactional을 사용하면 뭔가 달라지나?" 그렇다... 정답이였다....생성된 데이터를 rollback을 하는 기능으로 적용이 된거였다.

위 코드만 보면. "??? save()를 안했는데 이게 자동으로 저장이 된다고???"
정말 간편하죠 ! 하지만 이렇게 사용하기 위해선 주의사항이 있습니다.
영속성이 있어야 한다. 무조건.
Dirty Checking은 영속 상태를 대상으로 합니다. 고로 비영속, 준영속 상태는 Dirty Checking의 대상이 아닙니다.
영속성을 잘 모르신다면...JPA에서 P에 대한 글자를 모르는것과 같습니다.
영속성(Persistence) 이란. 프로그램이 종료되도 사라지지 않는 데이터의 특성을 의미합니다. (DB처럼)
- 비영속
- 말 그대로 영속성 상태가 아닙니다. (프로그램 종료시 데이터가 날라간다....)
- Entity객체가 아닌 임의로 생성한 객체들이 비영속성을 가지고 있다고 말할 수 있습니다.
- 준영속 (Detached)
- 영속 상태였던것 ! 현재는 연결이 끊긴 상태를 의미합니다. (첫 실행시 영속성을 가지지만 실행이 완료되면 준영속성을 가지게 된다고 표현한다)
- 준영속 상태 역시 Dirty Checking이 안되기에 영속성이 있는 객체를 불러와 영속 상태로 변경해줘야 합니다.
@Transactional
public void updateUser(Long id, String name){
// DB로 부터 데이터를 가져온다 (영속성)
Optional<User> user = userRepository.findById(id);
user.ifPresent(value -> value.setName(name));
}
위 코드와 같이 find() 메소드를 사용하여 영속 상태의 객체를 가져온 다음 해당 객체에 데이터값을 변경 시키면 됩니다. 그래야지 영속 상태의 객체에서 변화가 일어났음을 Dirty Checking이 탐지를 하고 UPDATE를 해줍니다. 이 때 변경
변경되지 않은 컬럼도 UPDATE를 한다.
별도의 설정을 하지 않으면 하나라도 변경사항이 있다면 모든 컬럼을 UPDATE합니다. 그렇다면 변경사항이 있는 컬럼만 바꾸고 싶다면?
@Getter
@NoArgsConstructor( access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Entity
@DynamicUpdate // 변경사항이 있는 것만 !
@Table(name = "user")
public class User {
Entity에 @DynamicUpdate 어노테이션을 사용하면 끝.
하지만 성능상 오버헤드 문제가 발생할 수도 있습니다 (캐싱을 하지 않고 그때마다 변경된 컬럼에 맞는 쿼리를 생성하기에)
그래서 테이블에 컬럼이 많거나, 이 컬럼들 중에서 일부 컬럼만 자주 바뀐다면 사용하는걸 고려해보는게 좋을거 같다고 합니다 :)
2. Merge (병합)
Save() 쓰면 그게 Merge 입니다. 끝.
Save() 메소드를 실행 시킬 때 해당 객체가 만약 DB에 존재하는 ID를 가지고 있는데 변경된 값이 있다면 Merge를 시도합니다.
당연히 null값으로 초기화가 안되있는 필드가 있을경우에 null로 merge가 되기에 위험합니다 !.......
@Test
void jpaMerge(){
User user = User.builder().id(3L).name("병합하기").build();
userRepository.save(user);
}
사실 Merge 코드를 직접 짤수도 있습니다. ex) EntityManager 인터페이스를 EntityFactory~~로 실체화 하여 .merge() 메소드를 활용 하는 방법도 있슴.... 하지만 사실 귀찮게 누가 그렇게 짜나 !!!
영속성에 대한 개념이 없다면 해당 글은 도움이 많이 안될겁니다.
실제로 어떻게 작동을 하는지. 작동 조건은 뭐가 있는지...
영속성이란 개념을 알아야 사용 하면서 에러가 났을때 대처하실수 있을겁니다.
다음은 직접 쿼리문을 작성할 수 있는 @Query 어노테이션에 대해 알아보겠습니다 !
궁금하신점이나 잘못 기재된 점에 대한 피드백은 언제나 환영입니다 :)
'Spring & Spring Boot' 카테고리의 다른 글
[Spring] @Query 사용법 및 예시코드 (JPA에서 쿼리를 직접쓰는 방법 !) (0) | 2023.03.16 |
---|---|
[Spring] JPA의 기본 CRUD를 사용해보자 ! (0) | 2023.03.14 |
[Spring] Mybatis와 JPA의 차이점 feat. ORM이란? (0) | 2023.03.12 |
[Spring] @Autowired, @Inject, @Resource 차이점 정리 (1) | 2023.03.06 |
Spring JPA? Java Presistence API 가 뭘까요 ! (0) | 2023.02.01 |