@OneToMany
JPA 주석 참조 의 예제 섹션에서 :
예제 1-59 @OneToMany-제네릭이있는 고객 클래스
@Entity
public class Customer implements Serializable {
...
@OneToMany(cascade=ALL, mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
...
}
예제 1-60 @ManyToOne-제네릭이있는 주문 클래스
@Entity
public class Order implements Serializable {
...
@ManyToOne
@JoinColumn(name="CUST_ID", nullable=false)
public Customer getCustomer() {
return customer;
}
...
}
Customer
실체가 협회의 소유자 인 것 같습니다 . 그러나 mappedBy
동일한 문서 의 속성에 대한 설명 에서 다음과 같이 작성되었습니다.
관계가 양방향 인 경우, 연관의 비 (소유하지 않은)면에서 mappingBy 요소를 예 1-60에 표시된대로 관계를 소유하는 필드 또는 특성의 이름으로 설정하십시오.
그러나 내가 틀리지 않으면 예와 같이 보입니다. mappedBy
실제로는 비 소유 측이 아닌 협회의 소유 측에 지정되어 있습니다.
그래서 내 질문은 기본적으로 :
-
양방향 (일대 다 / 다 대일) 연결에서 소유자 중 어느 엔터티입니까? 한면을 소유자로 어떻게 지정할 수 있습니까? 다수를 소유자로 어떻게 지정할 수 있습니까?
-
“협회의 반대면”이란 무엇입니까? 우리는 어떻게 한쪽을 역으로 지정할 수 있습니까? 우리는 어떻게 많은면을 역수로 지정할 수 있습니까?
답변
이를 이해하려면 한발 물러서야합니다. OO에서 고객은 주문을 소유합니다 (주문은 고객 오브젝트의 목록입니다). 고객이 없으면 주문할 수 없습니다. 따라서 고객은 주문의 소유자 인 것 같습니다.
그러나 SQL 세계에서 한 항목에는 실제로 다른 항목에 대한 포인터가 포함됩니다. N 개의 주문에 대해 1 명의 고객이 있으므로 각 주문에는 자신이 속한 고객에 대한 외래 키가 포함됩니다. 이것은 “연결”이며 연결 (정보)을 “소유”(또는 문자 그대로 포함)하는 순서를 의미합니다. 이것은 OO / 모델 세계와 정반대입니다.
이것은 이해하는 데 도움이 될 수 있습니다.
public class Customer {
// This field doesn't exist in the database
// It is simulated with a SQL query
// "OO speak": Customer owns the orders
private List<Order> orders;
}
public class Order {
// This field actually exists in the DB
// In a purely OO model, we could omit it
// "DB speak": Order contains a foreign key to customer
private Customer customer;
}
반대쪽은 개체,이 경우 고객의 OO “소유자”입니다. 고객은 주문을 저장할 테이블에 열이 없으므로 주문 테이블에서이 데이터를 저장할 수있는 위치를 알려야합니다 (이를 통해 발생 함 mappedBy
).
또 다른 일반적인 예는 부모와 자식이 될 수있는 노드가있는 나무입니다. 이 경우 두 필드는 하나의 클래스에서 사용됩니다.
public class Node {
// Again, this is managed by Hibernate.
// There is no matching column in the database.
@OneToMany(cascade = CascadeType.ALL) // mappedBy is only necessary when there are two fields with the type "Node"
private List<Node> children;
// This field exists in the database.
// For the OO model, it's not really necessary and in fact
// some XML implementations omit it to save memory.
// Of course, that limits your options to navigate the tree.
@ManyToOne
private Node parent;
}
이것은 “외국 키”다 대일 디자인 작업에 대해 설명합니다. 관계를 유지하기 위해 다른 테이블을 사용하는 두 번째 방법이 있습니다. 즉, 첫 번째 예의 경우 고객이있는 테이블과 주문이있는 테이블, 기본 키 쌍 (customerPK, orderPK)이있는 2 열 테이블의 세 가지 테이블이 있습니다.
이 방법은 위의 방법보다 융통성이 있습니다 (일대일, 다 대일, 일대 다 및 다대 다를 쉽게 처리 할 수 있음). 가격은
- 조금 느립니다 (다른 테이블을 유지하고 조인하면 두 테이블 대신 세 테이블을 사용합니다).
- 조인 구문은 더 복잡합니다 (예를 들어 무언가를 디버깅하려고 할 때 많은 쿼리를 수동으로 작성해야하는 경우 지루할 수 있음)
- 연결 테이블을 관리하는 코드에서 무언가 잘못되었을 때 갑자기 너무 많거나 너무 적은 결과를 얻을 수 있기 때문에 오류가 발생하기 쉽습니다.
그렇기 때문에이 방법을 거의 권장하지 않습니다.
답변
믿을 수 없을 정도로, 3 년 안에 아무도 당신의 관계에 대한 두 가지 방법의 모범으로 당신의 훌륭한 질문에 대답하지 못했습니다.
다른 사람들이 언급했듯이 “소유자”측은 데이터베이스의 포인터 (외부 키)를 포함합니다. 한쪽을 소유자로 지정할 수 있지만, 한쪽을 소유자로 지정하면 관계가 양방향이 아닙니다 (역의 “다수”쪽은 “소유자”에 대한 지식이 없음). 이것은 캡슐화 / 느슨한 커플 링에 바람직 할 수 있습니다.
// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
@OneToMany(cascade = CascadeType.ALL)
private List<Order> orders;
}
// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
// @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}
유일한 양방향 맵핑 솔루션은 “many”측이 “one”에 대한 포인터를 소유하고 @OneToMany “mappedBy”속성을 사용하는 것입니다. “mappedBy”속성이 없으면 Hibernate는 이중 매핑을 예상 할 것이다 (데이터베이스는 조인 열과 조인 테이블을 둘 다 가질 것이다.
// "One" Customer as the inverse side of the relationship
public class Customer {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
private List<Order> orders;
}
// "many" orders each own their pointer to a Customer
public class Order {
@ManyToOne
private Customer customer;
}
답변
데이터베이스에 외래 키가있는 테이블이있는 엔터티는 소유 엔터티이며, 가리키고있는 다른 테이블은 역 엔터티입니다.
답변
양방향 관계의 간단한 규칙 :
1. 다 대일 양방향 관계의 경우 많은 쪽이 항상 관계의 소유 측입니다. 예 : 1 방에 많은 사람이 있습니다 (사람은 한 방에만 속함)-> 소유 측은 사람입니다
2. 일대일 양방향 관계의 경우, 소유 측은 해당 외래 키를 포함하는 측에 해당합니다.
다 대다 양방향 관계의 경우, 어느 쪽이든 소유하는 쪽일 수있다.
희망이 당신을 도울 수 있습니다.
답변
두 개의 엔티티 클래스 Customer 및 Order의 경우 최대 절전 모드는 두 개의 테이블을 작성합니다.
가능한 경우 :
-
mappingBy는 Customer.java 및 Order.java 클래스에서 사용되지 않습니다.
고객 측에서 CUSTOMER_ID와 ORDER_ID의 매핑을 유지하는 새 테이블이 생성됩니다 [이름 = CUSTOMER_ORDER]. 이들은 고객 및 주문 테이블의 기본 키입니다. 주문 측에서 해당 Customer_ID 레코드 매핑을 저장하려면 추가 열이 필요합니다.
-
mappingBy는 Customer.java에서 사용됩니다. [문제 설명에 제공된대로] 이제 추가 테이블 [CUSTOMER_ORDER]이 작성되지 않습니다. 주문 테이블에서 하나의 열만
-
mappingby가 Order.java에서 사용됩니다. 이제 추가 테이블이 최대 절전 모드로 작성됩니다. [name = CUSTOMER_ORDER] 주문 테이블에는 맵핑을위한 추가 열 [Customer_ID]가 없습니다.
어느 쪽이든 관계의 소유자가 될 수 있습니다. 그러나 xxxToOne 쪽을 선택하는 것이 좋습니다.
코딩 효과-> 엔티티의 소유 측만 관계 상태를 변경할 수 있습니다. 아래 예제에서 BoyFriend 클래스는 관계의 소유자입니다. 여자 친구가 헤어지기를 원하더라도 할 수 없습니다.
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "BoyFriend21")
public class BoyFriend21 {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Boy_ID")
@SequenceGenerator(name = "Boy_ID", sequenceName = "Boy_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
private Integer id;
@Column(name = "BOY_NAME")
private String name;
@OneToOne(cascade = { CascadeType.ALL })
private GirlFriend21 girlFriend;
public BoyFriend21(String name) {
this.name = name;
}
public BoyFriend21() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BoyFriend21(String name, GirlFriend21 girlFriend) {
this.name = name;
this.girlFriend = girlFriend;
}
public GirlFriend21 getGirlFriend() {
return girlFriend;
}
public void setGirlFriend(GirlFriend21 girlFriend) {
this.girlFriend = girlFriend;
}
}
import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "GirlFriend21")
public class GirlFriend21 {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Girl_ID")
@SequenceGenerator(name = "Girl_ID", sequenceName = "Girl_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
private Integer id;
@Column(name = "GIRL_NAME")
private String name;
@OneToOne(cascade = {CascadeType.ALL},mappedBy = "girlFriend")
private BoyFriend21 boyFriends = new BoyFriend21();
public GirlFriend21() {
}
public GirlFriend21(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public GirlFriend21(String name, BoyFriend21 boyFriends) {
this.name = name;
this.boyFriends = boyFriends;
}
public BoyFriend21 getBoyFriends() {
return boyFriends;
}
public void setBoyFriends(BoyFriend21 boyFriends) {
this.boyFriends = boyFriends;
}
}
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.Arrays;
public class Main578_DS {
public static void main(String[] args) {
final Configuration configuration = new Configuration();
try {
configuration.configure("hibernate.cfg.xml");
} catch (HibernateException e) {
throw new RuntimeException(e);
}
final SessionFactory sessionFactory = configuration.buildSessionFactory();
final Session session = sessionFactory.openSession();
session.beginTransaction();
final BoyFriend21 clinton = new BoyFriend21("Bill Clinton");
final GirlFriend21 monica = new GirlFriend21("monica lewinsky");
clinton.setGirlFriend(monica);
session.save(clinton);
session.getTransaction().commit();
session.close();
}
}
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.List;
public class Main578_Modify {
public static void main(String[] args) {
final Configuration configuration = new Configuration();
try {
configuration.configure("hibernate.cfg.xml");
} catch (HibernateException e) {
throw new RuntimeException(e);
}
final SessionFactory sessionFactory = configuration.buildSessionFactory();
final Session session1 = sessionFactory.openSession();
session1.beginTransaction();
GirlFriend21 monica = (GirlFriend21)session1.load(GirlFriend21.class,10); // Monica lewinsky record has id 10.
BoyFriend21 boyfriend = monica.getBoyFriends();
System.out.println(boyfriend.getName()); // It will print Clinton Name
monica.setBoyFriends(null); // It will not impact relationship
session1.getTransaction().commit();
session1.close();
final Session session2 = sessionFactory.openSession();
session2.beginTransaction();
BoyFriend21 clinton = (BoyFriend21)session2.load(BoyFriend21.class,10); // Bill clinton record
GirlFriend21 girlfriend = clinton.getGirlFriend();
System.out.println(girlfriend.getName()); // It will print Monica name.
//But if Clinton[Who owns the relationship as per "mappedby" rule can break this]
clinton.setGirlFriend(null);
// Now if Monica tries to check BoyFriend Details, she will find Clinton is no more her boyFriend
session2.getTransaction().commit();
session2.close();
final Session session3 = sessionFactory.openSession();
session1.beginTransaction();
monica = (GirlFriend21)session3.load(GirlFriend21.class,10); // Monica lewinsky record has id 10.
boyfriend = monica.getBoyFriends();
System.out.println(boyfriend.getName()); // Does not print Clinton Name
session3.getTransaction().commit();
session3.close();
}
}
답변
테이블 관계와 엔티티 관계
관계형 데이터베이스 시스템에서는 세 가지 유형의 테이블 관계 만있을 수 있습니다.
- 일대 다 (외래 키 열을 통해)
- 일대일 (공유 기본 키를 통해)
- 다 대다 (2 개의 개별 부모 테이블을 참조하는 2 개의 외래 키가있는 링크 테이블을 통해)
따라서 one-to-many
테이블 관계 는 다음과 같습니다.
관계는 post_id
자식 테이블 의 외래 키 열 (예 :)을 기반으로 합니다.
따라서 one-to-many
테이블 관계 를 관리 할 때 진실의 원천이 있습니다.
이제 one-to-many
이전에 보았던 테이블 관계 에 맵핑되는 양방향 엔티티 관계를 사용하는 경우 :
위의 다이어그램을 보면이 관계를 관리하는 두 가지 방법이 있음을 알 수 있습니다.
에서 Post
엔티티, 당신은이 comments
모음 :
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
그리고 PostComment
에서 post
연결은 다음과 같이 매핑됩니다.
@ManyToOne(
fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;
따라서 엔티티 연관을 변경할 수있는 두 가지 측면이 있습니다.
comments
하위 컬렉션에 항목을 추가하면 새post_comment
행이 하위post
항목을 통해 상위 항목 과 연결되어야합니다.post_id
.- 엔터티 의
post
속성을 설정하여 열도 업데이트해야합니다.PostComment
post_id
외래 키 열을 나타내는 두 가지 방법이 있기 때문에 연결 상태 변경을 해당 외래 키 열 값 수정으로 변환 할 때 진실의 원인을 정의해야합니다.
MappedBy (일명 반대쪽)
이 mappedBy
특성은 @ManyToOne
측면이 외래 키 열 관리를 담당하고 컬렉션은 하위 엔터티를 가져오고 상위 엔터티 상태 변경을 하위 항목으로 계단식으로 만드는 데만 사용됩니다 (예 : 상위를 제거하면 하위 엔터티도 제거해야 함).
이 테이블 관계를 관리하는 자식 엔터티 속성을 참조하기 때문에 역측 이라고합니다 .
양방향 연관의 양쪽을 동기화
이제 mappedBy
속성 을 정의하고 하위 측 @ManyToOne
연결이 외래 키 열을 관리하더라도 양방향 연결의 양쪽을 동기화해야합니다.
이를 수행하는 가장 좋은 방법은 다음 두 가지 유틸리티 방법을 추가하는 것입니다.
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
addComment
및 removeComment
방법은 양측이 동기화되어 있는지 확인합니다. 따라서 자식 엔터티를 추가하면 자식 엔터티가 부모를 가리켜 야하며 부모 엔터티는 자식 컬렉션에 자식이 포함되어 있어야합니다.
모든 양방향 엔티티 연관 유형을 동기화하는 가장 좋은 방법에 대한 자세한 내용은 이 기사를 확인 하십시오 .