[우아한테크코스] 6월 29일 TIL

4 minute read

[JPA] 연관관계 매핑

객체는 파스칼 표기법, 테이블은 스네이크 표기법

연관관계: 객체는 참조를 사용해 관계를 맺고, 테이블은 외래 키를 사용해 관계를 맺는다.
테이블은 양방향, 객체는 단방향
객체를 양방향으로 연관 관계를 맺으면 그 관계의 주인이 필요하다.

지하철역과 노선은 다대일, 노선과 지하철은 일대다 관계(지하철역 입장에서는 단방향)

기본생성자 없으면 컴파일러가 자동으로 만들고 매개변수 있는 생성자 만들면 안만든다.

  • 다대일 관계 매핑
      @ManyToOne  //매핑 설정
      @JoinColumn(name = "line_id", nullable = false) //외래키 이름 지정 -> default: line_id
      private Line line;
    

연관관계를 매핑하는 친구들은 영속화가 되어있어야 한다.(line과 station 모두 엔티티로 저장되어있어야 한다는 것)
연관관계 만들 때 id가 같이 가야하는데 영속화가 안되어 있으면 id가 같이 안가기 때문
nullable은 false로 맞춰주는 것이 좋음
생성자로도 관계 설정 가능

관계가 설정된 상태(잠실역과 2호선이 연관을 맺은 상태)에서 2호선을 지우려 하면?
기존에 있던 연관관계를 제거하고 2호선을 지워야 한다.

  • 양방향 관계 매핑
    Line에서 Station 갈 수 있듯이 Station에서 Line 갈 수 있게 하는 것
      @OneToMany(mappedBy = "line")
      private List<Station> stations = new ArrayList<>();
    

    Station의 line 필드에 매핑되어라 라고 하는 것
    station이 연관관계를 주로 잡고 있으므로 line이 양방향으로 매핑되고자 할 때 mappedBy로 설정이 필요한 것
    이거를 안주면 jpa는 디폴트로 line_stations라는 연결테이블을 만든다. line과 station 사이에 관리할 테이블
    하지만 이렇게 실제 세상에 존재하지 않는 테이블을 만들 필요 없이 우리는 mappedBy로 힌트를 줌으로써 해결할 수 있다.

  • 연관 관계의 주인
    객체 간의 연관 관계는 서로 다른 단방향으로 연결된거지 양방향 연결이 아니다.
    두 객체에 있어서 연관 관계를 정의할 수 있는 키를 소유하고 있는 객체가 연관관계의 주인이다.
    연관관계의 주인, 즉, 외래키 관리자는 외래키를 등록/수정/삭제할 수 있는 객체를 의미한다.
    외래키 관리자가 아닌 객체는 read만 가능하다. 연결된 객체에 대해 어떠한 수정/삭제 행위를 할 수 없다.
    보통은 다대일, 일대다에서 다가 외래키 관리자가 된다.
    외래키 관리자가 아닌 객체가 수정/삭제의 행위를 하고 싶으면 외래키 관리자 객체에 요청을 보내야만 한다.
    양방향 관계에서는 아래와 같이 관리자에게 추가하고, 요청을 보내는 자신 객체에도 추가하는 두가지 행위를 다 해야한다.
      public void addStation(Station station){
          station.setLine(this);
          stations.add(station);
      }
    

    하지만 이렇게 station에도 넣고 line에도 넣는 메서드를 만들면 무한으로 도니까 들어가있으면 지우고 다시 넣는 행위를 수행해야 한다.

      public void setLine(Line line) {
          if(Objects.nonNull(line)) {
              line.getStations().remove(this);
          }
          this.line = line;
          line.getStations().add(this);
      }
    
  • 일대다 관계 매핑
      @OneToMany
      @JoinColumn(name = "member_id")
      private List<Favorite> favorites = new ArrayList<>();
    

    JoinColumn에 이름을 설정해주면 별도의 중간 테이블을 만들지 않고 favorite에 member_id 컬럼이 생성된다.
    이것을 지정해주지 않으면 중간 테이블을 jpa에서 자동으로 만든다.
    일대다일 때 다인 Favorite 객체는 연관관계가 있는지 없는지 모르기 때문에 insert한 후에 한번 더 update 쿼리를 보내는 과정을 거친다.

  • 일대일 연관 관계 매핑
    source와 target을 정해야 한다. 주 테이블과 대상 테이블 중 어디에 외래 키를 둘지 고민해야 한다.
    • 주 테이블에 외래 키를 두는 경우: 주 테이블만 확인해도 대상 테이블과의 연관 관계 확인 가능, 대상 테이블 코드에는 변화 없음
      양방향 연관 관계 가지는 방법: @OneToOne(mappedBy = "lineStation")
    • 대상 테이블에 외래 키를 두는 경우: 일대일 관계를 일대다로 확장할 때 테이블 구조에 변화를 안가질 수 있다. oneToOne -> ManyToOne, 하지만 단방향 연관관계가 성립되지 않는다.
  • 다대다 연관 관계 매핑
    관계형 데이터베이스에서는 불가능하다. 연결 테이블에 필드가 추가되면 더는 사용할 수 없다.

각 관계 장단점 정리

지연로딩, CASCADE, 고아 객체부터 다시

[JPA] 데이터베이스 스키마 자동 생성하기

어플리케이션 실행 시점에 자동 생성되는 DDL은 개발 장비에서만 사용하고 운영서버에서는 사용하지 않도록 한다. 개발 초기에는 create/update, 테스트는 update/validate, 운영은 validate/none

  • hibernate.hbm2ddl.auto
    create: drop + create
    create-drop: 종료 시점에 테이블 drop
    update: 변경된 것만 반영(운영DB에서 사용하면 안됨) -> 수만건일 때 문제 발생
    validate: 엔티티와 테이블 정상 매핑 확인
    none: 사용하지 않음

[JPA] 매핑 어노테이션

  • database에 매핑될 매핑 정보
    • @Column
      name: 필드와 매핑할 테이블의 컬럼 이름
      insertable, updatable: 읽기 전용 false면 삽입 안됨
      nullable: null 허용여부 결정, DDL 생성 시 사용
      unique: 유니크 제약 조건, DDL 생성 시 사용
      columnDefinition, length, precision, scale(DDL)
      length: 길이 제한
    • @Temporal
      날짜 타입 매핑
      DATE: 날짜
      TIME: 시간
      TIMESTAMP: 날짜와 시간
    • @Enumerated(EnumType.STRING)
      무조건 STRING 사용할 것!! ORDINAL 사용하면 0, 1 이런 식으로 들어감
      ORDINAL: 순서 저장(default)
      STRING: 열거형 이름을 그대로 저장
    • @Lob
      CLOB: string, char[], java.sql.CLOB
      BLOB: byte[], java.sql.BLOB
      컨텐츠 길이가 길 때 바이너리 파일로 db에 넣을 때 사용하는 것
    • @Transient
      db에 저장하지 않는 필드
  • 식별자 매핑 방법
    기본 키 제약 조건은 not-null, unique, immutable
    영원히 조건 만족하기는 어려우므로 대체 키를 사용하자
    권장 사항: Long + 대체키 + 키 생성전략 사용
    • @Id
    • @GeneratedValue IDENTITY: 데이터베이스에 위임 (auto_increment)
      SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용 (@SequenceGenerator)
      TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용 (@TableGenerator)
      AUTO: 방언에 따라 자동 지정, 기본값

[JPA] 연관관계 매핑

  • 연관관계 매핑 어노테이션: @ManyToOne, @OneToMany, @OneToOne, @ManyToMany / @JoinColumn, @JoinTable
  • 상속관계 매핑 어노테이션: @Inheritance, @DiscriminatorColumn, @DiscriminatorValue, @MappedSuperclass
  • 복합키 어노테이션: @IdClass, @EmbeddedId, @Embeddable, @MapsId => 여러 키 묶어서 pk로 쓰고 싶은 경우

** 자율적인 객체들의 협력 공동체를 만들자 **

  • 객체를 테이블에 맞추어 모델링
    협력 관계를 만들 수 없다.
    테이블은 외래키 조인을 통해 연관된 테이블을 찾고, 객체는 객체대로 참조를 이용해야 한다는 단점이 존재한다.
    EntityManager를 통할 경우 모든걸 entityManager가 하도록 함

  • 객체 지향 모델링
    객체의 참조와 테이블의 외래키를 매핑한다.
    Member(다) -> Team(일)
    참조로 연관관계 조회와 객체 그래프 탐색

  • 단방향 매핑 @ManyToOne(fetch = FetchType.LAZY) 설정하면 조회하는 것이 사용될 때 실행됨
    웬만하면 LAZY 하기, EAGER가 즉시 실행
    연관관계에서 수정하면 찐 객체도 수정됨
      @ManyToOne
      @JoinColumn(name = "TEAM_ID")
      private Team team;
    
  • 양방향 매핑
    mappedBy: 객체와 테이블 간의 연관관계를 맺는 방식의 차이로 인해 등장
    객체는 단방향으로 연관관계를 맺는다.
    객체를 양방향으로 참조하려면 단방향 연과관계를 서로 갖고 있는 것이다.
    테이블은 양방향으로 연관관계를 맺는다.
    테이블은 외래키로 양방향 참조가 가능하다.

연관관계의 주인
둘 중 하나, 주인만 외래키를 관리할 수 있다.
주인이 아닌 쪽은 읽기만 가능하다. 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
외래 키가 있는 곳을 주인으로 정하자.

    @OneToMany(mappedBy = "team")
    List<Member> members = new ArrayList<>();

연관관계의 주인에 값을 넣지 않고(Member.setTeam을 하지 않고) 주인이 아닌 객체에만 값을 넣으면(team에만 member를 add하면) 안된다.
이를 방지하기 위해 양쪽 다 값을 입력하도록 구현해야 한다.
단방향 매핑으로도 연관관계 매핑은 되었지만 반대 방향으로 조회 기능을 추가한 것(JPQL에서 역방향 탐색할 일이 많음)이므로 양방향은 필요할 때 추가해도 된다.

db 설계를 철저히 하고 구현을 시작해야한다..