티스토리 뷰

스프링프레임워크

JPA 연관 관계 정리

잔잔한 물결처럼 2026. 2. 5. 23:45

SQL 을 통해 테이블을 선언할때 

CREATE TABLE DEPARTMENT (
    DEPT_ID   NUMBER       CONSTRAINT PK_DEPARTMENT PRIMARY KEY,
    DEPT_NAME VARCHAR2(100) NOT NULL
);

CREATE TABLE EMPLOYEE (
    EMP_ID    NUMBER       CONSTRAINT PK_EMPLOYEE PRIMARY KEY,
    EMP_NAME  VARCHAR2(100) NOT NULL,
    DEPT_ID   NUMBER        REFERENCES DEPARTMENT(DEPT_ID)  -- FK 설정
);

 

이렇게 EMPLOYEE 테이블에 DEPT_ID 에 해당하는 외래키를 두어 조인을 하도록 테이블을 생성할 수가 있다.

그렇다면 JPA 에서는 이런 테이블의 연관관계를 나타낼때 어떻게 할까?

 사용자의 내용을 저장하는 엔티티와 해당 사용자의 프로필 이미지를 저장하는 엔티티가 있고 프로필 이미지를 저장하는 엔티티에 외래키를 두어서 연관관계를  나타내려고 하면
(엔티티는 JPA 에서의 테이블에 매핑되는 자바 클래스를 의미한다)

@Entity
public class Member {
    @OneToMany(mappedBy = "member", 
    		   fetch = FetchType.LAZY,
               cascade = CascadeType.ALL,
               orphanRemoval = true)
    private List<ProfileImg> profileImgs;
    
}


@Entity
public class ProfileImg {
    @ManyToOne(optional = false)
    @JoinColumn(name = "member_id") // 컬럼 이름
    private Member member;          // 필드 이름
    
}


위에 있는 하나의 사용자는 여러 사진을 가질 수 있지만 하나의 사진은 하나의 사용자만 가질수 있기 때문에 일대다 관계이다.
따라서 OneToMany 관계에서 One 에 해당하는 Member 클래스가 부모 클래스, ProfileImg 클래스가 자식 클래스라고 볼 수 있다.
반면 테이블에서 PROFILE_IMG 테이블(profileImg 엔티티) 가 외래키를 가지므로 해당하는 필드 ProfileImg.member 가 주인(owner) 에 해당한다.

그러면 위에 사용된 어노테이션을 살펴보자


mappedBy 는 외래키를 가지지 않은 쪽(주인이 아닌쪽) 에서만 사용한다. 값으로 적는 것은 반대편 엔티티의 필드 이름이다
(! 주의 반대편 테이블의 컬럼 이름이 아니다!)

fetch = FetchType.LAZY 어노테이션은 DB 에서 해당하는 데이터를 가져올때 해당하는 데이터 (위 예제에서는 member.profileImgs) 에 접근하지 않는다면 member 테이블만 조회하고 해당하는 필드에는 실제 인스턴스가 아니라 프록시 객체가 들어간다(프록시 객체는 가짜 객체를 의미한다) 
fetch = FetchType.LAZY 어노테이션은 DB 의 과도한 접근을 막기 위해 사용하는 설정이다 그러나 주의해야할 점이 있는데
만약에 트랜잭션 중에 한번도 해당 어노테이션이 선언된 객체에 접근하지 않았을 경우 해당 객체에는 프록시 값이 들어있고 트랜잭션이 끝났을때 해당 객체에 접근을 하면 그 때는 프록시 값이 필요한 인스턴스 값으로 대체되지 않는다.

@Transactional
public Member getMember(Long id) {
    Member member = memberRepository.findById(id).orElseThrow();
    return member; // 여기까지는 트랜잭션 안
}

// 트랜잭션을 마치고 나서 해당하는 객체에 접근했을때
// 해당하는 인스턴스를 DB 에서 찾아서 넣어줄 수 없다.
Member member = memberService.getMember(id);
member.getProfileImgs.getName(); // 여기서 LAZY 초기화 시도

 

그래서 fetch = FetchType.LAZY 어노테이션이 선언되어 있는 객체를 쓰려면 반드시 트랜잭션 중간에 명시적으로 접근을 해야한다.

이러한 오류를 피하고 싶다면

fetch = FetchType.EAGER 어노테이션을 적용할 수있다 이 어노테이션이 선언되어 있는 필드는 DB 트랜잭션시 접근여부와 상관없이 데이터를 가져온다.

 

@Entity
public class Member {
    @OneToMany(mappedBy = "member",
               cascade = CascadeType.ALL,
               orphanRemoval = true,
               fetch = FetchType.EAGER)
    private List<ProfileImg> profileImgs = new ArrayList<>();
}



fetch = FetchType.EAGER 어노테이션이 선언되어 있으면 해당 인스턴스를 멤버 인스턴스를 생성할때 동시에 생성된다.

 


cascade = CascadeType.ALL 이 어노테이션은 해당하는 값이 DB 에서 삭제될때 연관관 값들도 같이 삭제되도록 설정하는 어노테이션이다. 주로 OneToMany 에서 One 에 해당하는 클래스에서 이 어노테이션을 선언한다. 

 

 

orphanRemoval = true 어노테이션은 부모와 연관이 끊긴 자식을 자동으로 삭제하도록 하는 어노테이션이다.

부모 컬렉션에서 자식을 제거하고, 그 자식의 부모 참조도 끊으면 DB 에서 DELETE 가 발생한다.

 

optional 어노테이션은 외래키에 null 을 허용할지 말지를 선택할 수 있는 어노테이션이다.

optional = false 인 경우에는 외래키에 null 이 올수 없기 때문에 반드시 부모가 있어야 해당 값을 생성할 수 있게 된다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함