JPA OneToOne 관계를 게으르게 만드는 방법


212

우리가 개발하고있는이 응용에서, 우리는 특히 느리다는 것을 알았습니다. 뷰를 프로파일 링하고 데이터베이스에 가져올 객체가 두 개 뿐인 경우에도 10 초가 걸리는 최대 절전 모드에서 실행 된 쿼리가 하나 있음을 알았습니다. 모든 OneToManyManyToMany그 문제가 아니었다 그래서 관계는 게으른했다. 실행중인 실제 SQL을 검사 할 때 쿼리에 80 개가 넘는 조인이 있음을 알았습니다.

문제를 더 조사한 결과, 문제는 엔터티 클래스 의 깊은 계층 구조 OneToOneManyToOne관계 로 인해 발생한 것으로 나타났습니다 . 그래서, 나는 그것들을 게으르게 가져 와서 문제를 해결할 것이라고 생각했습니다. 그러나 주석 중 하나 @OneToOne(fetch=FetchType.LAZY)또는 @ManyToOne(fetch=FetchType.LAZY)작동하지 않습니다. 예외가 발생하거나 실제로 프록시 객체로 바뀌지 않아서 게으르지 않습니다.

내가 어떻게 작동하게 할 아이디어가 있습니까? persistence.xml관계 또는 구성 세부 정보를 정의 하기 위해 를 사용하지는 않습니다 . 모든 것이 Java 코드로 수행됩니다.

답변:


218

먼저 KLE 의 답변에 대한 설명이 있습니다 .

  1. 제한되지 않은 (널 (null)) 일대일 연관은 바이트 코드 계측없이 프록시 할 수없는 유일한 연관입니다. 그 이유는 소유자 엔티티가 연관 특성에 프록시 오브젝트 또는 NULL을 포함해야하는지 알아야하며 일대일로 공유 PK를 통해 맵핑되므로 기본 테이블의 열을보고이를 판별 할 수 없기 때문입니다. 어쨌든 프록시를 무의미하게 만들기 위해 열심히 가져와야합니다. 자세한 설명 은 다음과 같습니다 .

  2. 다 대일 협회 (및 일대 다 협회)는이 문제를 겪지 않습니다. 소유자 엔티티는 자체 FK를 쉽게 확인할 수 있으며 (일대 다의 경우 비어있는 콜렉션 프록시가 처음에 작성되어 필요할 때 채워짐) 연관성이 게으를 수 있습니다.

  3. 일대일을 일대 다로 바꾸는 것은 좋은 생각이 아닙니다. 고유 한 다대 일로 대체 할 수 있지만 다른 (아마도 더 나은) 옵션이 있습니다.

Rob H. 는 유효한 포인트를 가지고 있지만 모델에 따라 구현하지 못할 수도 있습니다 (예 : 일대일 연결 null 인 경우).

이제 원래 질문에 이르기까지 :

A) @ManyToOne(fetch=FetchType.LAZY)잘 작동합니다. 쿼리 자체에서 덮어 쓰지 않습니까? join fetchHQL 에서 지정 하거나 클래스 주석보다 우선하는 Criteria API를 통해 명시 적으로 페치 모드를 설정할 수 있습니다. 그렇지 않은 경우에도 여전히 문제가 발생하면 더 많은 대화를 위해 클래스, 쿼리 및 결과 SQL을 게시하십시오.

B) @OneToOne더 까다 롭다. 확실히 널 입력 가능하지 않은 경우 Rob H.의 제안에 따라 다음과 같이 지정하십시오.

@OneToOne(optional = false, fetch = FetchType.LAZY)

그렇지 않으면 데이터베이스를 변경할 수있는 경우 (소유자 테이블에 외래 키 열 추가)이를 변경하고 "joined"로 매핑하십시오.

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

그리고 OtherEntity에서 :

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

그렇게 할 수없고 (열심히 가져 와서 살 수 없다면) 바이트 코드 계측이 유일한 옵션입니다. 그러나 CPerkins 에 동의 해야합니다 -80이 있다면 ! 열망하는 OneToOne 연결로 인해 조인하면 더 큰 문제가 있습니다.


어쩌면 다른 옵션이있을 수도 있지만 개인적으로 테스트하지는 않았습니다. 제한되지 않은 측면에서 one-to-one와 같은 수식을 사용하십시오 select other_entity.id from other_entity where id = other_entity.id. 물론 이것은 쿼리 성능에 이상적이지 않습니다.
프레데릭

1
선택 사항 = false, 나를 위해 작동하지 않습니다. @OneToOne (fetch = FetchType.LAZY, mappingBy = "fundSeries", 선택 사항 = false) private FundSeriesDetailEntity fundSeriesDetail;
Oleg Kuts

21

nullable 일대일 매핑에서 지연로드 작업을 수행하려면 최대 절전 모드에서 컴파일 타임 계측을 수행 하고 @LazyToOne(value = LazyToOneOption.NO_PROXY)일대일 관계에을 추가 해야합니다.

매핑 예 :

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

Ant 빌드 파일 확장자 예제 (Hibernate 컴파일 타임 인스 트루먼 테이션 수행) :

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>

3
LazyToOneOption.NO_PROXY그렇지 LazyToOneOption.PROXY않습니까?
Telmo Marques

이것은 "왜"대답하지 않지만,이 사실은 (이하 "일반 매핑"섹션의 끝 부분)도 여기 주장한다 : vladmihalcea.com/...
DanielM

12

Hibernate에서 XToOne을 만드는 기본 아이디어는 대부분의 경우 게으르지 않다는 것입니다.

한 가지 이유는, Hibernate가 프록시 (id를 갖는) 또는 널 (null)을 놓기로 결정할 때
어쨌든 다른 테이블을 조사 하여 조인해야하기 때문이다. 데이터베이스의 다른 테이블에 액세스하는 비용은 상당하므로 나중에 요청에서 해당 테이블에 대한 두 번째 액세스가 필요한 데이터를 가져 오는 대신 해당 순간에 해당 테이블의 데이터를 가져 오는 것 (비 지연 동작) 같은 테이블.

편집 : 자세한 내용은 ChssPly76의 답변을 참조하십시오 . 이것은 덜 정확하고 상세하며 제공 할 것이 없습니다. 감사합니다 ChssPly76.


몇 가지 잘못된 여기에있다 - 나는 설명과 함께 아래에 다른 대답을 제공 한 (너무 많은 물건을 댓글로 적합하지 않습니다)
ChssPly76

8

나를 위해 (계측없이) 일한 것이 있습니다.

@OneToOne양쪽에서 사용 하는 대신 @OneToMany관계의 반대 부분 (와의 관계 mappedBy)을 사용합니다. 속성을 컬렉션으로 List만들지 만 (아래 예에서) 게터의 항목으로 변환하여 클라이언트에게 투명하게 만듭니다.

이 설정은 느리게 작동합니다. 즉, 선택은 getPrevious()또는 getNext()호출 될 때만 수행 되며 각 호출에 대해 하나만 선택됩니다.

테이블 구조 :

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

클래스:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}

7

이 기사 에서 설명했듯이 Bytecode Enhancement를 사용하지 않으면 부모 측 @OneToOne연결을 느리게 가져올 수 없습니다 .

그러나 대부분 @MapsId클라이언트 측에서 사용 하는 경우 부모 측 연결이 필요하지 않습니다 .

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

을 사용 하면 자식 테이블 @MapsIdid속성이 부모 테이블 기본 키의 기본 키 및 외래 키 역할을합니다.

따라서 부모 Post엔터티에 대한 참조가 있으면 부모 엔터티 식별자를 사용하여 자식 엔터티를 쉽게 가져올 수 있습니다.

PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);

이렇게 하면 부모 쪽 의 연결 로 인해 발생할 수있는 N + 1 쿼리 문제 가 발생 하지 않습니다 mappedBy @OneToOne.


이런 식으로 우리는 더 이상 부모에서 자식으로 계단식 작업을 수행 할 수 없습니다 : /
Hamdi

지속의 경우 추가 지속 호출 일 뿐이므로 삭제를 위해 DDL 캐스케이드를 사용할 수 있습니다.
Vlad Mihalcea

6

네이티브 Hibernate XML 매핑에서, 제한된 속성을 true로 설정하여 일대일 매핑 선언함으로써이를 달성 할 수 있습니다 . 나는 Hibernate / JPA 주석이 그에 상응하는 것이 무엇인지 확실하지 않으며, 문서를 빠르게 검색하면 아무런 대답도 얻지 못했지만 계속해서 리드 할 수 있기를 바랍니다.


5
좋은 제안은 +1; 불행히도 도메인 모델이 실제로 null을 요구할 수 있기 때문에 항상 적용 가능한 것은 아닙니다. 주석을 통해이지도하는 올바른 방법입니다@OneToOne(optional=false,fetch=FetchMode.LAZY)
ChssPly76

나는 이것을 시도했지만 성능 향상을 보지 못했습니다. 나는 여전히 디버거를 통해 최대 절전 모드 출력에서 ​​많은 쿼리를 보았습니다.
P.Brian.Mackey

3

ChssPly76에 의해 이미 완벽하게 설명 된 것처럼, Hibernate의 프록시는 제한되지 않은 (널 (null)) 일대일 연관에 도움이되지 않지만, 인스 트루먼 테이션 설정을 피하기 위해 여기 에 설명 된 요령이 있습니다 . 아이디어는 우리가 사용하고자하는 엔티티 클래스가 이미 계측되었다는 Hibernate를 속이는 것입니다. 소스 코드에서 수동으로 계측합니다. 그것은 간단합니다! 바이트 코드 공급자로 CGLib을 사용하여 구현했으며 작동합니다 (HBM에서 "join"이 아닌 lazy = "no-proxy"및 fetch = "select"를 구성하십시오).

나는 이것이 당신이 게으르고 싶어하는 일대일 nullable 관계가있을 때 실제 (자동 의미) 계측 의 좋은 대안이라고 생각합니다 . 주요 단점은 솔루션이 사용중인 바이트 코드 공급자에 따라 달라 지므로 나중에 바이트 코드 공급자를 변경해야 할 수 있으므로 클래스를 정확하게 주석 처리하십시오. 물론 기술적 인 이유로 모델 빈을 수정하고 있지만 이것은 좋지 않습니다.


1

이 질문은 꽤 오래되었지만 최대 절전 모드 5.1.10에는 더 나은 새롭고 편안한 솔루션이 있습니다.

@OneToOne 연결의 부모 쪽을 제외하고 지연 로딩이 작동합니다. 이것은 Hibernate가이 변수에 널 (null) 또는 프록시를 할당 할 지 알 수있는 다른 방법이 없기 때문입니다. 이 기사 에서 더 자세한 내용을 볼 수 있습니다

  • 지연 로딩 바이트 코드 향상을 활성화 할 수 있습니다
  • 또는 위 기사에서 설명한 것처럼 부모 측을 제거하고 @MapsId와 함께 클라이언트 측을 사용할 수 있습니다. 이렇게하면 자식이 부모와 동일한 ID를 공유하므로 부모 쪽을 알 필요가 없으므로 자식을 쉽게 가져올 수 있으므로 부모 쪽이 실제로 필요하지 않습니다.

0

관계가 양방향이 아니어야하는 경우 @ElementCollection이 게으른 One2Many 컬렉션을 사용하는 것보다 쉬울 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.