-
카테고리
-
세부 분야
백엔드
-
해결 여부
미해결
/api/v1/members 버그
24.07.02 00:49 작성 조회수 39
0
1. 강의 내용과 관련된 질문인가요? 예
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예
3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예
[질문 내용]
강사님처럼 코드를 다음과 같이 작성했습니다.
package jpabook.jpashop.api;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.MemberService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
Long joinedMemberId = memberService.join(member);
return new CreateMemberResponse(joinedMemberId);
}
@PutMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2(@PathVariable("id") Long id, @RequestBody @Valid UpdateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
memberService.update(id, request.getName());
return new UpdateMemberResponse(id, request.getName());
}
@Data
static class CreateMemberRequest {
@NotEmpty
private String name;
}
@Data
@AllArgsConstructor
static class CreateMemberResponse {
private final Long id;
}
@Data
static class UpdateMemberRequest {
@NotEmpty
private String name;
}
@Data
@AllArgsConstructor
static class UpdateMemberResponse {
private Long id;
private String name;
}
}
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {
private final MemberRepository memberRepository;
/*
* 회원 가입
* */
@Transactional
public Long join(Member member) {
validateDuplicateMember(member); // 중복 회원 검증
memberRepository.save(member);
return member.getId();
}
/*
* 중복 확인 메서드
* */
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
// 회원 전체 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name); // 영속성 컨텍스트에서 엔티티를 수정하면 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)가 동작한다.
}
}
package jpabook.jpashop.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jpabook.jpashop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
// 컴포넌트 스캔에 의해
// 스프링 빈에 자동으로 등록
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void save(Member member) {
em.persist(member);
}
public Member findOne(Long id) {
return em.find(Member.class, id);
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
}
}
package jpabook.jpashop.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter @Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
@NotEmpty
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
그리고 postman으로 localhost:8080/api/v1/members로 호출을 하게 되면 다음과 같이 무지막지하게 많은 양의 결과가 나옵니다. 왜 그러는 걸까요?
답변을 작성해보세요.
0
인프런 AI 인턴
2024.07.04
안녕하세요, 인프런 AI 인턴입니다.
박동규님의 코드와 관련된 질문을 주셨네요.
문제의 원인은 Member
엔티티가 Order
엔티티와 양방향 연관관계를 맺고 있어서 Member
조회 시 연관된 Order
정보까지 함께 조회하기 때문입니다. 그 결과, Order
내에 다시 Member
정보가 포함되고, 이로 인해 무한한 참조가 발생하여 결과적으로 많은 양의 데이터가 출력되는 것입니다.
이를 해결하기 위해서는 양방향 연관관계에 있는 엔티티 중 하나를 JSON 결과에서 제외시킬 필요가 있습니다. @JsonIgnore
어노테이션을 사용해서 Member
엔티티 내의 orders
필드를 JSON 결과에서 제외시킬 수 있습니다.
import com.fasterxml.jackson.annotation.JsonIgnore;
...
@Entity
@Getter @Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
@NotEmpty
private String name;
@Embedded
private Address address;
@JsonIgnore
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
해당 변경을 적용하시면, /api/v1/members
요청 시 Member
정보만 반환되고, 연관된 Order
정보는 반환되지 않게 됩니다. 이렇게 하면 결과 데이터 양을 줄일 수 있으며 무한 참조 문제를 방지할 수 있습니다.
도움이 되셨기를 바랍니다.
답변 1