1. save 로 update 하면 발생하는 문제점

@PutMapping("/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) { 
    // json데이터를 요청 -> Java Object(MessageConverter의 Jackson 라이브러리가 변환해서 받아준다.)
    System.out.println("id : " + id);
    System.out.println("password : " + requestUser.getPassword());
    System.out.println("email : " + requestUser.getEmail());
    
    requestUser.setId(id);
    requestUser.setUsername("happy");
    userRepository.save(requestUser);   

   return null;
}

 

postman 에서 1번 id으로 send

1번 id를 찾아서 password,email 과, username은 값을 강제로 넣어

password,email, username 세개의 값만 저장하게 되면 User객체의 나머지는 null 값으로 들어가게 되어 문제가 생긴다.

 

 

 

username 은 값을 강제로 넣은이유

  •  requestUser.setUsername("happy"); 

 

nullable = false  → null 값을 저장할 때 오류가 생겨서 강제로 값을 집어넣었다.

@Column(nullable = false, length = 30) // null이 될 수 없고 30자까지
private String username;

오류메세지 not-null property references a null or transient value : com.cos.blog.model.User.username

 

 

 

 

2. save로 update하는 방법

@PutMapping("/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) { 
    // json데이터를 요청 -> Java Object(MessageConverter의 Jackson 라이브러리가 변환해서 받아준다.)
    System.out.println("id : " + id);
    System.out.println("password : " + requestUser.getPassword());
    System.out.println("email : " + requestUser.getEmail());

    User user = userRepository.findById(id).orElseThrow(()->{
        return new IllegalArgumentException("수정에 실패하였습니다.");
    });
    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());

    userRepository.save(user);

    return null;
}

 

  • 람다식으로 findById(id)를 못찾았을때 오류메세지 뜨기 

orElseThrow(()->{
        return new IllegalArgumentException("수정에 실패하였습니다.");
    });

 

 

실제 DB에서 받은 user 객체에는 null이 없으며 변경 된 password, email 을 가져와 user 객체에 넣고 

  •   user.setPassword(requestUser.getPassword());
      user.setEmail(requestUser.getEmail());

변경 된 user객체를 save 해준다.

  • userRepository.save(user);

 

 

 

postman에서 2번 id로 password, email을 수정하여 send

 

@RequestBody 어노테이션 역할이

{ "password" : "5678", "email" : "seon@naver.com" } 과 같이 json데이터를 요청하니 스프링이 Java Object 로 변환해서 받아준다. (그때 MessageConverter가 작동함으로서 Jackson 라이브러리가 변환해서 받아준다.)

 

그래서 @RequestBody은 json으로 받기위해 필요한 어노테이션이다.

 

 

 

findById(id) 로 2번을 찾아서 DB에 있는 실제 데이터를 user에 담고 (null값 없음) 업데이트를 해줌

DB

 

 

 

save 함수는 id를 전달하지 않으면 insert를 해주고
save 함수는 id를 전달하면 해당 id에 대한 데이터가 있으면 udpate를 해주고
save 함수는 id를 전달하면 해당 id에 대한 데이터가 없으면 insert를 해준다.

 

 

3. save를 안하고 @Transactional 붙여서 update 하기 ▶ 더티 체킹

 

userRepository.save(user); 를 주석처리하면 insert, update 가 안되고 @Transactional 어노테이션을 붙이면

@Transactional
@PutMapping("/dummy/user/{id}")

public User updateUser(@PathVariable int id, @RequestBody User requestUser) { 
    // json데이터를 요청 -> Java Object(MessageConverter의 Jackson 라이브러리가 변환해서 받아준다.)
    System.out.println("id : " + id);
    System.out.println("password : " + requestUser.getPassword());
    System.out.println("email : " + requestUser.getEmail());

    User user = userRepository.findById(id).orElseThrow(()->{
        return new IllegalArgumentException("수정에 실패하였습니다.");
    });
    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());

    // userRepository.save(user);

    return null;
}

 

postman 에서 3번 id를 수정시

id 3번 수정

 

save를 호출하지 않았음에도 DB에 변경이 됐다

DB

이것을 '더티 체킹' 이라고 함.

 

 


더티체킹이란?

상태 변경 검사이다.

JPA(영속성 컨텍스트)에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터베이스(DB) 반영한다.

그렇기 때문에 값을 변경한 뒤, save 하지 않더라도 DB에 반영되는 것이다.

 

 

JPA(영속성 컨텍스트)

서버(Controller)와 DB사이에 존재한다.

 

Controller에서 user객체를 save했을때

 jpa 영속성 컨텍스트 안에 있는 1차 캐시 '영속화' 된 user 객체가 생성된다. 

 영속화 된 user 객체 DB에 넣는것을 flush 라고 한다. (1차 캐시에 있는 user객체는 안사라지고 남아있음)

 

* '영속화 되었다' 는 영속성 컨텍스트의 1차 캐시에 객체를 영속 시켰다 (저장)

* flush 는 간단히 말해서 물건이 꽉 찬 창고를 더 큰 창고로 옮겨서 비우고 새로운 물건을 채우기 위함

 

하지만 JPA의 1차 캐시에는 flush 를 했지만 비우지 않고 남아있음

서버가 id 4번 데이터를 select 할때 굳이 DB에 접근해서 가져오지 않아도 1차 캐시에 있는 영속화 된 객체를 들고온다.

 

 

 

update를 할때

save vs @Transactional 

 

1. save 를 호출시 

- DB에 있는 id 2번 데이터를 select 하여 → 영속성 컨텍스트에 가져와 영속화를 시키고 서버에 들고온다.

- 서버에 있는 user 값을 변경하고 save 호출한다면 

서버에서 바뀐 값만! 영속화 한 user 객체에 업뎃을 시킨다. ▶ 변경 된 객체를 flush해서 DB에 넣어 값 변경 완료.

 

 

 

2. @Transactional 어노테이션을 썼을때

@Transactional 는 updateUser 함수(Controller)가 종료될때 (= 리턴) 자동으로 commit이 된다.

 

@Transactional
@PutMapping("/dummy/user/{id}")

public User updateUser(@PathVariable int id, @RequestBody User requestUser) { 
    System.out.println("id : " + id);
    System.out.println("password : " + requestUser.getPassword());
    System.out.println("email : " + requestUser.getEmail());

    User user = userRepository.findById(id).orElseThrow(()->{
        return new IllegalArgumentException("수정에 실패하였습니다.");
    });
    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());

    // userRepository.save(user);

    return null;
} //함수 종료

 

 

user 객체 User user = userRepository.findById(id) 를 DB에서 select jpa의 1차 캐시에 영속화하고

↕ 비교

Controller에서 password, email을 변경하고 

user.setPassword(requestUser.getPassword());
user.setEmail(requestUser.getEmail());

 

Controller 종료시 commit을 하면 1차 캐시에 있는 user 객체와 비교를 하는데

 

Controller 영속성 컨텍스트(JPA) DB
@Transactional
updateUser() {

user.setPassword(requestUser.getPassword());

user.setEmail(requestUser.getEmail());
} // 종료시 commit
1차 캐쉬 :
user 객체 ( id=1 )

JPA에서 변경 된 user객체를 update 하여 저장

 

 

이때 @Transactional 어노테이션이 Controller user 객체와 영속성 컨텍스트의 user객체를 변경감지를 한다.

수정 된 영속성 컨텍스트의 user객체는 Controller 종료시 DB에 update문 수행

 

 

즉 save호출은 변경 된 값만 jpa에 저장하여 DB에 update.

@Transactional 는 함수 종료할때 변경을 감지하고 수정된 JPA가 DB에 udpate를 넘겨준다. => 더티 체킹

 

 

반응형
LIST

+ Recent posts