티스토리 뷰
0. 양방향의 설정에서 가장 중요한 것 중 하나가 참조이다.
0-1 삭제할 때는 관계를 맺는 enitity와의 참조도 중요한 역활을 하므로 꼼꼼하게 확인해야 한다.
1. One To One 관계의 테이블 사이에 Bi-directional은 실제 관계형데이터 베이스에서는 사용되지 않는다.
1-0 즉 프로그램 상 편의로 양방향 모두 Entity를 소유하도록 하는 것이다.
1-1 지난 예제처럼 강사 테이블과 강사세부정보 테이블이 1:1의 관계를 가지고 있다고 하자.
1-1-1 강사 테이블에 instructor_detail_id라는 외래키 column이 존재한다.
1-1-2 이 컬럼 값은 Instructor_detail 테이블의 기본키 값을 참조하고 있다.
1-1-3 즉 강사테이블에서만 강사세부정보에 대한 정보를 가지고 있다.
1-1-4 강사세부정보 테이블에는 어떠한 외부 테이블에 대한 데이터를 가지고 있지 않는다.
1-1-5 저번 포스팅 Uni-Direction의 접근처럼 강사 -> 강사세부정보는 방향은 변할 수 없다.
1-1-6 데이터베이스에서 강사세부정보 테이블에서 매핑된 강사가 누구인지 알기 위해서는
1-1-6-1 강사 테이블에서 시작하여 강사세부정보 테이블을 Join하여 값을 가지고 와야 한다.
1-1-6-2 다시 말하면 방향성은 그대로 강사 -> 강사세부정보가 된다.
1-1-6-2-1 이는 당연하게도 참조정보가 강사 테이블에만 있기 때문이다.
2. 프로그램에서 생각하면 Bi-Direction의 핵심은 어떻게 접근 방향이 세부정보-> 강사인 경우를 해결하는가이다.
2-0 왜냐하면 기본적으로 강사 -> 세부정보는 당연한 정보로 인식되기 때문에 신경 쓸 필요가 없다.
2-1 ORM의 Entity에서는 데이터베이스와 달리 has 속성으로 관계를 정의한다.
2-2 세부정보 -> 강사 방향이 되러면 두 Entity의 관계에 대한 정의를 세부정보 Entity가 알아야 한다.
2-3 이 정보는 @OneToOne의 mappedBy 속성을 통해 지정된다.
2-3-2 이 정보는 관계를 가지는 Entity의 어떤 속성이 두 테이블 관계의 mapping정보를 가지고 있는지를 지정한다.
2-3-3 mappedBy로 지정된 상대 Entity 속성의 @JoinColumn 정보를 통해
2-3-4 어떤 instructor와 매핑되는지 hibernate는 알 수 있고 내부적으로 처리하여 테이블 정보를 가져온다.
3. Bi-directional 방향을 위해 강사세부정보 Entity가 매핑된 강사 테이블 정보를 가지고 있는 속성을 가져야 한다.
3-1 세부정보 -> 강사 방향의 설정이기 때문에 강사세부정보에서도 설정이 추가 되어야 한다.
3-1-1 아래의 소스에서 InstructorDetail Entity에 Instructor 속성이 추가되었다.
3-2. 강사세부정보에서 맵핑된 강사를 가져오기 위해서 @OneToOne annotation을 지정한다.
3-2-1 cascade 속성은 강사세부정보 -> 강사 방향으로도 어떤 transcation이 cascade되는지를 지정할 수 있다.
3-2-1-1 아래의 경우는 CascadeType.All로 지정되어 있다.
3-2-2 mappedBy에서 가지고 올 매핑 entity의 foreign key 속성을 명시한다.
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "instructor_detail")
@Getter
@Setter
@NoArgsConstructor
public class InstructorDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private Long id;
@Column(name = "youtube_channel")
private String youtubeChannel;
@Column
private String hobby;
// This attribute should be added to make bi-directional relationship
@OneToOne(cascade = CascadeType.ALL, mappedBy = "instructorDetail")
private Instructor instructor;
public InstructorDetail(String youtubeChannel, String hobby) {
super();
this.youtubeChannel = youtubeChannel;
this.hobby = hobby;
}
@Override
public String toString() {
return "InstructorDetail [id=" + id + ", youtubeChannel=" + youtubeChannel + ", hobby=" + hobby + "]";
}
}
4. 위에 코드에서 lombok 관련 추가 및 변경된 부분이 있다.
4-0 이 부분은 무한 재귀호출에 의한 StackOverflow Error을 피하기 위해서이다.
4-1 lombok의 @Data annotation은 @Getter, @Setter, @ToString을 모두 포함하고 있다.
4-2 Bi-directional의 경우에 관계 있는 두 Entity 모두에게 @Data를 설정하면 toString 호출에 Recusive flow가 생긴다.
4-2-1 즉 Instructor의 toString()은 Instructor 속성 중 InstructorDetail 속성의 toString을 호출하는데,
4-2-2 InstructorDetail에도 Instructor 속성이 있기 때문에 InstructorDetail의 toString() 호출 시
4-2-2-1 다시 Instructor의 toString을 호출하게 되고 끝없이 이것이 반복된다.
4-3 따라서 이 문제를 해결하기 위해 둘 중의 하나의 Entity에 @Data를 제거하고 @Getter @Setter를 별도로 명시하고
4-3-1 toString() 메소드는 재귀를 방지하기 위해 매핑 Entity를 가진 속성을 제외한 형태로 구현해야 한다.
4-4 이 부분이 이해가 안되면 lombok을 사용하지 말고 setter/getter, constructor를 모두 수동으로 생성하면 된다.
5. 테이블의 접근을 Instructor_detail -> Instructor방향으로 하여 데이터를 가져오고 삭제하는 예제이다.
5-1 아래의 코드를 보면 무한 재귀호출을 피하기 위한 코드 때문에 두 번의 toString을 각각의 Entity에서 호출했다.
5-2 강사세부정보 데이터를 가지고 와서 출력하는 예제이다.
5-2-1 이젠 강사상세정보 Entity에 강사정보가 포함되어 있다.
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import pe.pilseong.hibernate_mapping.entities.Instructor;
import pe.pilseong.hibernate_mapping.entities.InstructorDetail;
public class SaveEntityBi {
public static void main(String[] args) {
SessionFactory factory = new Configuration().configure()
.addAnnotatedClass(Instructor.class)
.addAnnotatedClass(InstructorDetail.class)
.buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
InstructorDetail instructorDetail = session.get(InstructorDetail.class, 3L);
// 관계를 가지는 두개의 Entity의 toString를 각각 호출한다.
System.out.println("Fetched InstructorDeail :: " + instructorDetail.toString());
System.out.println("Fetched Instructor :: " + instructorDetail.getInstructor().toString());
session.getTransaction().commit();
factory.close();
}
}
5-3 강사세부정보를 조회하여 삭제하는 예제
5-3-1 Entity 설정 시 cascade가 all로 설정되어 있으므로 참조된 테이블 정보도 삭제된다.
5-3-2 강사세부정보 -> 강사의 방향으로도 동일하게 처리가 가능하다.
5-3-3 아래의 코드는 try - catch - finally가 추가되어 있다.
5-3-3-1 만일 session.get에서 데이터를 가져오지 못할 경우 출력하는 부분에서 null pointer 예외가 발생한다.
5-3-3-2 그럴 경우 session이 닫히지 않아 leak이 생기는데 이를 방지하기 위한 코드를 추가하였다.
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import pe.pilseong.hibernate_mapping.entities.Instructor;
import pe.pilseong.hibernate_mapping.entities.InstructorDetail;
public class DeleteEntityBi {
public static void main(String[] args) {
SessionFactory factory = new Configuration().configure()
.addAnnotatedClass(Instructor.class)
.addAnnotatedClass(InstructorDetail.class)
.buildSessionFactory();
Session session = factory.getCurrentSession();
try {
session.beginTransaction();
InstructorDetail instructorDetail = session.get(InstructorDetail.class, 3L);
System.out.println("Fetched Instructor :: " + instructorDetail.getInstructor().toString());
session.delete(instructorDetail);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
factory.close();
}
}
}
6. 약간 예외적인 상황을 가정한다. 위와 같은 Entity설정에서
6-1 강사세부정보만 삭제하고 강사정보는 삭제하지 않으려고 한다.
6-2 Bi-directional 이지만 세부적인 cascade control이 필요한 부분이다.
6-3 삭제를 제외한 Refresh, Persist 등의 연동 기능은 그대로 유지하고 싶은 경우이다.
6-4 절차를 세부적으로 기록하면
6-4-1 여전히 중요한 것은 방향이다. 강사세부정보를 삭제할 때 강사가 삭제되지 않아야 한다.
6-4-2 강사세부정보를 delete할 경우 강사 entity는 removed state에 있는 강사정보를 참조하게 된다.
6-4-3 따라서 참조에 문제가 발생하게 되고 null pointer 예외가 발생한다.
6-4-3-1 removed state의 entity는 commit으로 확정되지 않았으므로
6-4-3-2 transaction manager가 관리하고
6-4-3-3 GC에 의해 아직 수거되지 않은 상태이다.
6-4-4 이 문제를 해결하려면 강사 entity에 강사세부정보 속성을 null로 미리 설정해 두어야 한다.
6-4-4-1 강사 entity는 현재 managed 상태에 있으므로 강사의 속성 변경은 commit 시에 반영된다.
6-4-4-2 null설정은 더 이상 참조하지 않는다는 의미이고 관련 instructor_detail 정보는 삭제되어야 하므로
6-4-4-2-1 명시적으로 instructorDetail entity를 delete 처리한다.
6-4-4-3 Entity lifesycle과 cascade를 머리속으로 잘 그려야 한다.
6-5 복잡해 보이지만 이 내용은 cascade를 재정의하면 단순하게 구현할 수 있다.
public class DeleteEntityBi {
public static void main(String[] args) {
SessionFactory factory = new Configuration().configure()
.addAnnotatedClass(Instructor.class)
.addAnnotatedClass(InstructorDetail.class)
.buildSessionFactory();
Session session = factory.getCurrentSession();
try {
session.beginTransaction();
InstructorDetail instructorDetail = session.get(InstructorDetail.class, 4L);
System.out.println("Fetched Instructor :: " + instructorDetail.getInstructor().toString());
// I have to decouple two entities. cut the reference from instructor to instructor_detail
Instructor instructor = instructorDetail.getInstructor();
instructor.setInstructorDetail(null);
session.delete(instructorDetail);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
factory.close();
}
}
}
'Spring > Hibernate' 카테고리의 다른 글
Hibernate : Lazy and Eager Loading (0) | 2020.05.07 |
---|---|
Hibernate : One-To-Many Mapping Bi-Directional (0) | 2020.05.06 |
Hibernate : One-To-One Mapping Uni-Directional(단방향) (0) | 2020.05.04 |
Hibernate : Mapping 기초 (0) | 2020.05.04 |
Hibernate : 기초적인 CRUD기능들 (0) | 2020.05.03 |
- Total
- Today
- Yesterday
- 도커 개발환경 참고
- AWS ARN 구조
- Immuability에 관한 설명
- 자바스크립트 멀티 비동기 함수 호출 참고
- WSDL 참고
- SOAP 컨슈머 참고
- MySql dump 사용법
- AWS Lambda with Addon
- NFC 드라이버 linux 설치
- electron IPC
- mifare classic 강의
- go module 관련 상세한 정보
- C 메모리 찍어보기
- C++ Addon 마이그레이션
- JAX WS Header 관련 stackoverflow
- SOAP Custom Header 설정 참고
- SOAP Custom Header
- SOAP BindingProvider
- dispatcher 사용하여 설정
- vagrant kvm으로 사용하기
- git fork, pull request to the …
- vagrant libvirt bridge network
- python, js의 async, await의 차이
- go JSON struct 생성
- Netflix Kinesis 활용 분석
- docker credential problem
- private subnet에서 outbound IP 확…
- 안드로이드 coroutine
- kotlin with, apply, also 등
- 안드로이드 초기로딩이 안되는 경우
- navigation 데이터 보내기
- 레이스 컨디션 navController
- raylib
- Spring
- login
- jsp
- RestTemplate
- Many-To-Many
- XML
- 설정
- crud
- Spring Security
- 자바
- 설정하기
- MYSQL
- Angular
- 상속
- WebMvc
- spring boot
- 스프링부트
- 스프링
- Security
- Rest
- 로그인
- form
- one-to-one
- 하이버네이트
- Validation
- mapping
- 외부파일
- one-to-many
- hibernate
- 매핑