병원
과 병원 이미지
두 엔티티의 연관관계를 통해서 양방향, 단방향, 연관관계의 주인에 대해서 알아볼 예정이다.테이블
의 연관관계병원의 PK
를 병원 이미지에서 FK
로 사용하여 연관관계를 나타냄객체(엔티티)
의 연관관계컬럼
이 Long타입으로 병원의 @Id
컬럼을 가질 수도 있지만, 이는 객체지향적이지 않다!!참조주소
, 해당 엔티티를 컬럼으로 가져야한다.병원 이미지
측에서는 참조주소로 연관관계가 이어져 있으므로, 참조 주소를 안다고 해서 병원
을 조회할 수 있지는 않다. FK와의 차이가 분명하다.
사실 모든 엔티티의 연관관계는 단방향이다.
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Hospital extends TimeStamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long hosId;
private String hosName;
private String hosPhone;
@Enumerated(EnumType.STRING)
private HosStatus hosStatus;
@Enumerated(EnumType.STRING)
private HosBooking hosBooking; // 예약 가능 상태를 나타내는 값
@Embedded
private Address hosAddress;
private String hosOpenhour;
}
// TimeStamped는 등록일, 수정일 관련 공통필드
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class HosImg extends TimeStamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long himId;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "hosId") //hosImg 테이블의 컬럼명
private Hospital hospital; //DB의 FK
private String himPath;
private boolean himMain;
private String himOrigin;
}
// fetch = LAZY, 엔티티 생성, 수정 방법은 추후에 알아보도록 하자
FK가 있는 쪽
에 연관관계를 지어준다.해당 프로젝트에선 엔티티에 setter를 제거해서 생성 메소드나, 변경하는 메소드를 사용하지만, 이 글에서는 setter를 쓰겠다.
tx.begin();
// 병원 엔티티 생성
Hospital hospital = new Hospital();
hospital.setHosName("test병원");
hospital.setHosPhone("010-1234-1234");
// 병원 엔티티 영속 상태
em.persist(hospital);
// 병원 이미지 엔티티 생성
HosImg hosImg = new HosImg();
hosImg.setPath("이미지 파일 경로");
// 연관관계 설정
hosImg.setHospital(hospital);
// 병원 이미지 엔티티 영속 상태
em.persist(hoImg);
// 트랜젝션 커밋 -> flush() -> INSERT 병원, 병원이미지
tx.commit();
// 비지니스 로직 상, 사진 한장을 조회하는 경우는 없을 것이지만, 일단 설명을 위한 코딩이다
// 1. 사진을 find해오는 과정
HosImg findHosImg = em.find(HosImg.class, 1L);
// 2. 사진을 통해 그래프 탐색으로 참조된 병원을 가져오는 과정
Hospital findHospital = findHosImg.getHospital();
지연로딩
, .getHospital() 2번 과정에서 병원을 찾는 SQL문이 따로 나간다.즉시로딩
, .find() 1번 과정에서 병원과 이미지를 JOIN하는 쿼리문을 통해 두 엔티티 정보를 다 가져온다. (N+1)문제
가 생길 수 있음, 이는 나중에 심도있게 공부해보자// 1. 사진을 find해오는 과정
HosImg findHosImg = em.find(HosImg.class, 1L);
log.info("병원 : {}", findHosImg.getHospital().getName()); // 병원 : test병원
// 2. 새로운 병원
Hospital hospital2 = new Hospital();
hospital2.setHosName("test병원22");
hospital2.setHosPhone("010-5678-5678");
em.persist(hospital2);
// 3. 병원 수정
findHosImg.setHospital(hospital2);
log.info("병원 : {}", findHosImg.getHospital().getName()); // 병원 : test병원22 > flush()되면 UPDATE문 날라감
병원 이미지
의 병원
컬럼의 참조를 바꾸면, 변경감지
(링크
)에 의해서, flush()될 때 UPDATE 문
날라감병원
객체의 참조값
을 안다고 해서, 그걸 참조하는 병원 이미지
를 알 수 있는 것은 아니다.병원
→ 병원 이미지
를 조회할 수 있는 단방향 연관관계를 하나 더 추가해 양방향 연관관계를 만드는 것이다.@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Hospital extends TimeStamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long hosId;
private String hosName;
//...
@OneToMany(mappedBy = "hospital");
List<HosImg> hosImgs = new ArrayList<>();
}
@OneToMany
의 연관관계 추가단방향 관계를 2개
만들어 양쪽에서 서로를 관계지어주는 것 뿐이다.new ArrayList<>();
를 미리 초기화해두는 것이 관례 (NullPointException 방지)// 병원 조회
Hospital findHospital = em.find(Hospital.class, 1L);
// 병원을 통한 병원 이미지 그래프 탐색
List<HosImg> findHosImgs = findHospital.getHosImgs();
for (HosImg hosImg : findHosImgs) {
log.info(hosImg.getHimPath());
}
연관관계의 주인
이라 한다.연관관계의 주인은 외래키가 있는 쪽으로 한다.
조회
를 하기 위한 연관관계이므로 주인키가 될 수 없다.아닌 쪽
에서 속성을 넣어주면 된다.tx.begin();
// 병원 엔티티 생성
Hospital hospital = new Hospital();
hospital.setHosName("test병원");
hospital.setHosPhone("010-1234-1234");
// 병원 엔티티 영속 상태
em.persist(hospital);
// 병원 이미지 엔티티 생성
HosImg hosImg = new HosImg();
hosImg.setPath("이미지 파일 경로");
// 연관관계 설정
hosImg.setHospital(hospital);
// 병원 이미지 엔티티 영속 상태
em.persist(hoImg);
// 트랜젝션 커밋 -> flush() -> INSERT 병원, 병원이미지
tx.commit();
따라서, 양쪽에 둘다 값을 설정해주는 것이 좋다.
@Entity
public class Hospital extends TimeStamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long hosId;
private String hosName;
//...
@OneToMany(mappedBy = "hospital");
List<HosImg> hosImgs = new ArrayList<>();
//편의 메소드
public addHosImg(HosImg hosImg) {
// 병원 이미지에 연관관계를 설정
hosImg.setHospital(this);
// 병원에 병원 이미지(역방향)을 추가
this.hosImgs.add(hosImg);
}
}
tx.begin();
// 병원 엔티티 생성
Hospital hospital = new Hospital();
hospital.setHosName("test병원");
hospital.setHosPhone("010-1234-1234");
// 병원 엔티티 영속 상태
em.persist(hospital);
// 병원 이미지 엔티티 생성
HosImg hosImg = new HosImg();
hosImg.setPath("이미지 파일 경로");
// 연관관계 주인에 대해서 설정을 하지 않고
// hosImg.setHospital(hospital);
// 역방향으로만 연관관계를 지어주는 척하면 이건 FK설정이 되지 않는다.
// 이렇게되면 hosImg테이블에 FK 는 null이 된다.
hospital.getHosImgs.add(hosImg);
// 병원 이미지 엔티티 영속 상태
em.persist(hoImg);
// 트랜젝션 커밋 -> flush() -> INSERT 병원, 병원이미지
tx.commit();
양방향 연관관계에서 Lombok에서 제공하는 @ToString 같은 경우 무한 루프가 일어날 수 있다.
hospital.java
@Override public String toString() {
return "..." + this.hosImgs
//이 과정에서 HosImg의 toString을 호출한 것이다.
}
@Override public String toString() {
return "..." + this.hospital
//이 과정에서 Hospital의 toString을 호출한 것이다.
}
단방향만으로도 연관관계 매핑 자체는 완성이다.
회원
-주문
과 같은 연관관계에서는 회원 엔티티에 List<Order>
를 가지고 있어야되는 것은 잘 생각해볼 문제다.