๊ฒ์ํ ์ฝ๋ ๋งํฌ
์ต๊ทผ ๊ณต๋ถํ ๋ด์ฉ๋ค์ ๊ฒ์ํ์ ์ ์ฉํ๋ฉด์ ์ฌ๋ฌ๊ฐ์ง ์๋๋ฅผ ํ๊ณ ์๋ค.
์ต๊ทผ์๋ API๋ ๋ง๋ค์ด ๋ณด๊ณ ์๋๋ฐ ํ์ ์์ , ์ญ์ ์์ Dirty Checking์ด ๋ฐ์ํ์ง ์๋ ๋ฌธ์ ๊ฐ ์๊ฒผ๋ค.
Member
package springJpaBoard.Board.domain;
import lombok.Getter;
import springJpaBoard.Board.controller.requestdto.MemberRequestDTO;
import springJpaBoard.Board.domain.status.GenderStatus;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
public class Member {
@GeneratedValue
@Id
@Column(name = "member_id")
private Long id;
private String loginId; //๋ก๊ทธ์ธ ID
private String password; //๋ก๊ทธ์ธ ๋น๋ฐ๋ฒํธ
private String name;
@Enumerated(EnumType.STRING)
private GenderStatus gender;
@Embedded
private Address address;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL) //์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ ์ง์
private List<Board> boardList = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade= CascadeType.ALL)
private List<Comment> commentList = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE)
private List<Like> likeList = new ArrayList<>();
public void createMember(MemberRequestDTO memberRequestDTO, Address address) {
this.name = memberRequestDTO.getName();
this.gender = memberRequestDTO.getGender();
this.loginId = memberRequestDTO.getLoginId();
this.password = memberRequestDTO.getPassword();
this.address = address;
}
/*
ํ์ ์์ , Dirty Checking ๋ฐ์(์
๋ฐ์ดํธ ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ๋๊ฐ)
Setter๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ ์ํด ์์ ๋ฉ์๋๋ฅผ ๋ง๋ฆ
*/
public void editMember(MemberRequestDTO memberDto) {
this.name = memberDto.getName();
this.gender = memberDto.getGender();
this.address = new Address(memberDto.getCity(), memberDto.getStreet(), memberDto.getZipcode());
}
}
MemberApiController ์ค ํ์ ์์ ๋ฉ์๋
package springJpaBoard.Board.api;
import ...
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/members")
@Transactional(readOnly = true)
@Slf4j
public class MemberApiController {
private final MemberService memberService;
private final BoardService boardService;
/* ํ์ ์์ */
/**
* TODO ์์ ์๋๋ ์ค๋ฅ ํด๊ฒฐ
* */
@PostMapping("{memberId}/edit")
public ResponseEntity updateMember(@RequestBody @Validated(UpdateCheck.class) MemberRequestDTO form, BindingResult result) {
if (result.hasErrors()) {
throw new UserException("ํ์ ์์ ์ค๋ฅ");
}
Member updateMember = memberService.update(form.getId(), form);
ModifyMemberDto modifyMember = new ModifyMemberDto(updateMember);
Message message = new Message(StatusEnum.OK, "ํ์ ์ ๋ณด ์์ ์ฑ๊ณต", modifyMember);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
return new ResponseEntity<>(message, headers, HttpStatus.OK);
}
@Data
@AllArgsConstructor
static class ModifyMemberDto {
private Long id;
private String name;
private GenderStatus gender;
private Address address;
public ModifyMemberDto(Member member) {
this.id = member.getId();
this.name = member.getName();
this.gender = member.getGender();
this.address = member.getAddress();
}
}
}
MemberService ์ค updateMember
package springJpaBoard.Board.service;
import ...
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* ํ์ ์ ๋ณด ์์
*/
@Transactional
public Member update(Long memberId, MemberRequestDTO memberDto) {
/*
Dirty Checking ๋ฐ์, ๊ฐ๋ฅํ๋ค๋ฉด Setter๋ ์ฌ์ฉํ์ง ์๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํ
*/
Member member = memberRepository.findById(memberId).get();
member.editMember(memberDto);
System.out.println("member.getName() = " + member.getName());
return member;
}
}
MemberRepository
package springJpaBoard.Board.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import springJpaBoard.Board.domain.Member;
import springJpaBoard.Board.domain.status.GenderStatus;
import java.util.List;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findAllByName(String name);
// name๋ง ์ ๋ง
Page<Member> findByNameContaining(String keyword, Pageable pageable);
// gender๋ง ์ ํํ ๊ฒฝ์ฐ
Page<Member> findByGender(GenderStatus gender, Pageable pageable);
// name๊ณผ gender์ ๋ชจ๋ ์
๋ ฅํ ๊ฒฝ์ฐ
Page<Member> findByNameContainingAndGender(String name, GenderStatus gender, Pageable pageable);
/* loginId๋ก Member ์ฐพ์์ค๊ธฐ */
Optional<Member> findByLoginId(String loginId);
List<Member> findAllByLoginId(String loginId);
}
์ฌ๊ธฐ๊น์ง๊ฐ ํ์ ์์ , ์ญ์ ๊ด๋ จ ์ฝ๋์ ๋๋ค.
Dirty Checking์ ์ด๋ค ์กฐ๊ฑด์์ ์คํ๋๋๊ฐ?์ฐ์ Dirty Checking์ด ๋ฌด์์ธ์ง ์์๋ณด์.
Dirty Checking์ ์ดํ๋ฆฌ์ผ์ด์ ๋ด๋ถ ์ํฐํฐ์ ๋ณํ๋ฅผ ์ถ์ ํ๊ธฐ ์ํด ORM์์ ์ฌ์ฉ๋๋ ๊ฐ๋ ์ผ๋ก, ๋ณํ๋ฅผ ๊ฐ์งํ๊ณ ํธ๋์ญ์ ์ด commit ๋๋ฉด ์์ ์ฌํญ์ DB์ ์๋์ผ๋ก ๋ฐ์ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
๊ทธ๋ผ Dirty Checking ์ ์ธ์ ์คํ๋ ๊น?
Dirty Checking ์ ํธ๋์ญ์ ์ฐ๊ธฐ ์ฐ์ฐ ์ค ์์์ฑ ์ปจํ ์คํธ(Persistent Context) ๊ฐ ์ํฐํฐ์ ๋ณ๊ฒฝ์ ๊ฐ์งํ ๋ ์๋์ผ๋ก ์คํ๋๋ค.
JPA์์๋ ์ํฐํฐ๋ฅผ ์กฐํํ๋ฉด ํด๋น ์ํฐํฐ์ ์กฐํ ์ํ ๊ทธ๋๋ก ์ค๋ ์ท์ ๋ง๋ค์ด๋๋๋ฐ, ์ด ์ค๋ ์ท์ ์ํ์ ๋น๊ตํ์ฌ ๋ณ๊ฒฝ์ฌํญ์ด ์๋ค๋ฉด ์ค์ผ(Dirty)์ด ๊ฐ์ง(Checking) ๋๋ค๊ณ ํ๋จํ๋ค. ์ฆ ์ต์ด ์กฐํํ ์ํ์ ๋ค๋ฅผ ๋ Dirty Checking ์ด ๊ฐ์ง๋๋ค.
์์์ฑ ์ปจํ ์คํธ ๋ผ๋ ๋จ์ด๊ฐ ๋ฑ์ฅํ๋ค. ์์์ฑ ์ปจํ ์คํธ๋ JPA ๋ฐํ์ ํ๊ฒฝ์ผ๋ก ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ ๋ฐ DB ์์์ฑ์ ๊ด๋ฆฌํ๋ ์ญํ ์ ๋งก๋๋ค. ์ฝ๊ฒ ๋งํด ์ํฐํฐ์ ์๊ตฌ ์ ์ฅ ํ๊ฒฝ์ด์, ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ DB ์ฌ์ด์์ ๊ฐ์ฒด๋ฅผ ๋ณด๊ดํ๋ ๋ ผ๋ฆฌ์ ๊ฐ๋ ์ด๋ผ ํ ์ ์๋ค. ์์์ฑ ์ปจํ ์คํธ๋ผ๋ ์ํฐํฐ ์๊ตฌ ์ ์ฅ ํ๊ฒฝ์ด, ์ํฐํฐ์ ๋ณ๊ฒฝ์ ๊ฐ์งํ๋ค๋ฉด Dirty Checking ์ด ์ด๋ค์ง๋ค๊ณ ๋ณผ ์ ์๋ค.
์ ๋ฆฌํ์๋ฉด, ์ํฐํฐ๊ฐ ์์์ฑ ์ปจํ ์คํธ์ ๊ด๋ฆฌ๋ฅผ ๋ฐ์ ๋ Dirty Checking ์ ์คํ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด ์์์ฑ ์ปจํ ์คํธ๋ผ๋ ๊ฒ์ ์ด๋ ํ์ด๋ฐ์ ๋ฑ์ฅํ๋ ๊ฒ์ผ๊น? ์ด ์ง๋ฌธ์ ๋ํ ๋ต์ @Transactional ์ด ๊ฐ์ง๊ณ ์๋ค.
๊ทผ๋ฐ ์ฌ๊ธฐ๊น์ง ์ฝ์ ์ฌ๋ ์ค ์ด๋ฏธ ๋ฌธ์ ์์ธ์ ๋ฐ๊ฒฌํ ์ฌ๋์ด ์์ ๊ฒ ๊ฐ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ์ฌ๋์ ๋๋ฅผ ๋ฐ๋ณด๋ผ๊ณ ์๊ฐํ ๊ฒ์ด๋ค. ๋ง๋ค.. ๋๋ ์ธ์ ํ๋ค.
๋ค์ MemberApiController๋ฅผ ๋ณด์
package springJpaBoard.Board.api;
import ...
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/members")
@Transactional(readOnly = true)
@Slf4j
public class MemberApiController {
private final MemberService memberService;
private final BoardService boardService;
/* ํ์ ์์ */
/**
* TODO ์์ ์๋๋ ์ค๋ฅ ํด๊ฒฐ
* */
@PostMapping("{memberId}/edit")
public ResponseEntity updateMember(@RequestBody @Validated(UpdateCheck.class) MemberRequestDTO form, BindingResult result) {
if (result.hasErrors()) {
throw new UserException("ํ์ ์์ ์ค๋ฅ");
}
Member updateMember = memberService.update(form.getId(), form);
ModifyMemberDto modifyMember = new ModifyMemberDto(updateMember);
Message message = new Message(StatusEnum.OK, "ํ์ ์ ๋ณด ์์ ์ฑ๊ณต", modifyMember);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
return new ResponseEntity<>(message, headers, HttpStatus.OK);
}
@Data
@AllArgsConstructor
static class ModifyMemberDto {
private Long id;
private String name;
private GenderStatus gender;
private Address address;
public ModifyMemberDto(Member member) {
this.id = member.getId();
this.name = member.getName();
this.gender = member.getGender();
this.address = member.getAddress();
}
}
}
์ด์ํ ์ ์ด ๋ณด์ธ๋ค.
@Transactional(readOnly = true) ์ธ์ ์ ์ด๋์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ค์๋ก Controller์ @Transactional(readOnly = true) ์ ๋ถ์ฌ๋์ต๋๋ค. ์ด๊ฑฐ ๋๋ฌธ์ 4์๊ฐ ๋์ ๊ณ ์ํ๋ค.
@Transactional(readOnly = true)
์์ผ๋ก๋ ์ฝ๋๋ฅผ ์ ํ์ธํด์ผ๊ฒ ๋ค..