Bir Spring Controller'da JPA ve Hazırda Bekletme ile FetchType.LAZY ilişkileri nasıl getirilir


146

Bir Kişi dersim var:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}

Tembel olan çoktan çoğa bir ilişkiyle.

Kontrol cihazımda var

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonRepository personRepository;

    @RequestMapping("/get")
    public @ResponseBody Person getPerson() {
        Person person = personRepository.findOne(1L);
        return person;
    }
}

Ve PersonRepository sadece bu kılavuza göre yazılan bu koddur

public interface PersonRepository extends JpaRepository<Person, Long> {
}

Ancak, bu denetleyicide aslında tembel verilere ihtiyacım var. Yüklemesini nasıl tetikleyebilirim?

Erişmeye çalışmak başarısız olur

bir rol koleksiyonunu tembel olarak başlatamadı: no.dusken.momus.model.Person.roles, proxy başlatılamadı - Oturum yok

veya denediğim şeye bağlı olarak diğer istisnalar.

Benim xml-açıklama , durumunda gerekli.

Teşekkürler.


Bir Personparametre verilen bir nesneyi getirmek için bir sorgu oluşturacak bir yöntem yazabilir misiniz ? Buna Query, fetchmaddeyi ekleyin Rolesve kişi için de yükleyin .
SudoRahul

Yanıtlar:


206

Tembel koleksiyonu başlatmak için açık bir çağrı yapmanız gerekecek (yaygın uygulama .size()bu amaç için çağrı yapmaktır ). Hazırda Bekletme modunda bunun için ayrılmış bir yöntem vardır ( Hibernate.initialize()), ancak JPA'nın buna eşdeğer bir değeri yoktur. Tabii ki, oturum hala kullanılabilir olduğunda çağrının yapıldığından emin olmanız gerekir, bu nedenle denetleyici yönteminize açıklama ekleyin @Transactional. Alternatif olarak, Denetleyici ile Havuz arasında tembel koleksiyonları başlatan yöntemleri ortaya çıkarabilecek bir ara Hizmet katmanı oluşturmaktır.

Güncelleme:

Yukarıdaki çözümün kolay olduğunu, ancak veritabanında (biri kullanıcı için, diğeri rolleri için) iki ayrı sorgu ile sonuçlandığını lütfen unutmayın. Daha iyi performans elde etmek istiyorsanız Spring Data JPA veri havuzu arayüzünüze aşağıdaki yöntemi ekleyin:

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p JOIN FETCH p.roles WHERE p.id = (:id)")
    public Person findByIdAndFetchRolesEagerly(@Param("id") Long id);

}

Bu yöntem , roller ilişkilendirmesini veritabanına tek bir gidiş dönüşle hevesle yüklemek için JPQL'in getirme birleştirme cümlesini kullanacak ve bu nedenle yukarıdaki çözümde iki ayrı sorgunun neden olduğu performans cezasını azaltacaktır.


3
Bunun kolay bir çözüm olduğunu, ancak veritabanında (biri kullanıcı için, diğeri rolleri için) iki ayrı sorgu ile sonuçlandığını lütfen unutmayın. Daha iyi bir performans elde etmek istiyorsanız, JPQL veya Criteria API'yi kullanarak başkalarının önerdiği gibi, kullanıcıyı ve ilişkili rolleri tek bir adımda hevesle getiren özel bir yöntem yazmayı deneyin.
Mart'ta

Şimdi Jose'nin cevabına bir örnek istedim, tamamen anlamıyorum.
Matsemann

Lütfen güncellenmiş cevabımda istenen sorgu yöntemi için olası bir çözümü kontrol edin.
Mart'ta

7
İlginç bir şey, eğer sadece joinolmadan fetch, set ile iade edilecektir initialized = false; bu nedenle, kümeye erişildikten sonra yine de ikinci bir sorgu yayınlar. fetchilişkinin tamamen yüklendiğinden emin olmanın ve ikinci sorgudan kaçınmanın anahtarıdır.
FGreg

Her ikisini de yapma ve birleştirme ve birleştirme ile ilgili sorunun, katılma ölçütlerine katılmanın göz ardı edilmesi ve listedeki veya haritadaki her şeyi almanızdır. Her şeyi istiyorsanız, o zaman bir getirme kullanın, belirli bir şey istiyorsanız, o zaman birleştirme, ancak söylendiği gibi, birleştirme boş olacaktır. Bu, .LAZY yüklemesini kullanma amacını bozar.
K.Nicholas

37

Bu eski bir yayın olmasına rağmen, lütfen @NamedEntityGraph (Javax Persistence) ve @EntityGraph (Spring Data JPA) kullanmayı düşünün. Kombinasyon çalışır.

Misal

@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
            attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}

ve sonra bahar repo aşağıdaki gibi

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}

1
@NamedEntityGraph4.3.0 sürümünden önce Hazırda Bekletme modunda uygulanmayan JPA 2.1 API'nin bir parçası olduğunu unutmayın .
naXa

2
@EntityGraph(attributePaths = "employeeGroups")@NamedEntityGraphRepo'yu açtığınızda kolayca anlaşılabilmesi için @Entity'nizde - daha az kod gerektirmeden bir yönteme açıklama eklemek için doğrudan bir Bahar Veri Deposunda kullanılabilir .
Desislav Kamenov

13

Bazı seçenekleriniz var

  • Havuzda, başlatılmış bir varlığı RJ'nin önerdiği gibi döndüren bir yöntem yazın.

Daha fazla iş, en iyi performans.

  • Oturumu tüm istek için açık tutmak için OpenEntityManagerInViewFilter kullanın.

Daha az iş, genellikle web ortamlarında kabul edilebilir.

  • Varlıkları gerektiğinde başlatmak için bir yardımcı sınıf kullanın.

Daha az çalışma, OEMIV bir seçenek olmadığında yararlıdır, örneğin bir Swing uygulamasında, ancak herhangi bir objeyi tek seferde başlatmak için depo uygulamalarında da yararlı olabilir.

Son seçenek için, bazı deph de varlıkları başlatmak için bir yardımcı sınıf, JpaUtils yazdım.

Örneğin:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

Tüm taleplerim, oluşturma vb. Olmadan basit REST çağrıları olduğundan, işlem temelde tüm isteğimdir. Girdiniz için teşekkürler.
Matsemann

İlkini nasıl yaparım? Nasıl bir sorgu yazmak biliyorum, ama ne dediğini yapmak değil. Lütfen bir örnek gösterebilir misiniz? Çok yardımcı olur.
Matsemann

zagyi cevabında bir örnek verdi, yine de beni doğru yöne yönlendirdiğiniz için teşekkürler.
Mart'ta Matsemann

Sınıfının nasıl adlandırılacağını bilmiyorum! tamamlanmadı çözelti başkalarının zamanını boşa harcıyor
Shady Sherif

Oturumu tüm istek için açık tutmak için OpenEntityManagerInViewFilter kullanın. - Kötü fikir. Varlıklarım için tüm koleksiyonları almak için ek bir talepte bulunacağım.
Yan Khonski


6

Görünüm oluşturma sırasında oturumunuzu açık tutmak için OpenSessionInViewFilter gerekir düşünüyorum (ama çok iyi bir uygulama değildir).


1
JSP ya da başka bir şey kullanmıyorum, sadece REST-api yapmak, @Transactional benim için yapacak. Ancak diğer zamanlarda yararlı olacaktır. Teşekkürler.
Mart'ta Matsemann

@Matsemann Artık geç olduğunu biliyorum ... ama bir kontrolörde bile OpenSessionInViewFilter kullanabilirsiniz ve bir cevap derlenene kadar oturum var ...
Vishwas Shashidhar

@Matsemann Teşekkürler! İşlemsel ek açıklama benim için hile yaptı! fyi: Bir dinlenme sınıfının üst sınıfına açıklama eklerseniz bile çalışır.
desperateCoder

3

Bahar Verileri JpaRepository

Bahar Verileri JpaRepositoryaşağıdaki iki yöntemi tanımlar:

  • getOne, alt varlık devam ederken bir veya üst ilişkilendirme ayarlamak için uygun bir varlık proxy'si döndürür .@ManyToOne@OneToOne
  • findById, varlığı ilişkili tablodan yükleyen SELECT deyimini çalıştırdıktan sonra POJO varlığı döndürür

Ancak, sizin durumunuzda, aramak ya yoktu getOneya findById:

Person person = personRepository.findOne(1L);

Bu nedenle, findOneyöntemin, içinde tanımladığınız bir yöntem olduğunu varsayalım PersonRepository. Ancak, findOneyöntem sizin durumunuzda çok yararlı değildir. Koleksiyon Personile birlikte getirmeniz gerektiğinden roles, bunun findOneWithRolesyerine bir yöntem kullanmak daha iyidir .

Özel Yay Verisi yöntemleri

PersonRepositoryCustomAşağıdaki gibi bir arayüz tanımlayabilirsiniz :

public interface PersonRepository
    extends JpaRepository<Person, Long>, PersonRepositoryCustom { 

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

Uygulamasını şu şekilde tanımlayın:

public class PersonRepositoryImpl implements PersonRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Person findOneWithRoles(Long id)() {
        return entityManager.createQuery("""
            select p 
            from Person p
            left join fetch p.roles
            where p.id = :id 
            """, Person.class)
        .setParameter("id", id)
        .getSingleResult();
    }
}

Bu kadar!


Sorguyu kendiniz yazmanızın ve @rakpan yanıtında EntityGraph gibi bir çözüm kullanmamanızın bir nedeni var mı? Bu aynı sonucu vermez mi?
Jeroen Vandevelde

EntityGraph kullanımı için ek yük bir JPQL sorgusundan daha yüksektir. Uzun vadede, sorguyu yazmak daha iyidir.
Vlad Mihalcea

Tepegöz üzerinde ayrıntılandırabilir misiniz (Nereden geliyor, farkediliyor mu ...)? Çünkü ikisi de aynı sorguyu oluşturuyorsa neden daha yüksek bir ek yük olduğunu anlamıyorum.
Jeroen Vandevelde

1
Çünkü EntityGraphs planları JPQL gibi önbelleğe alınmaz. Bu önemli bir performans isabeti olabilir.
Vlad Mihalcea

1
Kesinlikle. Biraz zamanım olduğunda bunun hakkında bir makale yazmam gerekecek.
Vlad Mihalcea

1

Aynısını şu şekilde yapabilirsiniz:

@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    FaqQuestions faqQuestions = null;
    try {
        faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
                questionId);
        Hibernate.initialize(faqQuestions.getFaqAnswers());

        tx.commit();
        faqQuestions.getFaqAnswers().size();
    } finally {
        session.close();
    }
    return faqQuestions;
}

Sadece kontrol cihazınızdaki faqQuestions.getFaqAnswers ().

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.