새소식

Java

[Java] JPA 관계 매핑: @OneToMany, @ManyToOne, @OneToOne, @ManyToMany Annotation, 관계의 주인 정리

  • -

 

JPA 관계 매핑

 

1)    JPA 관계 매핑의 개념

 

객체 간의 관계를 데이터베이스 테이블과 연결하는 기술

 

객체 지향 프로그래밍과 관계형 데이터베이스 간의 불일치를 해결할 수 있으며,

객체 간의 관계를 표현하고 조회, 추가, 수정, 삭제 등의 작업을 객체 단위로 수행할 수 있습니다.

 

@OneToMany, @ManyToOne, @OneToOne, @ManyToMany 등의 애너테이션을 사용하여 관계를 표현할 수 있습니다.

 

 

 

 

 

2)    연관관계

 

● 단방향 연관관계

한 엔티티에서 다른 엔티티를 참조하는 관계를 말합니다.

 

한 쪽 방향으로만 연관관계를 조회하고 수정할 수 있고, 연관 엔티티의 정보를 가져오기 위해서는 별도의 조회가 필요함.

 

간단하고 명확한 구조를 갖추어야 할 때 사용합니다.

 

 

아래 예시처럼

한 명의 작가(Author)는 여러 권의 책(Book)을 쓸 수 있지만, 책은 작가에 대한 참조를 갖지 않는 단방향 연관관계입니다.

@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // 생성자, getter, setter 등
}

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    // 생성자, getter, setter 등
}

 

 

 

 

 

● 양방향 연관관계

두 엔티티가 서로를 참조하여 상호 연결된 관계

 

한 엔티티를 통해 다른 엔티티를 조회할 수 있을 뿐만 아니라, 반대 방향으로도 연관된 엔티티를 조회할 수 있습니다.

 

무한 루프(Infinity Loop)를 방지하기 위해 toString(), hashCode(), equals() 메소드에서 상호 참조되는 필드를 제외!!!!!

 

 

한 학생(Student)은 여러 개의 과목(Course)을 수강할 수 있으며, 각 과목은 여러 학생에게서 수강되는 양방향 연관관계

@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "students")
    private List<Course> courses;

    // 생성자, getter, setter 등
}

@Entity
public class Course {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToMany
    private List<Student> students;

    // 생성자, getter, setter 등
}

 

 

 

 

 

3)    관계의 주인

● 개념

관계의 주인(Owner of the Relationship)은 양방향 연관관계에서 외래키(Foreign Key)를 관리하는 엔티티

 

mappedBy 속성을 사용하여 반대쪽 엔티티와의 연관관계를 맺고 있는 필드를 지정

 

 

 

● 어느쪽이?

외래키를 가지는 쪽이 관계의 주인입니다.

 

1. 일대다 관계의 경우

1) 다(N) 쪽이 관계의 주인인 경우

대부분의 경우, 다(N) 쪽에서는 외래 키를 관리하는 경우가 많습니다.

따라서 다(N) 쪽에서는 @OneToMany 애너테이션을 사용하여 일(1) 쪽과의 관계를 설정하고,

mappedBy 속성을 사용하여 외래 키를 관리하는 필드를 지정합니다.

 

2) 일(1) 쪽이 관계의 주인인 경우

일부 상황에서는 일(1) 쪽이 관계의 주인이 될 수 있습니다.

일(1) 쪽에서는 @OneToOne 또는 @ManyToOne 애너테이션을 사용하여 다(N) 쪽과의 관계를 설정합니다.

이때, 일(1) 쪽이 외래 키를 가지는 경우에는 관계의 주인이 됩니다.

 

 

 

2. 일대일 관계의 경우

주로 양쪽 엔티티 중 데이터베이스 스키마에 외래 키를 가지는 쪽이 관계의 주인으로 설정되며,

mappedBy 속성을 사용하여 주인이 아닌 엔티티와의 관계를 설정합니다.

 

특정 상황에 따라 주인을 명확히 정할 수 없는 경우에는 양쪽 엔티티가 서로에 대한 관계의 주인이 될 수도 있습니다.

 

 

 

3. 다대다 관계의 경우

양쪽 엔티티가 서로에 대한 관계의 주인이 아닌 경우가 일반적입니다.

이는 다대다 관계에서는 연결 테이블을 통해 관계를 맺기 때문에, 주인이 명확하게 정해지기 어렵기 때문입니다.

 

따라서, 다대다 관계에서는 양쪽 엔티티 중 어느 한 쪽을 관계의 주인으로 설정하지 않고,

mappedBy 속성을 사용하여 상호 참조를 가능하게 합니다.

(맨 아래 @ManyToMany 예시 참고)

 

 

 

● 사용하는 이유

데이터베이스에서 외래키를 어떤 엔티티가 관리하고 업데이트할지를 결정하기 위함입니다.

JPA에서는 연관관계를 저장할 때, 관계의 주인에 설정된 엔티티를 기준으로 외래 키를 업데이트합니다.

 

 

● 특징

1) 관계의 주인은 외래 키를 관리하고 업데이트하는 역할을 수행

2) 관계의 주인으로 설정된 엔티티는 @JoinColumn 애너테이션을 사용하여 외래 키를 지정할 수 있음

3) 데이터베이스에 변경사항을 반영할 때 해당 연관관계를 기준으로 외래 키를 업데이트함

 

 

● 예시

@Entity
public class Team {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

    // 생성자, getter, setter 등
}

Team 클래스는 일(1) 쪽 엔티티로, members 필드를 통해 다수의 Member 엔티티와 관계를 맺습니다.

일(1) 쪽에서 다(N) 쪽을 참조하는 방식입니다.

 

 

 

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Team team;

    // 생성자, getter, setter 등
}

@ManyToOne 애너테이션을 사용하여 team 필드를 통해 한 개의 Team 엔티티와 관계를 맺습니다.

이는 다(N) 쪽에서 일(1) 쪽을 참조하는 관계

 

Member 엔티티에서 Team과의 관계를 맺는 필드는 team 필드로, 외래 키는 Member 엔티티의 team 필드를 통해 생성

 

즉, Member 엔티티에서는 team_id 또는 teamId 같은 외래키 컬럼이 생성되고, 이 컬럼이 Team 엔티티의 주키(id)와 연결됩니다.

Member 엔티티는 teamId 외래키를 가지니까 관계의 주인입니다.

 

 

 

 

4)   @OneToMany 

● 개념

일대다 관계(One-to-Many)를 표현하기 위해 사용

 

한 엔티티(One)가 여러 개의 다른 엔티티(Many)와 관계를 맺을 때 사용됩니다.

 

예) 한 게임 플레이어가 여러 개의 캐릭터를 소유하는 상황, 한 사용자가 여러 개의 주문을 가지는 상황

 

 

● 구조

Entity1     -   One

@Entity
public class Entity1 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @OneToMany(mappedBy = "entity1" <- 관계의 주인인 Entity2의 필드값)
    private List<Entity2> entity2List;

    // 생성자, getter, setter 등
}

 

Entity2     -   Many

@Entity
public class Entity2 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @ManyToOne
    private Entity1 entity1;

    // 생성자, getter, setter 등
}

 

 

 

0) 관계의 주인 : 주로 다(Many)쪽이 관계의 주인이 됩니다.

                          외래키를 가진 쪽이 관계의 주인이 됩니다.

 

1) @OneToMany : 일인쪽에서 사용하는 애너테이션

 

2) mappedBy : 관계의 주인이 아닌 엔티티에서 양방향 관계를 설정할 때 사용되며,

                          해당 필드가 관계의 주인 엔티티와의 연결을 의미합니다.

 

★ mappedBy 뒤에 오는 값이 주인인 엔티티의 필드와 동일해야하니 아래 예시 반드시 참조

 

 

3) fetch : 관련된 엔티티를 어떻게 로딩할지 설정

  • FetchType.LAZY : 지연 로딩을 사용하여 필요할 때 엔티티를 로딩
  • FetchType.EAGER : 즉시 로딩을 사용하여 엔티티를 함께 로딩

 

 

 

 

● @JoinColumn

다대일(@ManyToOne) 또는 일대다(@OneToMany) 관계에서 외래 키를 지정할 때 사용

→ 다쪽에서도 사용가능하고, 일쪽에서도 사용가능함.

 

@OneToMany 관계에서는 일쪽 엔티티가 다쪽 엔티티의 컬렉션을 가지고 있으며, 다쪽 엔티티에 대한 외래 키를 지정해야 합니다.

 

@Entity
public class Department {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn(name = "department_id")
    private List<Employee> employees;

    // 생성자, getter, setter 등
}

@Entity
public class Employee {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // 다른 필드들

    @ManyToOne
    private Department department;

    // 생성자, getter, setter 등
}

 

 

 

 

● 예시

Trainer.java    -   One

@Entity
public class Trainer {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "trainer")
    private List<Pokemon> pokemonTeam;

    // 생성자, getter, setter 등
}

 

Pokemon.java     -   Many

@Entity
public class Pokemon {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Trainer trainer;

    // 생성자, getter, setter 등
}

 

위 예시는 한명의 트레이너는 여러 포켓몬을 가질 수 있는 @OneToMany이고

여러 포켓몬은 한명의 트레이너에 속할 수 있으니 @ManyToOne입니다.

 

trainer 필드를 통해 Pokemon 엔티티와 Trainer 엔티티가 연결되며, 양방향 관계가 형성됩니다.

 

 

 

 

 

 

5)   @ManyToOne

● 개념

다대일(Many-to-One) 관계를 매핑하기 위해 사용

 

여러 엔티티(Many)가  다른 하나의 엔티티(One)와 관계를 맺을 때 사용됩니다.

 

 

● 구조

Entity1     -   One

@Entity
public class Entity1 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @ManyToOne
    private Entity2 entity2;

    // 생성자, getter, setter 등
}

 

Entity2     -   Many

@Entity
public class Entity2 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    // 다른 쪽 엔티티와의 연관 관계 필드
    @OneToMany(mappedBy = "entity2")
    private List<Entity1> entity1List;

    // 생성자, getter, setter 등
}

 

 

● @JoinColumn

다대일(@ManyToOne) 또는 일대다(@OneToMany) 관계에서 외래 키를 지정할 때 사용

 

@ManyToOne 관계에서는 다른 테이블의 기본 키를 참조하는 외래 키 컬럼이 필요합니다.

이때 @JoinColumn을 사용하여 외래 키 컬럼의 이름, 데이터 타입, 길이 등을 지정할 수 있습니다.

 

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;

    private String orderNumber;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // 생성자, getter, setter 등
}

@Entity
public class Customer {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // 다른 필드들

    @OneToMany(mappedBy = "customer")
    private List<Order> orders;

    // 생성자, getter, setter 등
}

 

 

● 속성

1) targetEntity

관계를 맺을 엔티티 클래스를 명시적으로 지정합니다.
2) fetch

엔티티를 로딩할 때 연관 엔티티를 함께 로딩할지 지정합니다.
3) optional

관계 필드가 필수인지 여부를 지정합니다.

optional 속성은 @JoinColumn 애너테이션을 통해 관계를 맺는 필드의 nullable 여부를 지정합니다.

기본적으로 @ManyToOne 관계에서는 필드가 필수적으로 존재해야 하지만,

optional = true로 설정하여 필드를 선택적으로 nullable하게 설정할 수 있습니다.

 

 

 

 

● 예시1

Book    -   Many

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToOne(targetEntity = Author.class)
    private Author author;

    // 생성자, getter, setter 등
}

 

Author    -   One

@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author")
    private List<Book> books;

    // 생성자, getter, setter 등
}

 

 

 

 

● 예시2 ( targetEntity 사용)

Book    -   Many

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToOne(targetEntity = Author.class)
    private Author author;

    // 생성자, getter, setter 등
}

author 필드를 Author 엔티티와의 다대일 관계로 설정

targetEntity 속성을 사용하여 명시적으로 Author 클래스를 관계 대상 엔티티로 지정

 

 

 

Author    -   One

@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author")
    private List<Book> books;

    // 생성자, getter, setter 등
}

 

 

 

 

 

● 예시3 ( optional 사용)

Book    -   Many

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToOne(optional = true, targetEntity = Author.class)
    @JoinColumn(name = "author_id")
    private Author author;

    // 생성자, getter, setter 등
}

optional = true로 설정하여 author 필드가 선택적으로 nullable하게 설정

 

 

 

Author    -   One

@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author")
    private List<Book> books;

    // 생성자, getter, setter 등
}

 

 

 

 

 

6)   @OneToOne

● 개념

일대일 관계를 매핑하기 위해 사용

 

한 엔티티 인스턴스가 다른 엔티티 인스턴스와 하나의 연관 관계만을 가질 수 있는 관계입니다.

 

 

● 구조

Entity1     -   One

@Entity
public class Entity1 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @OneToOne(fetch = FetchType.LAZY / FetchType.EAGER, cascade = CascadeType.XXX)
    @JoinColumn(name = "foreign_key_column_name", referencedColumnName = "primary_key_column_name")
    private Entity2 entity2;

    // 생성자, getter, setter 등
}

 

Entity2     -   One

@Entity
public class Entity2 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @OneToOne(mappedBy = "entity2")
    private Entity1 entity1;

    // 생성자, getter, setter 등
}

 

 

 

● 속성

1) fetch: 연관 엔티티를 로딩할 때의 전략을 설정합니다.

    FetchType.LAZY는 지연 로딩을 사용하며, FetchType.EAGER는 즉시 로딩을 사용합니다.
2) cascade: 관련된 엔티티에 대한 변경 작업을 어떻게 처리할지 설정합니다.

    CascadeType.XXX 형식으로 사용하며, 여러 개의 옵션을 동시에 사용할 수 있습니다.
3) JoinColumn: 외래 키 컬럼을 지정합니다. 

    name 속성은 외래 키 컬럼의 이름을 설정하고,

    referencedColumnName 속성은 참조하는 엔티티의 기본 키 컬럼을 설정합니다.

 

 

 

● 예시

Person.java

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToOne(mappedBy = "person")
    private License license;

    // 생성자, getter, setter 등
}

 

License.java

@Entity
public class License {
    @Id
    @GeneratedValue
    private Long id;

    private String number;

    @OneToOne
    private Person person;

    // 생성자, getter, setter 등
}

Person 엔티티에서는 License 엔티티와의 일대일 관계

( Person 엔티티는 주 엔티티이고, License 엔티티는 보조 엔티티 )

 

 

 

 

 

7)   @ManyToMany

● 개념

다대다 관계를 매핑하기 위해 사용

 

여러 엔티티가 서로 다중 관계를 맺는 경우

 

 

● 구조

Entity1     -   Many

@Entity
public class Entity1 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @ManyToMany
    @JoinTable(
        name = "entity1_entity2", // 연결 테이블의 이름
        joinColumns = @JoinColumn(name = "entity1_id"), // Entity1 엔티티의 외래 키 컬럼
        inverseJoinColumns = @JoinColumn(name = "entity2_id") // Entity2 엔티티의 외래 키 컬럼
    )
    private List<Entity2> entity2List;

    // 생성자, getter, setter 등
}

 

Entity2     -   Many

@Entity
public class Entity2 {
    @Id
    @GeneratedValue
    private Long id;

    // 다른 필드들

    @ManyToMany(mappedBy = "entity2List")
    private List<Entity1> entity1List;

    // 생성자, getter, setter 등
}

@ManyToMany 애너테이션을 사용하여 양쪽 엔티티를 서로 연결

 

@JoinTable 애너테이션을 사용하여 연결 테이블의 이름과 외래 키 컬럼을 설정

Entity1 =  "entity1_entity2"라는 이름의 연결 테이블을 사용

Entity1의 외래키 컬럼 = entity1_id

Entity2의 외래키 컬럼 = entity2의 id  (entity2_id가 아니라 그냥 id)

 

 

● 속성

0) 관계의 주인

다대다 관계에서는 연결 테이블이 존재하며, 연결 테이블에는 양쪽 엔티티의 외래 키가 포함됩니다.

따라서, 양쪽 엔티티 모두 관계의 주인이 될 수 있습니다.

 

하지만 양쪽 엔티티가 서로에 대한 관계의 주인이 아닌 경우가 일반적입니다.

이는 다대다 관계에서는 연결 테이블을 통해 관계를 맺기 때문에, 주인이 명확하게 정해지기 어렵기 때문입니다.

 

따라서, 다대다 관계에서는 양쪽 엔티티 중 어느 한 쪽을 관계의 주인으로 설정하지 않고,

mappedBy 속성을 사용하여 상호 참조를 가능하게 합니다.

 

 

1) joinTable

연결 테이블을 지정을 위해 사용함.

연결 테이블은 실제로 다대다 관계를 관리하는 테이블로서, 주키값을 참조하는 외래키가 저장됩니다.


2) joinColumns

현재 엔티티의 외래키 컬럼에 대한 매핑 정보를 제공합니다.

연결 테이블에서 현재 엔티티의 외래키 컬럼을 지정하는 역할

3) inverseJoinColumns 

반대 엔티티의 외래 키 컬럼에 대한 매핑 정보를 제공합니다.

연결 테이블에서 반대 엔티티의 외래 키 컬럼을 지정하는 역할

4) cascade

연관된 엔티티에 대해 연관관계를 함께 관리할지 지정합니다.

예를 들어, 부모 엔티티가 삭제되면 자식 엔티티도 함께 삭제되도록 설정할 수 있습니다.

5) fetch

f연관된 엔티티를 어떻게 로딩할지 지정합니다. FetchType.LAZY = 지연 로딩 / FetchType.EAGER = 즉시 로딩

6) targetEntity

연관된 엔티티의 클래스를 명시적으로 지정합니다.

일반적으로 필드의 타입을 기반으로 자동으로 추론되지만, 명시적으로 지정할 수도 있습니다.

 

 

 

 

● 예시

Student.java

@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(
        name = "student_course", // 연결 테이블의 이름
        joinColumns = @JoinColumn(name = "student_id"), // 현재 엔티티의 외래 키 컬럼
        inverseJoinColumns = @JoinColumn(name = "course_id") // 반대 엔티티의 외래 키 컬럼
    )
    private List<Course> courses;

    // 생성자, getter, setter 등
}

@JoinTable: 연결 테이블의 이름을 "student_course"로 설정

joinColumns: 현재 엔티티(Student)의 외래키 컬럼을 "student_id"로 설정

- inverseJoinColumns: 반대 엔티티(Course)의 외래키 컬럼을 "course_id"로 설정

 

Student 엔티티에서 @JoinTable 애너테이션을 사용하여 연결 테이블의 이름과 현재 엔티티의 외래 키 컬럼(student_id)을 설정

 

Course.java

@Entity
public class Course {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "courses")
    private List<Student> students;

    // 생성자, getter, setter 등
}

mappedBy: Course 엔티티에서 Student 엔티티와의 관계를 매핑하는 필드인 "courses"를 지정

 

 

★다대다관계는 관계의 주인을 설정하지 않는 편이나,

이 예씨에서는 Student 엔티티의 courses 필드가 다대다 관계의 주인입니다.

이는 @JoinTable 애너테이션에서 joinColumns 속성을 사용하여

현재 엔티티(Student)의 외래 키 컬럼인 "student_id"를 지정했기 때문입니다.

반대로, Course 엔티티의 students 필드에서는 mappedBy 속성을 사용하여 관계의 주인이 아님을 나타냅니다.



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2023.07.14 - [Java] - @GenerativeValue의 개념과 예시

 

 

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.