IT story

OneToOne 관계 게으른 만들기

hot-time 2020. 5. 7. 07:59
반응형

OneToOne 관계 게으른 만들기


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

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

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


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

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

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

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

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

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

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 연결로 인해 조인하면 더 큰 문제가 있습니다 :-)


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>

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

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

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


다음은 나를 위해 작동하지 않은 것입니다 (계측없이).

Instead of using @OneToOne on both sides, I use @OneToMany in the inverse part of the relationship (the one with mappedBy). That makes the property a collection (List in the example below), but I translate it into an item in the getter, making it transparent to the clients.

This setup works lazily, that is, the selects are only made when getPrevious() or getNext() are called - and only one select for each call.

The table structure:

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`);

The class:

@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); }

}

In native Hibernate XML mappings, you can accomplish this by declaring a one-to-one mapping with the constrained attribute set to true. I am not sure what the Hibernate/JPA annotation equivalent of that is, and a quick search of the doc provided no answer, but hopefully that gives you a lead to go on.


As already perfectly explained by ChssPly76, Hibernate's proxies don't help with unconstrained (nullable) one-to-one associations, BUT there is a trick explained here to avoid to set up instrumentation. The idea is to fool Hibernate that the entity class which we want to use has been already instrumented: you instrument it manually in the source code. It's easy! I've implemented it with CGLib as bytecode provider and it works (ensure that you configure lazy="no-proxy" and fetch="select", not "join", in your HBM).

I think this is a good alternative to real (I mean automatic) instrumentation when you have just one one-to-one nullable relation that you want to make lazy. The main drawback is that the solution depends on the bytecode provider you are using, so comment your class accurately because you could have to change the bytecode provider in the future; of course, you are also modifying your model bean for a technical reason and this is not fine.


As I explained in this article, unless you are using Bytecode Enhancement, you cannot fetch lazily the parent-side @OneToOne association.

However, most often, you don't even need the parent-side association if you use @MapsId on the client side:

@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
}

With @MapsId, the id property in the child table serves as both Primary Key and Foreign Key to the parent table Primary Key.

So, if you have a reference to the parent Post entity, you can easily fetch the child entity using the parent entity identifier:

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

This way, you won't have N+1 query issues that could be caused by the mappedBy @OneToOne association on the parent side.


This question is quite old, but with Hibernate 5.1.10, there are some new better comfortable solution.

Lazy loading works except for the parent side of a @OneToOne association. This is because Hibernate has no other way of knowing whether to assign a null or a Proxy to this variable. More details you can find in this article

  • You can activate lazy loading bytecode enhancement
  • Or, you can just remove the parent side and use the client side with @MapsId as explained in the article above. This way, you will find that you don’t really need the parent side since the child shares the same id with the parent so you can easily fetch the child by knowing the parent id .

참고URL : https://stackoverflow.com/questions/1444227/making-a-onetoone-relation-lazy

반응형