JPA kalıtım @EntityGraph isteğe bağlı alt sınıf ilişkilendirmeleri içerir


12

Aşağıdaki etki alanı modeli göz önüne alındığında, ben Answerkendi Values ve alt alt çocukları da dahil olmak üzere tüm s yüklemek ve AnswerDTOsonra bir JSON dönüştürmek için bir koymak istiyorum . Çalışan bir çözümüm var ama bir ad-hoc kullanarak kurtulmak istediğim N + 1 probleminden muzdarip @EntityGraph. Tüm ilişkilendirmeler yapılandırıldı LAZY.

resim açıklamasını buraya girin

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

Yöntem @EntityGraphüzerinde bir ad-hoc kullanarak ilişkide RepositoryN + 1 önlemek için değerlerin önceden getirildiğinden emin olabilirim Answer->Value. Sonucum iyi olsa da, temassız yükleme nedeniyle s selectedilişkisini başka bir N + 1 sorunu var MCValue.

Bunu kullanarak

@EntityGraph(attributePaths = {"value.selected"})

başarısız olur, çünkü selectedalan elbette bazı Valuevarlıkların sadece bir parçasıdır :

Unable to locate Attribute  with the the given name [selected] on this ManagedType [x.model.Value];

JPA'ya yalnızca selectedbir değer olması durumunda ilişkilendirmeyi getirmeyi denediğini nasıl anlayabilirim MCValue? Gibi bir şeye ihtiyacım var optionalAttributePaths.

Yanıtlar:


8

Bir EntityGraphilişkilendirme özniteliği üst sınıfın bir parçasıysa ve bu nedenle de tüm alt sınıfların bir parçasıysa kullanabilirsiniz. Aksi takdirde, EntityGraphher zaman Exceptionşu anda aldığınız ile başarısız olur .

N + 1 seçim sorununuzu önlemenin en iyi yolu sorgunuzu 2 sorguya bölmektir:

İlk sorgu , özellik tarafından eşlenen ilişkilendirmeyi almak için MCValuebir öğesini kullanarak varlıkları getirir . Bu sorgudan sonra, bu varlıklar Hazırda Bekletme'nin 1. düzey önbelleğinde / kalıcılık bağlamında depolanır. Hazırda Beklet, 2. sorgunun sonucunu işlediğinde bunları kullanır.EntityGraphselected

@Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
@EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();

2. sorgu daha sonra Answervarlığı getirir ve EntityGraphilişkili Valuevarlıkları da getirmek için bir kullanır . Her Valuevarlık için Hazırda Beklet, belirli alt sınıfı başlatır ve 1. düzey önbelleğin söz konusu sınıf ve birincil anahtar bileşimi için zaten bir nesne içerip içermediğini kontrol eder. Bu durumda, Hazırda Bekletme, sorgu tarafından döndürülen veriler yerine 1. düzey önbellekten nesneyi kullanır.

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

MCValueİlişkili varlıkları olan tüm varlıkları zaten getirmiş selectedolduğumuz için, şimdi Answerbaşlatılmış bir valueilişkilendirmeye sahip varlıklar alıyoruz . Dernek bir MCValuevarlık içeriyorsa , selectedilişkilendirmesi de başlatılır.


Ben iki sorguları, 1 + cevapları almak selectediçin bir 1. ve bu cevaplar için bir almak için bir 2. bir sorgu düşündüm MCValue. Bunun ek bir döngü gerektirmediğini ve veri kümeleri arasındaki eşlemeyi yönetmem gerektiğini sevmedim. Bunun için Hazırda Bekletme önbelleğinden yararlanma fikrinizi seviyorum. Sonuçları içermenin önbelleğe güvenmenin ne kadar güvenli olduğunu (tutarlılık açısından) açıklayabilir misiniz? Bir işlemde sorgular yapıldığında bu çalışır mı? Ben tespit etmek zor ve düzensiz tembel başlatma hataları korkuyorum.
sıkışmış

1
Her iki sorguyu da aynı işlem içinde gerçekleştirmeniz gerekir. Bunu yaptığınız ve kalıcılığınızı temizlemediğiniz sürece kesinlikle güvenlidir. 1. düzey önbelleğiniz her zaman MCValuevarlıkları içerir . Ek bir döngüye ihtiyacınız yok. Geçerli sorgu ile aynı WHERE yan tümcesine MCValuekatılan Answerve aynı sonucu kullanan 1 sorgu içeren tüm varlıkları getirmelisiniz . Ayrıca bugünün canlı akışında bu konuda konuştum: youtu.be/70B9znTmi00?t=238 03:58 'de başladı ama aralarında birkaç soru daha aldım ...
Thorben Janssen

Harika, takip için teşekkürler! Ayrıca, bu çözümün Alt Sınıf başına 1 sorgu gerektirdiğini eklemek istiyorum. Bu nedenle, sürdürülebilirlik bizim için uygundur, ancak bu çözüm tüm durumlar için uygun olmayabilir.
sıkışmış

Benim son yorum biraz düzeltmek gerekir: Tabii ki sadece sorundan muzdarip alt sınıf başına bir sorgu gerekir. Ayrıca, alt sınıfların nitelikleri için, kullanım nedeniyle bir sorun yaratmadığını belirtmek gerekir SINGLE_TABLE_INHERITANCE.
sıkışmış

7

Spring-Data'nın orada ne yaptığını bilmiyorum, ancak bunu yapmak için, genellikle TREATalt birliğe erişebilmek için operatörü kullanmanız gerekir, ancak bu Operatörün uygulaması oldukça hatalıdır. Hazırda Beklet, burada ihtiyaç duyacağınız örtük alt tür özellik erişimini destekler, ancak görünüşe göre Spring-Data bunu düzgün bir şekilde işleyemez. JPA'nın üstünde çalışan ve keyfi yapıları varlık modelinize göre eşleştirmenize olanak tanıyan Blaze-Persistence Entity-Views'a göz atmanızı tavsiye edebilirim . DTO modelinizi miras yapısını da içeren güvenli bir şekilde eşleyebilirsiniz. Kullanım durumunuz için varlık görünümleri şöyle görünebilir

@EntityView(Answer.class)
interface AnswerDTO {
  @IdMapping
  Long getId();
  ValueDTO getValue();
}
@EntityView(Value.class)
@EntityViewInheritance
interface ValueDTO {
  @IdMapping
  Long getId();
}
@EntityView(TextValue.class)
interface TextValueDTO extends ValueDTO {
  String getText();
}
@EntityView(RatingValue.class)
interface RatingValueDTO extends ValueDTO {
  int getRating();
}
@EntityView(MCValue.class)
interface TextValueDTO extends ValueDTO {
  @Mapping("selected.id")
  Set<Long> getOption();
}

Blaze-Persistence tarafından sağlanan yaylı veri entegrasyonu ile böyle bir depo tanımlayabilir ve doğrudan sonucu kullanabilirsiniz

@Transactional(readOnly = true)
interface AnswerRepository extends Repository<Answer, Long> {
  List<AnswerDTO> findAll();
}

AnswerDTOAşağıdaki gibi bir şey olan sadece eşlediğinizi seçen bir HQL sorgusu oluşturur .

SELECT
  a.id, 
  v.id,
  TYPE(v), 
  CASE WHEN TYPE(v) = TextValue THEN v.text END,
  CASE WHEN TYPE(v) = RatingValue THEN v.rating END,
  CASE WHEN TYPE(v) = MCValue THEN s.id END
FROM Answer a
LEFT JOIN a.value v
LEFT JOIN v.selected s

Hmm zaten bulduğum kitaplığınıza ipucu için teşekkürler, ancak 2 ana nedenden dolayı kullanmayız: 1) projemizin ömrü boyunca desteklenmesi için lib'e güvenemeyiz (şirketiniz blazebit oldukça küçük ve başlangıcında). 2) Tek bir sorguyu optimize etmek için daha karmaşık bir teknoloji yığını taahhüt etmeyeceğiz. (Lib'inizin daha fazlasını yapabileceğini biliyorum, ancak ortak bir teknoloji yığını tercih ediyoruz ve JPA çözümü yoksa özel bir sorgu / dönüşüm uygulayacağız).
Stuck

1
Blaze-Persistence açık kaynak kodludur ve Entity-Views, standart olan JPQL / HQL'in üstüne az çok uygulanır. Uyguladığı özellikler kararlı ve hala Hibernate'in gelecekteki sürümleriyle çalışacak, çünkü standardın üstünde çalışıyor. Tek kullanımlık bir durum nedeniyle bir şey tanıtmak istemediğinizi anlıyorum, ancak Varlık Görünümlerini kullanabileceğiniz tek kullanımlık durum olduğundan şüpheliyim. Varlık Görünümlerinin Tanıtımı genellikle boyler plakası kodunun miktarını önemli ölçüde azaltır ve sorgu performansını artırır. Size yardımcı olacak araçları kullanmak istemiyorsanız, öyle olsun.
Christian Beikov

En azından sorunu çözmediniz ve bir çözüm sunuyorsunuz. Böylece, yanıtlar orijinal problemde tam olarak neler olup bittiğini ve JPA'nın bunu nasıl çözebileceğini açıklamasa da ödül kazanırsınız. Benim düşünceme göre sadece JPA tarafından desteklenmiyor ve bir özellik isteği haline gelmeli. Yalnızca JPA'yı hedefleyen daha ayrıntılı bir yanıt için başka bir ödül sunacağım.
Stuck

JPA ile mümkün değil. Herhangi bir JPA sağlayıcısında tam olarak desteklenmeyen veya EntityGraph ek açıklamalarında desteklenmeyen TREAT operatörüne ihtiyacınız vardır. Bunu modellemenin tek yolu, açık birleştirmeleri kullanmanızı gerektiren Hazırda Bekletme alt türü özellik çözümleme özelliğidir.
Christian Beikov

1
Cevabınız görünümünde tanımı olmalıdırinterface MCValueDTO extends ValueDTO { @Mapping("selected.id") Set<Long> getOption(); }
Stuck

0

En son projem GraphQL (benim için bir ilk) kullandı ve N + 1 sorguları ile büyük bir sorun yaşadık ve sorguları yalnızca gerektiğinde tablolara katılmak için optimize etmeye çalıştık. Bulduk Cosium / yay veri JPA-işletme-grafik doldurulamaz. JpaRepositoryVarlık grafiğini sorguya iletmek için yöntemleri genişletir ve ekler. Daha sonra, yalnızca ihtiyacınız olan veriler için sol birleşimleri eklemek üzere çalışma zamanında dinamik varlık grafikleri oluşturabilirsiniz.

Veri akışımız şuna benzer:

  1. GraphQL isteği al
  2. GraphQL isteğini ayrıştırın ve sorgudaki varlık grafiği düğümleri listesine dönüştürün
  3. Bulunan düğümlerden varlık grafiği oluşturun ve yürütme için depoya geçin

Varlık grafiğine geçersiz düğümler dahil etmeme sorununu çözmek için (örneğin __typenamegraphql'den), varlık grafiği oluşturmayı işleyen bir yardımcı program sınıfı oluşturdum. Çağıran sınıf, grafiğini oluşturduğu sınıf adından geçer ve daha sonra grafikteki her düğümü ORM tarafından tutulan metamodele göre doğrular. Düğüm modelde değilse, grafik düğümleri listesinden kaldırır. (Bu kontrol tekrarlamalı olmalı ve her çocuğu da kontrol etmelidir)

Bunu bulmadan önce, Bahar JPA / Hibernate belgelerinde önerilen projeksiyonları ve diğer tüm alternatifleri denedim, ancak hiçbir şey sorunu zarif veya en azından bir ton ekstra kodla çözecek gibi görünmüyordu.


süper tipte bilinmeyen dernekleri yükleme problemini nasıl çözer? Ayrıca, diğer cevaba söylendiği gibi, saf bir JPA çözümü olup olmadığını bilmek istiyoruz, ama aynı zamanda lib'in, selectedderneğin tüm alt türleri için mevcut olmadığı aynı sorundan muzdarip olduğunu düşünüyorum value.
Stuck

GraphQL ile ilgileniyorsanız, Blaze-Persistence Entity Views'ın graphql-java ile entegrasyonuna sahibiz: persistence.blazebit.com/documentation/1.5/entity-view/manual/…
Christian Beikov

@ChristianBeikov teşekkürler ama şemamızı programlı olarak modellerimizden / yöntemlerden üretmek için SQPR kullanıyoruz
aarbor

Önce kod yaklaşımını seviyorsanız, GraphQL entegrasyonuna bayılacaksınız. Yalnızca gerçekte kullanılan sütunları / ifadeleri birleştirmeyi vb. Otomatik olarak almayı sağlar.
Christian Beikov

0

Yorumunuzdan sonra düzenlendi:

Özür dilerim, ilk turda size sorun yaşamaya devam etmedim, sorununuz sadece findAll () öğesini çağırmaya çalıştığınızda bahar verilerinin başlangıcında ortaya çıkıyor.

Böylece, tüm örnekte gezinebilirsiniz github benim çekilebilir: https://github.com/bdzzaid/stackoverflow-java/blob/master/jpa-hibernate/

Sorununuzu bu proje içinde kolayca yeniden oluşturabilir ve düzeltebilirsiniz.

Etkili bir şekilde, Bahar verileri ve hazırda bekletme modu varsayılan olarak "seçili" grafiği belirleyemez ve seçilen seçeneği toplama yolunu belirtmeniz gerekir.

Yani öncelikle, Answer sınıfının NamedEntityGraphs'ını bildirmelisiniz

Gördüğünüz gibi, Answer sınıfının öznitelik değeri için iki NamedEntityGraph var

  • Yüklemek için belirli bir ilişki olmadan tüm Değer için ilk

  • Belirli Multichoice değeri için ikinci . Bunu kaldırırsanız, istisnayı yeniden oluşturursunuz.

İkinci olarak, bir işlem bağlamında olması gerekir answerRepository.findAll () Tür verileri alıp istiyorsanız Lazy

@Entity
@Table(name = "answer")
@NamedEntityGraphs({
    @NamedEntityGraph(
            name = "graph.Answer", 
            attributeNodes = @NamedAttributeNode(value = "value")
    ),
    @NamedEntityGraph(
            name = "graph.AnswerMultichoice",
            attributeNodes = @NamedAttributeNode(value = "value"),
            subgraphs = {
                    @NamedSubgraph(
                            name = "graph.AnswerMultichoice.selected",
                            attributeNodes = {
                                    @NamedAttributeNode("selected")
                            }
                    )
            }
    )
}
)
public class Answer
{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private int id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "value_id", referencedColumnName = "id")
    private Value value;
// ..
}

Sorun alma değil value-Ortaklık Answerama alma selecteddurumunda dernek valuebir olduğunu MCValue. Cevabınız bununla ilgili herhangi bir bilgi içermiyor.
sıkışmış

@Stuck Cevabınız için teşekkürler, lütfen MCValue sınıfını benimle paylaşır mısınız, sorunu yerel olarak yeniden oluşturmaya çalışacağım.
bdzzaid

Örneğiniz, yalnızca ilişkilendirmeyi şu soruda belirtildiği OneToManygibi tanımladığınız için çalışır FetchType.EAGER: tüm ilişkilendirmeler LAZY.
sıkışmış

@ Stuck Son güncellemenizden bu yana cevabımı güncelledim, cevabımın sorununuzu çözmenize yardımcı olacağını ve isteğe bağlı ilişkiler de dahil olmak üzere varlık grafiğini yükleme yolunu anlamanıza yardımcı olacağını umuyoruz.
bdzzaid

Sizin "çözüm" hala bu sorunun hakkında orijinal N + 1 sorunu muzdarip: test ve farklı işlemlerde insert ve bulmak yöntemleri koymak ve jpa selectedonları yüklemek yerine her cevap için bir DB sorgu verecektir göreceksiniz .
sıkışmış
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.