DB 테이블에서는 관계에서 방향의 개념이 존재하지 않다. DB 테이블에서는 특정 테이블을 기준으로 원하는 정보를 JOIN하여 조회하기 때문이다. 즉, DB 테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고 방향 상관없이 조회가 가능하다.
하지만, 객체에서는 다른 엔터티의 정보를 갖고있지 않다면 조회(참조)가 불가능하다. 이로인해 DB 테이블에서는 없는 단방향, 양방향와 같은 방향의 개념이 존재한다.
따라서 DB 테이블에 실제 칼럼으로 존재하지는 않지만 Entity 상태에서 다른 Entity를 참조하기 위해 방향을 설정한다.
OverView
- 연관관계 어노테이션
- @OneToOne
- @ManyToOne
- @OneToMany
- @ManyToMany
연관관계 어노테이션
JPA 는 방향(Direction), 다중성(Multiplicity), 연관관계의 주인(Owner) 를 통해 객체와 테이블 간의 연관 관계를 맺는 방식의 차이를 극복한다.
- 방향(Direction): 객체가 다른 객체를 참조하는 방향을 말한다. (단방향/양방향)
테이블은 외래키를 기반으로 테이블간 양방향으로 JOIN이 가능하다. 하지만, 객체의 참조는 항상 단방향이다.
- 단방향: 특정 Entity 에서만 조회가 가능한 경우, 1:N 관계
- 양방향: 양쪽 Entity 에서 모두 참조가 가능한 경우, N:M 관계
- 다중성(Multiplicity): 객체의 관계(Relationships) 표현. 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
- 연관관계의 주인(Owner): 객체간 양방향 으로 방향성을 가질때 연관관계에 있어 누가 주인 인가를 정해야 된다.
테이블로 보면 누가 FK를 가져야 하는가의 문제이다. 연관관계의 주인인 엔티티가 FK를 업데이트 한다.
*연관 관계의 주인은 외래 키가 있는 곳으로 설정하는 것이 좋다. 이는 데이터베이스 입장에서 가장 효율적이기 때문이다.
모든 예시 설명에서는 Order Entity가 외래키의 주인을 가정하고 진행한다.
@OneToOne
단방향 관계
외래키의 주인만 상대 Entity 타입의 필드를 가지면서 @JoinCoumn()을 활용하여 외래키의 속성을 설정한다.
- 외래키의 주인을 정해야한다.
- Entity 에서 외래키의 주인은 일반적으로 N(다)의 관계인 Entity 이지만, 1:1 관계에서는 *외래키의 주인을 직접 지정해야 한다.
- 외래키의 주인이 활용하는 애너테이션인 @JoinColumn()을 활용하여 방향을 표현한다.
- @JoinColumn(): 외래키 매핑 시 사용되며 칼럼명, null 여부, unique 여부 등을 지정한다.
외래키의 주인만이 외래키를 등록, 수정, 삭제할 수 있으며, 주인이 아닌쪽은 오직 외래키를 읽기만 가능하다. 외래키의 주인이 아닌 곳에서 Entity 를 수정할 경우, 값이 지정되지 않는다.(직접 할당하는 방법으로 우회 가능)
Order Entity가 외래키의 주인인 경우
@Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @OneToOne @JoinColumn(name = "user_id") private User user; } @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; }
User Entity가 외래키의 주인인 경우
@Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; } @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name = "order_id") private Order order; }
양방향 관계
- 양방향 관계에서 외래키의 주인을 지정할 때, mappedBy 옵션을 사용한다.
- mappedBy는 외래키의 주인이 아닌곳에서 사용한다.
- mappedBy의 속성 값은 외래키의 주인인 상대 Entity의 필드명을 의미한다.
- 외래키의 주인은 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 외래키의 속성을 설정하고,
- 상대 Entity는 외래키의 주인 Entity 타입의 필드를 가지면서 mappedBy 옵션을 사용하여 속성 값으로 외래키의 주인 Entity에 선언된 @JoinColumn() 으로 설정되고 있는 필드명을 넣는다.
외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션이 적용되기 때문에 생략이 가능하다.
다만 1 대 N 관계에서 외래 키의 주인 Entity가 @JoinColumn() 애너테이션을 생략한다면 JPA가 외래 키를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성될 수 있다.따라서 외래키의 주인 Entity에서 @JoinColumn() 애너테이션을 활용하는것이 좋다.
양방향 관계에서 mappedBy 옵션을 생략할 경우 JPA가 외래 키의 주인 Entity를 파악할 수가 없어 의도하지 않은 중간 테이블이 생성되기 때문에 반드시 설정하도록 하자.
Order Entity가 외래키의 주인인 경우
@Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @OneToOne @JoinColumn(name = "user_id") private User user; } @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne(mappedBy = "user") private Order order; }
User Entity가 외래키의 주인인 경우
@Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @OneToOne(mappedBy = order) private User user; } @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name = "order_id") private Order order; }
@ManyToOne
- 다대일 관계의 반대는 항상 일대다(OneToMany) 관계이다.
- 연관 관계의 주인은 항상 N(다) 쪽이 갖는다.(== N(다) 쪽에 외래키가 존재한다.)
단방향 관계
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
// Order properties and methods
}
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// User properties and methods
}
양방향 관계
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
// Order properties and methods
}
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Order> orders = new ArrayList<>();
// User properties and methods
}
@OneToMany
단방향 관계
- 외래키를 OrderEntity가 직접 가질 수 있다면, INSERT 발생 시 한번에 처리할 수 있지만 실제 DB에서 외래키를 User테이블이 갖고 있기 때문에 추가적인 UPDATE가 발생한다는 단점이 존재한다.
// 외래키의 주인은 Order
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany
@JoinColumn(name = "order_id") // users 테이블에 order_id 칼럼
private List<User> userList = new ArrayList<>();
// Order properties and methods
}
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// User properties and methods
}
양방향 관계
- 1:N 관계에서는 일반적으로 양방향 관계가 존재하지 않는다.
- 1:N 관계에서 양방향 관계를 맺기위해서는 Order Entity를 외래키의 주인으로 정해주기 위해 User Entity 에서 mappedBy 옵션을 사용해야하지만, @ManyToOne 애너테이션은 mappedBy 속성을 제공하지 않기 때문이다.
- N 관계의 Entity인 User Entity 에서 @JoinColum의 insertable 과 updatable 옵션을 false 로 설정하여, JOIN 설정을 하면, 양방향처럼 설정할 수는 있다.
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "orderId", isertable = false, updatable = false) //ReadOnly 설정
private Order order;
// User properties and methods
}
@ManyToMany
- N:M 관계를 맺어주는 역할을 한다.
- N:M 관계를 풀어내기 위해 중간 테이블을 생성하여 사용한다.
- @JoinTable: 중간(연결) 테이블 사용 시, 별도 Entity 생성 없이 매핑한다.
단방향 관계
- 생성되는 중간 테이블을 컨트롤하기 어렵기 때문에 추후에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 있다.
// 외래키의 주인은 Order
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "statistics", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "order_id"), // 현재 위치인 Order Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
}
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// User properties and methods
}
양방향 관계
- 반대 방향인 UserEntity에 @ManyToMany로 Order Entity를 연결하고, mappedBy 옵션을 설정하여 외래키의 주인을 설정하면 양방향 관계 맺음이 가능하다.
// 외래키의 주인은 Order
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "statistics", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "order_id"), // 현재 위치인 Order Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
}
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "userList")
private List<Order> orderList = new ArrayList<>();
// User properties and methods
}
중간 테이블을 직접 만들어 관리하는 방법
- 중간 테이블 Statistics를 직접 생성하여 관리할 경우, 변경 발생 시 컨트롤 하기 쉬우며 확장성 측면에서 유용하다.
// 외래키의 주인은 Statistics
@Entity
class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToMany
@JoinTable(name = "order")
private List<Statistic> StatisticList = new ArrayList<>();
}
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Statistic> StatisticList = new ArrayList<>();
// User properties and methods
}
@Entity
@Table(name = "statistics")
public class Statistic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "order_id")
private Order order;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
728x90
'개인공부' 카테고리의 다른 글
[ Pydantic ] Pydantic을 활용한 Serialization(직렬화)와 Deserialization(역직렬화) (0) | 2024.05.30 |
---|---|
[ Pydantic ] Pydantic의 BaseModel 사용하기 (0) | 2024.05.28 |
[ JPA ] 다양한 어노테이션을 사용한 효과적인 엔터티 매핑_1.객체와 테이블 매핑 (0) | 2024.02.12 |
[ JPA ] JPA Entity, EntityManagerFactory, EntityManager, Persistence context (0) | 2024.02.11 |
[ JPA ] JPA 에 관하여 (1) | 2024.02.11 |