Bir JPA OneToOne ilişkisini nasıl tembel yapabilirim


212

Geliştirdiğimiz bu uygulamada, bir görüşün özellikle yavaş olduğunu fark ettik. Görünümü profilli ve hibernate tarafından yürütülen ve sadece getirilecek veritabanında sadece iki nesne olsa bile 10 saniye sürdü bir sorgu olduğunu fark ettim. Hepsi OneToManyve ManyToManyilişkiler tembeldi, bu yüzden sorun değildi. Gerçek SQL yürütülmekte incelerken, sorguda 80'den fazla birleşme olduğunu fark ettim.

Sorunu daha fazla inceleyerek, sorunun varlık sınıfları arasındaki derin hiyerarşi OneToOneve ManyToOneilişkilerden kaynaklandığını fark ettim . Ben de onları tembel getireceğim diye düşündüm, bu sorunu çözmeli. Ama annotating ya @OneToOne(fetch=FetchType.LAZY)ya @ManyToOne(fetch=FetchType.LAZY)işe görünmüyor. Ya bir istisna alıyorum ya da aslında bir proxy nesnesiyle değiştirilmez ve tembel olurlar.

Bunu nasıl çalıştıracağım konusunda bir fikrin var mı? persistence.xmlİlişkileri veya yapılandırma ayrıntılarını tanımlamak için kullanmadığımı unutmayın , her şey java kodunda yapılır.

Yanıtlar:


218

Öncelikle, KLE'nin cevabına bazı açıklamalar :

  1. Kısıtsız (nulllable) bire bir ilişki, bayt kodu enstrümantasyonu olmadan proxy edilemeyen tek ilişkidir. Bunun nedeni, sahibi varlığın, ilişkilendirme özelliğinin bir proxy nesnesi mi yoksa NULL içermesi mi gerektiğini GEREKMESİ GEREKİR ve normalde paylaşılan PK aracılığıyla eşleştirilen bire bir nedeniyle temel tablonun sütunlarına bakarak bunu belirleyemez. vekaletle vekalet etmek için istekli bir şekilde getirilmelidir. İşte daha ayrıntılı bir açıklama.

  2. birebir dernekler (ve açıkça birebir) bu sorundan muzdarip değildir. Sahip kuruluş kendi FK'sini kolayca kontrol edebilir (ve bire çok olması durumunda, başlangıçta boş toplama proxy'si oluşturulur ve istek üzerine doldurulur), böylece dernek tembel olabilir.

  3. Bire bir ile bire çok arasında değiştirmek, asla iyi bir fikir değildir. Bunu benzersiz bire bir ile değiştirebilirsiniz, ancak başka (muhtemelen daha iyi) seçenekler vardır.

Rob H. ancak modele bağlı olarak bunu uygulamak mümkün olmayabilir, geçerli bir noktası vardır (örneğin one-to-one dernek eğer olduğunu null).

Şimdi, orijinal soruya gelince:

A) @ManyToOne(fetch=FetchType.LAZY)iyi çalışmalıdır. Sorgunun üzerine yazılmadığından emin misiniz? join fetchHQL'de ve / veya sınıf ek açıklamalarına göre öncelikli olacak Ölçüt API'sı aracılığıyla açıkça getirme modunu belirtmek mümkündür . Durum böyle değilse ve hala sorun yaşıyorsanız, lütfen daha fazla konuşma için sınıflarınızı, sorgunuzu ve sonuçta ortaya çıkan SQL'i gönderin.

B) @OneToOnedaha hileli. Kesinlikle hükümsüz değilse, Rob H.'nin önerisine gidin ve şöyle belirtin:

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

Aksi takdirde, veritabanınızı değiştirebilirseniz (sahip tablosuna bir yabancı anahtar sütunu ekleyin), bunu yapın ve "birleştirilmiş" olarak eşleyin:

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

ve OtherEntity'de:

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

Bunu yapamıyorsanız (ve istekli getirme ile yaşayamazsanız) bytecode enstrümantasyonu tek seçeneğinizdir. 80 Varsa , ancak - CPerkins ile anlaşmak zorunda !!! istekli OneToOne dernekleri nedeniyle katılır, bundan daha büyük sorunlarınız olur :-)


Belki başka bir seçenek var, ama kişisel olarak test etmedim: kısıtlı olmayan tarafta, one-to-onea gibi bir formül kullanın select other_entity.id from other_entity where id = other_entity.id. Tabii ki, bu sorgu performansları için ideal değildir.
Frédéric

1
isteğe bağlı = yanlış, benim için çalışmıyor. @OneToOne (fetch = FetchType.LAZY, mappedBy = "fundSeries", isteğe bağlı = yanlış) özel FundSeriesDetailEntity fundSeriesDetail;
Oleg Kuts

21

Sıfırlanabilir bire bir eşlemeler üzerinde tembel yükleme yapmak için, hazırda bekletme modunun zaman enstrümanları derlemesine ve @LazyToOne(value = LazyToOneOption.NO_PROXY)bire bir ilişkiye a eklemesine izin vermeniz gerekir .

Örnek Eşleme:

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

Örnek Ant Build dosya uzantısı (Hazırda Bekletme derleme zamanı enstrümantasyonunu yapmak için):

<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
Neden LazyToOneOption.NO_PROXYolmasın LazyToOneOption.PROXY?
Telmo Marques

Bu "neden" yanıtı vermez, ancak bu gerçek burada da ("Tipik haritalama" bölümünün sonuna doğru) ileri
sürülmektedir

12

Hazırda Bekletme'de XToOnes'un arkasındaki temel fikir, çoğu durumda tembel olmamalarıdır.

Bunun bir nedeni, Hazırda Beklet'in bir proxy (id ile) veya null koymaya karar vermesi gerektiğinde , katılmak için
yine de diğer tabloya bakması gerekir. Veritabanındaki diğer tabloya erişmenin maliyeti önemlidir, bu nedenle, daha sonraki bir istekte, aynı tablo.

Düzenlendi: ayrıntılar için lütfen ChssPly76'nın cevabına bakın . Bu daha az doğru ve ayrıntılı, sunacak bir şey yok. Teşekkürler ChssPly76.


Burada yanlış olan birkaç şey var - Aşağıda bir açıklama ile başka bir cevap verdim (çok fazla şey, bir yoruma sığmayacak)
ChssPly76

8

İşte benim için çalışan bir şey (enstrümantasyon olmadan):

@OneToOneHer iki tarafta kullanmak yerine @OneToMany, ilişkinin ters kısmında (ile olan mappedBy) kullanıyorum. Bu özelliği bir koleksiyon yapar ( Listaşağıdaki örnekte), ancak alıcıdaki bir öğeye çevirerek istemcilere şeffaf hale getiriyorum.

Bu kurulum tembel bir şekilde çalışır, yani seçimler yalnızca çağrıldığında getPrevious()veya getNext()çağrıldığında yapılır - ve her çağrı için yalnızca bir seçim yapılır.

Tablo yapısı:

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

Sınıf:

@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

Bu makalede açıkladığım gibi , Bytecode Enhancement kullanmıyorsanız , üst tarafı tembel olarak getiremezsiniz@OneToOne ilişkilendirmesini .

Ancak, @MapsIdistemci tarafında kullanıyorsanız , çoğunlukla üst taraf ilişkilendirmesine bile ihtiyacınız yoktur :

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

İle @MapsId, idalt tablodaki özellik, üst tablodaki Birincil Anahtar için hem Birincil Anahtar hem de Yabancı Anahtar işlevi görür.

Bu nedenle, üst Postöğeye referansınız varsa , üst varlık tanımlayıcısını kullanarak alt öğeyi kolayca getirebilirsiniz:

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

Bu şekilde, üst taraftaki ilişkilendirmeden kaynaklanabilecek N + 1 sorgu sorunlarınız olmazmappedBy @OneToOne .


bu şekilde ebeveynlerden çocuğa kademeli operasyonlar yapamayız: /
Hamdi

Kalıcı olarak, bu sadece ekstra bir kalıcı çağrı, silmek için, DDL kaskadı usd yapabilirsiniz.
Vlad Mihalcea

6

Yerel Hazırda Bekletme XML eşlemelerinde, bunu bir kısıtlanmış özniteliği true olarak ayarlanmış bire bir eşleme . Hazırda Bekletme / JPA ek açıklamasının eşdeğer olduğundan emin değilim ve dokümanın hızlı bir şekilde araştırılması yanıt vermedi, ancak umarım bu devam etmenizi sağlar.


5
İyi bir öneri için +1; Maalesef, alan adı modeli aslında geçersizlik gerektirebileceğinden her zaman geçerli değildir. Bunu ek açıklamalarla eşleştirmenin doğru yolu@OneToOne(optional=false,fetch=FetchMode.LAZY)
ChssPly76

Bunu denedim ve hiçbir performans artışı görmedim. Hâlâ hata ayıklayıcı aracılığıyla hazırda bekletme çıktısında birçok sorgu gördüm.
P.Brian.Mackey

3

ChssPly76 tarafından zaten mükemmel şekilde açıklandığı gibi, Hibernate'in proxy'leri kısıtlanmamış (nulllable) bire bir derneklere yardımcı olmaz, ancak burada açıklanan bir hile var enstrümantasyon kurmaktan kaçınmak için . Fikir, kullanmak istediğimiz varlık sınıfının zaten araç haline getirildiğini Hazırda Bekletme'yi kandırmaktır: bunu kaynak kodunda elle düzenlersiniz. Bu kolay! Ben bytecode sağlayıcısı olarak CGLib ile uyguladım ve çalışır (HBM'nizde tembel = "no-proxy" ve fetch = "select" değil, "join" yapılandırdığınızdan emin olun).

Tembel hale getirmek istediğiniz sadece bire bir nullable ilişkiniz olduğunda , bunun gerçek (yani otomatik) enstrümantasyona iyi bir alternatif olduğunu düşünüyorum . Ana dezavantajı, çözümün kullandığınız bayt kodu sağlayıcısına bağlı olmasıdır, bu nedenle ileride bayt kodu sağlayıcısını değiştirmeniz gerekebileceğinden sınıfınızı doğru bir şekilde yorumlayın; Tabii ki, teknik bir nedenden dolayı model fasulyenizi de değiştiriyorsunuz ve bu iyi değil.


1

Bu soru oldukça eski, ancak Hibernate 5.1.10 ile daha iyi ve rahat bir çözüm var.

Tembel yükleme @OneToOne birliğinin ana tarafı dışında çalışır. Bunun nedeni, Hazırda Beklet'in bu değişkene null veya Proxy atamasını bilmenin başka bir yolu olmamasıdır. Bu makalede daha fazla ayrıntı bulabilirsiniz

  • Tembel yükleme bayt kodu geliştirmeyi etkinleştirebilirsiniz
  • Veya üst tarafı kaldırabilir ve istemci makalesini yukarıdaki makalede açıklandığı gibi @MapsId ile kullanabilirsiniz. Bu şekilde, çocuk aynı kimliği paylaştığından ebeveyn tarafına gerçekten ihtiyacınız olmadığını görürsünüz, böylece ebeveyn kimliğini bilerek çocuğu kolayca alabilirsiniz.

0

İlişkinin çift yönlü olmaması gerekiyorsa, @ElementCollection tembel One2Many koleksiyonu kullanmaktan daha kolay olabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.