DynamicUpdate가 외않됂데?

recordsbeat·2020년 4월 29일
10
post-thumbnail

CRUD중 delete를 제외하고 본격적으로 작업에 들어갔다.

update를 구현할 때 일반적인 JPA환경에서는 다음과 같이 진행된다.

  1. id를 통해 영속객체 로드
  2. 객체 필드 값 변경
  3. 트랜잭션 진행

나 또한 이 절차를 따랐고 다음과 같은 코드가 나오게 되었다.


@RequiredArgsConstructor
@Service
public class UserService {
    private final UserRepository userRepository;
	...

    @Transactional
    public void update(Long id, UserUpdateRequestDto request){
        User user =  findUserById(id);
        user.updateInfo(request);
    }

    private User findUserById(Long id){
        return userRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("user가 존재하지 않습니다. id : " + id));
    }


}
@NoArgsConstructor
@Getter
@DynamicUpdate
@Entity
@Table(name = "TB_USER")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_idx")
    private Long id;

    ...

    public void updateInfo(UserUpdateRequestDto requestDto) {
        if (ObjectUtils.isEmpty(requestDto))
            throw new IllegalArgumentException("요청 파라미터가 NULL입니다.");
        
        this.name = requestDto.getName();
        this.language = requestDto.getLanguage();
        this.phoneNumber = requestDto.getPhoneNumber());
        this.nation = requestDto.getNation();
    }

}


    

JPA는 기본적으로 update를 진행할 당시에 모든 필드를 update한다.
그렇기 때문에

Null 필드를 update쿼리에서 제외하고자 하면 @DynamicUpdate 어노테이션을 사용

해야 한다.

???

안됐다.. 왜일까

영속성에 문제가 있는 것일까 다시 한 번 찾아봤다.
그리고 아래 링크..
https://singun.github.io/2017/01/25/why-does-not-work-preupdate-annotation/

상황은 조금 달랐지만 중간정도 내용이 도움이 되었다.

"JPA에서 제공하는 변경 감지란 것에 대해서 조금 더 알아보았습니다. JPA는 update()라는 메소드를 제공하고 있지 않습니다.
entity에 setter 메소드를 통해서 값을 설정해주기만 하면 트랜잭션이 커밋되는 시점에 entity의 최종 상태(snapshot)를 데이터베이스에 자동으로 반영해줍니다.
이를 변경 감지라고 합니다.
트랜잭션이 커밋되면 entity manager 내부에서 flush() 메소드가 호출됩니다.
이때 1차 캐시에 저장되어 있는 앤티티와 스냅샷을 비교합니다.
변경 내용이 있는 경우 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 해당 내용을 쓰게 됩니다.
그리고 쓰기 지연 저장소의 SQL을 데이터베이스에 커밋하게 됩니다."

나 같은 경우 updateDto 객체를 받아 entity 값을 변경하도록 하였다.
비록 User Entity의 필드가 Null로 변경 되었어도 @DynamicUpdate가 무효 처리를 해줄 것으로 생각했지만.

영속성에 따라 값이 변경 되었음을 감지 했기에 update 대상으로 취급된 것이다.

ex)
필드 a,b,c 중 c만 값이 바뀔 경우 update c ...
그러나
a,b,c 중 a=null, b=null , c="valid value" 일 경우
a,b 가 null 일지라도
update a,b,c

이 때문에 updateDto의 유효성을 검사해야하는 일이 생겼다.
(클라이언트에서 유효성 체크를 한 후 통신을 하면 되겠지만 안전장치 정도로 생각..)

@NoArgsConstructor
@Getter
@DynamicUpdate
@Entity
@Table(name = "TB_USER")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_idx")
    private Long id;

   ...

    public void updateInfo(UserUpdateRequestDto requestDto) {
        if (ObjectUtils.isEmpty(requestDto.getName()))
            throw new IllegalArgumentException("요청 파라미터가 NULL입니다.");

        if (requestDto.getName().isValidated())
            this.name = requestDto.getName();

        if(ObjectUtils.isNotEmpty(requestDto.getLanguage()))
            this.language = requestDto.getLanguage();

        if (requestDto.getPhoneNumber().isValidated())
            this.phoneNumber = requestDto.getPhoneNumber();

        if(ObjectUtils.isNotEmpty(requestDto.getNation()))
            this.nation = requestDto.getNation();
    }

}

@EqualsAndHashCode
@NoArgsConstructor
@Embeddable
@Data
public class Name {
    private String firstName;
    private String lastName;

    @Builder
    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFullName(){
        return firstName+" " + lastName;
    }

    public boolean isValid(){
        return StringUtils.isNoneBlank(firstName,lastName);
    }
}

@NoArgsConstructor
@EqualsAndHashCode
@Data
@Embeddable
public class PhoneNumber {
    private String phoneNumber;

    @Builder
    public PhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public boolean isValid(){
        return StringUtils.isNotBlank(phoneNumber);
    }
}

사용자 정의로 된 vo들은 isValid로 따로 유효성 검사를 정의 해주었다.

아쉬운 것은 필드별 if를 사용한 유효성 검사다.

class의 getFields()를 사용한 유틸을 만들 유효성 검사를 생각해 보았지만 그럴 경우 dto와 entity의 필드명을 정확히 같게 하지 않는 이상 오류가 기능을 장담할 수 없다고 생각이 들어 일단은 if으로 처리..

다음은 위 링크에서 영속성에 따른 메소드 호출 순서를 표현한 이미지다. 이해 하는데 많은 도움이 되어 첨부..

profile
Beyond the same routine

0개의 댓글