Bi-Directional
기존 Uni-Directional 에서는 Instructor 를 load 해야만 그에 대한 InstructorDetail 을 가져올 수
있었다.

그러나 Bi-Directional relationship 을 사용하면 InstructorDetail 을 load 함으로써 이와 associated 된 Instructor 를 get 할 수 있다.
Uni-Directional 에서 Bi-Directional 로 바꾸는 것은 단순히 Java code 만 수정하면 된다. 기존의 DB schema 를 수정할 필요도 없다.
Bi-Directional One-to-One Mapping 의 Development Process 는 다음과 같다.
- Make updates to
InstructorDetailclass- Add new feild to reference
Instructorwith@OneToOneannotation- Add getter/setter methods for
Instructor- Create Main App
1.1. Add new feild to reference Instructor with @OneToOne annotation
@OneToOne(mappedBy = "instructorDetail", cascade = CascadeType.ALL)
private Instructor instructor;@OneToOne annotation 을 통하여 위와 같이 field 를 설정하면 된다. 또한 mapping 된 다른 entity class 의 어떤 field 와 연관이 되어있는지를 mappedBy 를 통하여 설정할 수 있다. 여기서는 Instructor class 의 instructorDetail field 와 연관된 것이다.
1.2. Add getter/setter methods for Instructor
이후에 instructor 의 getter 와 setter 를 추가한다. 이때, toString() method 에는 추가된 instructor field 에 대한 내용을 추가하면 안 된다.
만약 디버깅이나 기타 출력을 위하여 entity class object 자체를 출력하는 상황에서, owning side(FK 를 보유한 측, 주인) 와 inverse side(비주인) 모두 서로의 object 가 모두 각각의 toString() method 에 포함되어 있다면 Infinite Recursion(무한 재귀 호출) 또는 Circular Reference(순환 참조) 문제가 발생하게 된다.
따라서 실제 FK 를 보유한 owning side 에서만 toString() 에 추가하는 것이 바람직한 방법인 것 같다.
2. Create Main App
private void findInstructorDetail(AppDAO appDAO) {
int theId = 2;
InstructorDetail tempInstructorDetail = appDAO.findInstructorDetailById(theId);
System.out.println("tempInstructorDetail: " + tempInstructorDetail);
System.out.println("the associated instructor: " + tempInstructorDetail.getInstructor());
System.out.println("Done! :)");
}이번에는 Bi-Directional One-To-One mapping 을 test 하기 위하여 위와 같이 InstructorDetail 을 통하여 Instructor data 를 읽어오도록 code 를 작성한다.

결과적으로 위와 같이 성공적으로 Instructor object 를 성공적으로 가져온 것을 볼 수 있다.
More on mappedBy

mappedBy 의 동작 방식에 대하여 자세하게 알아보자.
mappedBy 를 사용하게 되면, Hibernate 는 다음과 같이 동작한다.
InstructorDetailclass 가 연관관계의 주인이 아니라는 걸 인식mappedBy = "instructorDetail"라고 했으니까,Instructorclass 안에서instructorDetail라는 field 를 검색- 해당 field 에는
@JoinColumn(name = "instructor_detail_id")가 붙어있기 때문에, Hibernate는 “아, 실제 FK 는instructor테이블에 있구나”라고 판단- 따라서 Hibernate는
InstructorDetail테이블에는 아무 외래 키도 만들지 않음, 단순히 mapping 만 참조
결과적으로 mappedBy 는 Hibernate 에게 “FK 는 이쪽말고 저쪽에 있어, 난 그냥 참고만 하는거야”라고 알려주는 역할이고, Hibernate 는 주인 쪽 enitity 에서 FK 정보(@JoinColumn)를 찾아 사용한다.
One-to-One Mapping Bi-Directional - Test (Delete)
이번에는 Delete 를 통하여 Bi-Directional 를 test 해보자.
@Override
@Transactional
public void deleteInstructorDetailById(int theId) {
InstructorDetail tempInstructorDetail = entityManager.find(InstructorDetail.class, theId);
entityManager.remove(tempInstructorDetail);
}위와 같이 AppDAOImpl 에 method 를 추가하고,
private void deleteInstrcutorDetail(AppDAO appDAO) {
int theId = 2;
System.out.println("Deleting instructor detail id: " + theId);
appDAO.deleteInstructorDetailById(theId);
System.out.println("Done!");
}Main App 에서도 method 를 추가하여 test 해보자.
위에서 InstructorDetail entity class 의 Instructor field 에도 Cascading option 을 ALL 로 걸어주었으니, InstructorDetail 을 delete 하면 그 작업이 Instructor 에도 흘러가 모두 delete 이 될 것이다. Main App 을 실행하여 결과를 확인하자.

성공적으로 실행된 것을 볼 수 있다.
One-to-One Mapping Bi-Directional - Test (Only delete Detail)
그럼 One-to-One Mapping Bi-Direction 의 상황에서, 오직 InstructorDetail 만 삭제하고 싶을 때에는 어떻게 해야 할까?
우선 말로 설명해보면 다음과 같다. 먼저 InstructorDetail 의 instructor field 에 설정한 Cascading option 을 조정해야 한다. 아래와 같이 ALL 에서 CascadeType.REMOVE 만 빼고 전부 다 넣어주면 될 것이다.
@OneToOne(mappedBy = "instructorDetail", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
private Instructor instructor;그러나 아직 Instructor object 의 instructorDetail field 가 InstructorDetail 의 id field 를 참조하고 있기 때문에 InstructorDetail object 를 먼저 delete 할 경우,

위와 같이 다른 exception 을 발생시키지는 않으나 Hibernate 가 SELECT query 만 실행할 뿐, DELETE query 는 내부적으로 실행조차 하지 않는다.
이유는 JPA 의 Persistence Context 의 관리 방식과 연관관계의 소유권 개념 때문이다.
아무리 Bi-Directional 이라고 하더라도 mappedBy 로 관리되는 쪽은 Non-Owning Side 이며, 이는 단순히 연관관계에서 Read-Only View 일 뿐이며, 변경 사항이 DB 에 직접 반영되지 않는다.
따라서 InstructorDetail 만 delete 하기 위하여는 해당 object 를 완전한 독립적 object 로 놔두어야 한다. 즉, 다른 object 로부터 참조를 받는 상태이면 안 된다.
@Override
@Transactional
public void deleteInstructorDetailById(int theId) {
...
tempInstructorDetail.getInstructor().setInstructorDetail(null);
...
}그렇다면 위와 같이 InstructorDetail 에 associate 된 Instructor 의 참조를 강제로 null 로 바꾸어 Bi-Directional 관계를 끊어야 한다. 이렇게 하고 다시 test 해보면,

Hibernate 가 null 로 바꾸는 query 를 실행하고, 아래에 DELETE query 도 정상적으로 보낸 것을 확인할 수 있다.


실제 DB 에서도 Instructor 은 유지되어 있는 것을 볼 수 있다.