SpringBoot - JPA 연관관계 매핑

Yu Seong Kim·2024년 1월 27일
0

SpringBoot

목록 보기
16/29

간단한 게시판을 만들거나 복잡한 프로젝트를 진행할때 RDBMS를 이용한다. RDBMS를 이용하면서, 테이블 하나로 모든 데이터를 관리할 수 없다. 즉, 테이블을 여러개 만들어 관련된 데이터들을 나누고, 관리한다. 필요시에는 테이블 조인(Join)하여 처리해야 한다.
JPA를 이용하면 테이블간의 관계처럼, 엔티티들 사이의 관계를 통해 데이터를 관리할 수 있다.
객체지향 프로그램에서의 객체와 RDBMS에서의 테이블이 서로 연관관계를 맺는 방법이 다르다. 그렇기 때문에 이 둘의 차이를 채우기 위한 매핑과정이 필요하고 이를 ORM 인 JPA 가 수행하게 된다.

  • 방향(Direction) : 단방향 연관관계, 양방향 연관관계
  • 다중성(Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
  • 연관관계의 주인(Owner)

객체 연관관계 vs 테이블 연관관계

객체 연관 관계

  • 회원 객체(User)는 User.board 필드(멤버 변수)로 팀 객체와 연관관계를 맺습니다.
    회원 객체와 팀 객체는 단방향 관계입니다. 회원은 User.board 필드를 통해 팀을 알 수 있지만, 팀 객체로 소속된 회원들을 알 수 없기 때문입니다. User ➡️ board 의 조회는 User.getBoard()으로 가능하지만, board ➡️ User 를 접근하는 필드는 없습니다.
    테이블 연관관계
  • 회원 테이블은 BOARD_ID 외래 키로 팀 테이블과 연관관계를 맺습니다.

회원 테이블과 팀 테이블은 양방향 관계입니다. 회원 테이블의 BOARD_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고, 반대로 팀과 회원도 조인할 수 있습니다. 예를 들어, USER 테이블의 BOARD_ID 외래 키 하나로 MEMBER board TEAM 과 board JOIN User 둘 다 가능합니다.

객체 연관관계와 테이블 연관관계의 가장 큰 차이

참조를 통한 연관관계는 항상 단방향입니다. 객체간에 연관관계를 양방향으로 만드록 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 합니다. 이렇듯 양쪽에서 서로 참조하는 것을 양방향 연관관계라고 합니다.
그러나 이것은 양방향 관계가 아니라 서로 다른 단방향 관계 2개입니다. 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있습니다.

객체 연관관계 vs 테이블 연관관계 정리

  • 객체는 참조(주소)로 연관관계를 맺습니다.
  • 테이블은 외래 키로 연관관계를 맺습니다.
  • 참조를 사용하는 객체의 연관관계는 단방향입니다.
  • 외래 키를 사용하는 테이블의 연관관계는 양방향입니다. (A JOIN B가 가능하면 반대로 B JOIN A도 가능)

[테이블연관관계]

User엔티티


@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Builder
@ToString(exclude = "boardList")
@Table
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false,unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;

    @ElementCollection(fetch = FetchType.EAGER)
    @Builder.Default
    private List<String> roles = new ArrayList<>();

    @OneToMany(fetch = FetchType.LAZY,mappedBy = "user",cascade = CascadeType.ALL)
    @JsonIgnore
    private List<Board> board;
    
    //계정이 가지고 있는 권한 목록 리턴
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities(){
        return this.roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
    //계정의 비밀번호 리턴
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public String getPassword(){
        return this.password;
    }
    //계정의 이름 리턴
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public String getUsername() {
        return this.getEmail();
    }

    //계정이 만료됐는지 리턴 - true면 만료 X
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //계정이 잠겨 있는지 리턴 -> true면 안잠김.
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //계정의 비밀번호가 만료됐는지 리턴 -> ture면 만료 X
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //계정이 활성화돼 있는지 리턴 -> true면 활성화
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isEnabled() {
        return true;
    }
    

}

Board엔티티

package com.springboot.jwt_securityprac.data.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity

@Getter
@Setter
@ToString(exclude = "member")
@Table(name = "Board")
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String title;

    @Column
    private String content;

    @Column
    private LocalDateTime createDate;

    @Column
    private LocalDateTime updateDate;

    @ManyToOne //다대일 관계, 여러 게시물이 하나의 user의 것
    @JoinColumn(name = "user_id")
    @JsonIgnore
    private User user;

}

[객체 연관관계]

User엔티티


@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Builder
@ToString(exclude = "boardList")
@Table
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false,unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;

    @ElementCollection(fetch = FetchType.EAGER)
    @Builder.Default
    private List<String> roles = new ArrayList<>();

    @OneToMany(fetch = FetchType.LAZY,mappedBy = "user",cascade = CascadeType.ALL)
    @JsonIgnore
    private Board board;
    
    //계정이 가지고 있는 권한 목록 리턴
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities(){
        return this.roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
    //계정의 비밀번호 리턴
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public String getPassword(){
        return this.password;
    }
    //계정의 이름 리턴
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public String getUsername() {
        return this.getEmail();
    }

    //계정이 만료됐는지 리턴 - true면 만료 X
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //계정이 잠겨 있는지 리턴 -> true면 안잠김.
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //계정의 비밀번호가 만료됐는지 리턴 -> ture면 만료 X
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //계정이 활성화돼 있는지 리턴 -> true면 활성화
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isEnabled() {
        return true;
    }
    

}

Board엔티티

package com.springboot.jwt_securityprac.data.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity

@Getter
@Setter
@ToString(exclude = "member")
@Table(name = "Board")
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String title;

    @Column
    private String content;

    @Column
    private LocalDateTime createDate;

    @Column
    private LocalDateTime updateDate;

}

@ManyToOne, @JoinColumn
@ManyToOne: 다대일(N:1) 관계라는 매핑 정보입니다. 위에서 봤던 회원과 팀은 다대일 관계입니다. 연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션을 필수로 사용해야 합니다.

속성기능기본값
optionalfalse로 설정하면 연관된 엔티티가 항상 있어야 한다true
fetch글로벌 패치 전략을 설정한다.@ManyToOne=FetchType.EAGER, @OneToMany=FetchType.LAZY
cascade영속성 전이 기능을 사용한다.
targetEntity연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않음

@JoinColumn: 외래 키를 매핑할 때 사용합니다.

속성기능기본값
name매핑할 외래 키 이름"필드명" + "_" + "참조하는 테이블의 기본 키 컬럼명"
referencedColumnName외래 키가 참조하는 대상 테이블의 컬럼명참조하는 테이블의 기본 키 컬럼명
foreignKey(DDL)외래 키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때만 사용한다.
unique@Column의 속성과 같다
nullable@Column의 속성과 같다
insertable@Column의 속성과 같다
updatable@Column의 속성과 같다
columnDefinition@Column의 속성과 같다
table@Column의 속성과 같다
profile
인생을 코딩하는 남자.

0개의 댓글