[JPA] 양방향 연관관계 매핑 및 생성 API

qufdl·2023년 6월 23일
0

JPA

목록 보기
1/1
post-thumbnail

프로젝트를 진행하면서 1:N 매핑 관련 API를 작성하는데 어려움이 있었어서 글을 작성하게 되었습니다. 그룹을 생성하고, 그룹을 생성한 멤버가 해당 그룹의 팀장 역할을 갖도록 하는 기능을 구현했습니다.

공부 기록용이기 때문에 틀리거나 미흡한 내용이 있을 수 있습니다.

연관관계 매핑

엔티티 구현

한 멤버는 여러 그룹을 생성할 수 있고, 그룹에는 여러 멤버가 가입할 수 있습니다.

ManyToMany를 사용하지 않은 이유?
@ManyToMany 어노테이션을 사용해 다대다 연관관계를 맺을 수도 있습니다. 하지만 그런 조인 방식으로 만든 매핑테이블에는 추가 컬럼을 만들 수 없다는 문제점이 있어 중간테이블을 만드는 방식을 사용했습니다.

GroupMember.java

public class GroupMember {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "group_id")
    private Group group;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id") 
    private Member member;

    @Enumerated(EnumType.STRING)
    private GroupRole groupRole;

    public GroupMember(Group group, Member member, String groupRole) {
        this(group, member, GroupRole.find(groupRole));
    }

    private GroupMember(Group group, Member member, GroupRole groupRole) {
        this.group = group;
        this.member = member;
        this.groupRole = groupRole;
    }
}
  • @ManyToOne
    • 연관관계에서 N에 해당하기 때문에 사용
    • 연관관계의 주인

Group.java

public class Group extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Enumerated(EnumType.STRING)
    private Interest interest;

    @Lob
    private String introduce;

    @OneToMany(mappedBy = "group", cascade = CascadeType.ALL)
    private List<GroupMember> groupMembers = new ArrayList<>();

    public Group(String name, String interest, String introduce) {
        this(name, Interest.find(interest), introduce);
    }

    private Group(String name, Interest interest, String introduce) {
        this.name = name;
        this.interest = interest;
        this.introduce = introduce;
    }

    public void putGroupMember(GroupMember groupMember) {
        this.groupMembers.add(groupMember);
    }
}
  • @OneToMany(mappedBy = "group", cascade = CascadeType.ALL)
    • mappedBy 속성을 사용해 연관관계의 주인(GroupMember)와 연관관계가 있다고 알려줌
    • cascade = CascadeType.ALL을 설정했기 때문에 group이 영속화될 때 GroupMember도 함께 영속화 됨
  • putGroupMember
    • setter 어노테이션을 설정하지 않고 의미있는 메소드 사용


연관관계의 주인은 ?

⇒ 외래키가 있는 곳!

  • 양방향 연관관계 매핑 시 두 연관관계 중 하나를 연관관계의 주인으로 정해야 합니다.
  • 연관관계의 주인만이 데이터베이스 연관관계와 매핑됩니다.
  • 연관관계의 주인만이 외래키를 관리(등록, 수정, 삭제)할 수 있습니다.
  • 주인이 아닌 곳은 읽기만 가능합니다.



서비스 계층 구현

GroupService.java

@Service
@AllArgsConstructor
public class GroupService {

    private final GroupRepository groupRepository;
    private final MemberRepository memberRepository;

    @Transactional
    public void createGroup(CreateGroupRequest request) {
        Member member = memberRepository.findByEmail(request.getEmail())
                .orElseThrow(() -> new IllegalArgumentException("Member not found"));

        if (groupRepository.existsByName(request.getName())) {
            throw new IllegalArgumentException("Duplicate group name");
        }

        Group group = new Group(request.getName(), request.getInterest(), request.getIntroduce());
        GroupMember groupMember = new GroupMember(group, member, "LEADER");
        group.putGroupMember(groupMember);

        groupRepository.save(group);
    }
}
  • createGroup

    • 그룹 생성시 groupName은 중복될 수 없음
    • 그룹을 생성하려는 멤버가 존재하면 Group 엔티티를 생성
    • GroupMember 엔티티를 생성
    • group에 groupMember 정보를 넣어 줌(양방향 관계 이므로)
    • groupRepository.save(group);을 호출해 영속화하여 DB에 저장



컨트롤러 계층 구현

GroupController.java

@RestController
@AllArgsConstructor
@RequestMapping("/api/group")
public class GroupController {

    private GroupService groupService;

    @Operation(summary = "그룹 생성")
    @PostMapping
    public ResponseEntity<Void> createGroup(@RequestBody CreateGroupRequest request) {
        groupService.createGroup(request);
        return ResponseEntity.noContent().build();
    }
}

postman으로 생성 api 실행

Group 테이블

GroupMember 테이블

데이터가 모두 잘 들어갑니다

Reference

0개의 댓글