spring/JPA

@OneToOne 관계에서 지연로딩(LAZY)이 안되는 문제

ri5 2022. 9. 18. 19:22

JPA를 공부하다가 1:1 관계에서는 FetchType.LAZY 를 사용해도 지연 로딩이 되지 않고 바로 객체를 불러온다고 한다. 책에 있는 내용을 통해 해당 링크를 찾아봤지만 어떠한 원리로 일어나고 해결방법은 어떤 것이 있는지 알고 있어야 실무에 실제로 사용할 때 주의할 수 있을 것 같다.

발생 원인

class A {
    private Set bees;
    public Set getBees() {
        return bees;
    }

    public void setBees(Set bees) {
        this.bees = bees;
    }

}

class B {
    // Not important really
}

보통 하이버네이트에서 프록시객체를 생성할 때 위와 같은 클래스 구조가 있고 하이버네이트가 Class A를 호출하게 되면 일단 초기화 되지 않은 Set wrapper로 감싸고 해당 a 객체에 "bess" a.setBess(wrapper) 형태로 있다가 a.getBess()로 호출하게 되면 이객체를 반환 합니다.  이 객체는 null이 아니라 데이터베이스에서 아직 로드되지 않은 형태입니다. a.getBess().size() 같은 의미있는 메소드가 호출 될 때 데이터베이스에서 해당 객체를 로딩하고 초기화 합니다.

 

이제 일대일 관계로 매핑된 객체를 보면

class B {
    private C cee;

    public C getCee() {
        return cee;
    }

    public void setCee(C cee) {
        this.cee = cee;
    }
}

class C {
    // Not important really
}

우리는 B class로 생성 된 객체를 로드한 후에 getCee()를 호출한다면 하이버네이트는 바로 Cee라는 객체를 바로 리턴해야합니다. 래퍼로 따로 감쌀 수 없는 형태이기 때문에 cee를 호출한 순간 리턴할 수 있도록 적절한 값을 넣어야 합니다. 

class B {
    @OneToOne(fetch=FetchType.LAZY)
    private C cee;

    public C getCee() {
        return cee;
    }

    public void setCee(C cee) {
        this.cee = cee;
    }
}

그래서 하이버네이트는 이처럼 LAZY를 설정하여 지연로딩을 설정한다면 프록시 객체를 통해 사용할 수 있습니다. 위에 Set처럼 래퍼로 감싸고 get이나 set같은 메소드를 호출했을 때 데이터를 불러오는 방식으로 호출할 수 있다. 이렇게 사용했을 때 문제는 외래키를 가지고 있지 않은 테이블과 매핑된 Entity에서 호출했을 때 문제가 발생한다.

public class Member {
	
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne(mappedB="member")
    private Locker locker
}

public class Locker {
	
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne
    @JoinColumn("MEMBER_ID")
    private Member member;
}

이와같은 구조로 Locker클래스가 연관관계 주인인 형태로 Member.locker라는 객체를 get을 통해 객체를 호출 했을 때 어떤 FK에 매핑된 데이터인지 알 수 없습니다. 결국 SELECT 문을 통해 모든 데이터를 확인해봐야하는데 이것은 너무 비효율적으로 동작하기 때문에 하이버네이트는 LAZY가 아닌 EAGER형태로 동작하게 되는 것 입니다.

 

해결 방법

@ManyToOne 관계로 풀어낸다.

아래와 같이 풀어냈을 경우는 lokers에 직접 주입해서 사용해야 되기 때문에 즉시로딩으로 생길 수 있는 문제점을 해결할 수 있다.

public class Member {
	
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToMany
    private List<Locker> lockers
}

public class Locker {
	
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @ManyToOne
    @JoinColumn("MEMBER_ID")
    private Member member;
}

@OneToOne옵션을 optional=false로 설정한다.

연관 관계가 선택 사항인 경우 Hibernate는 쿼리를 실행시키지 않고는 해당 객체에 대한 주소가 존재하는지 알 수 있는 방법이 없습니다. 따라서 객체 참조하는 주소가 없을 수 있고 있을 수 있기 때문에 주소 필드를 프록시로 채울 수 없습니다.

하지만 null을 연결을 필수(예: optional=false)로 만들면 연결이 필수이므로 객체를 신뢰하고 주소가 존재한다고 가정합니다. 따라서 사람을 참조하는 주소가 있다는 것을 알고 주소 필드를 프록시로 직접 채웁니다. 사실상 EAGER와 같은 형태로 동작해야한다.

public class Member {
	
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne(optional=false)
    private Locker lockers
}

public class Locker {
	
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne
    @JoinColumn("MEMBER_ID")
    private Member member;
}

@MapsId로 FK를 PK로 설정한다.

MapsId어노테이션은 FK를 PK로 설정하게 도와주는 어노테이션이다. LOCKER 테이블의 PK를 MEMBER.ID로 사용하게 되면서 연결된 FK값을 알고 있기 때문에 문제를 해결할 수 있다. 단점은 1 : N 구조로 변경되었을 때 데이터베이스를 마이그레이션하는 작업을 해야되기 때문에 신중하게 설계를 해야한다.

public class Member {
	
    @Id
    private Long id;
    
    private String name;
    
    @OneToOne
    private Locker locker
}

public class Locker {
	
    @Id
    private Long id;
    
    private String name;
    
    @MapId
    @OneToOne
    private Member member;
}

 

참조

https://stackoverflow.com/questions/17987638/hibernate-one-to-one-lazy-loading-optional-false

 

Hibernate: one-to-one lazy loading, optional = false

I faced the problem that one-to-one lazy loading doesn't work in hibernate. I've already solved it, but still don't properly understand what happens. My code (lazy loading doesn't work here, when I

stackoverflow.com

https://developer.jboss.org/docs/DOC-13960

 

Some explanations on lazy loading (one-to-one)| JBoss.org Content Archive (Read Only)

Are you sure you want to delete this article?

developer.jboss.org

https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/