1) 기본생성자
@Data
//@NoArgsConstructor
public class Profile {
private String bio;
private String url;
private String occupation;
private String location;
public Profile(Account account) {
this.bio = account.getBio();
this.url = account.getUrl();
this.occupation = account.getOccupation();
this.location = account.getLocation();
}
}
java.lang.NullPointerException: null 발생
기본생성자가 따로 지정되지 않은 상태에서 매개변수가 있는 생성자 하나만 있을 경우 Account 객체가 없어서
기본생성자를 호출할 경우 NullPointerException이 발생하게 된다.
그래서 기본 생성자를 추가하면 해당 에러를 피할 수 있다. 단순한 문제이지만 실수하기 딱 좋다..
2) 트랜잭션에 따른 영속, 비영속 상태
<controller>
@PostMapping("/settings/profile")
public String updateProfile(@CurrentUser Account account, @Valid Profile profile, Errors errors, Model model) {
if (errors.hasErrors()) {
model.addAttribute(account);
return SETTINGS_PROFILE_VIEW;
}
accountService.updateProfile(account, profile);
return "redirect:/" + SETTINGS_PROFILE_URL;
}
<service>
@Transactional
public void updateProfile(Account account, Profile profile) {
account.setUrl(profile.getUrl());
account.setOccupation(profile.getOccupation());
account.setLocation(profile.getLocation());
account.setBio(profile.getBio());
}
service에 @Transction 이 선언되어 있다고 해도 해당 update가 진행되지 않는다. 왜일까?
한가지 예시를 보자면
<controller>
@GetMapping("/check-email-token")
public String checkEmailToken(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
String view = "account/checked-email";
if (account == null) {
model.addAttribute("error", "wrong.email");
return view;
}
if (!account.isValidToken(token)) {
model.addAttribute("error", "wrong.token");
return view;
}
accountService.completeSignUp(account);
model.addAttribute("numberOfUser", accountRepository.count());
model.addAttribute("nickname", account.getNickname());
return view;
}
<service>
public void completeSignUp(Account account) {
account.completeSignUp();
login(account);
}
findByEmail을 통해서 repository에서 account 객체를 불러오면서 영속상태(persist)를 가지게 된다.
영속상태에서 serivce를 호출하고 변경이 이루어지기 때문에 뷰에 렌더링 되기전에 db에 쿼리가 전달된다.
(JpaRepository 자체에 Transaction 처리가 되어있음을 명심하자!)
@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
boolean existsByEmail(String email);
boolean existsByNickname(String nickname);
Account findByEmail(String email);
Account findByNickname(String emailOrNickName);
}
직접 만든 메소드도 트랜잭션 처리를 하기 위해서는 해당 repository 에 꼭 트랜잭션 어노테이션을 붙혀주어야한다!
<controller>
@PostMapping("/settings/profile")
public String updateProfile(@CurrentUser Account account, @Valid Profile profile, Errors errors, Model model) {
if (errors.hasErrors()) {
model.addAttribute(account);
return SETTINGS_PROFILE_VIEW;
}
accountService.updateProfile(account, profile);
return "redirect:/" + SETTINGS_PROFILE_URL;
}
처음에 가져오려고 했던 account의 정보를 보자.
해당 account는 http sessiono안에 들어있던 account 정보를 가져온거라 (repository를 통하지 않음)
영속 상태가 아니다. detach 상태의 객체이다.
그래서 updateProfile에 account 정보를 넘겨서 값을 변경해도 update 쿼리가 나가지 않게 된다.
<service>
public void updateProfile(Account account, Profile profile) {
account.setUrl(profile.getUrl());
account.setOccupation(profile.getOccupation());
account.setLocation(profile.getLocation());
account.setBio(profile.getBio());
accountRepository.save(account);
}
save를 호출하게 되면 id값이 있는지 없는지 확인하고 merge를 시킨다.
그래서 update가 발생하게 된다.
현재 수정하고 있는 객체가 트랜잭션이 있는 상태인지, 아닌지 감안해서 코딩을 해야한다.