JPA ile EntityManager.find () ve EntityManager.getReference () ne zaman kullanılır?


103

Bir veritabanı varlığını almak için EntityManager.getReference (LObj.getClass (), LObj.getId ()) kullandığım ve ardından döndürülen nesneyi şu adrese ilettiğim bir durumla karşılaştım (garip olduğunu düşünüyorum, ancak muhtemelen oldukça normal) başka bir tabloda ısrarcı olun.

Yani temelde akış şöyleydi:

class TFacade {

  createT (FObj, AObj) {
    T TObj = yeni T ();
    TObj.setF (FObj);
    TObj.setA (AObj);
    ...
    EntityManager.persist (TObj);
    ...
    L LObj = A.getL ();
    FObj.setL (LObj);
    FFacade.editF (FObj);
  }
}

@ TransactionAttributeType.REQUIRES_NEW
class FFacade {

  editF (FObj) {
    L LObj = FObj.getL ();
    LObj = EntityManager.getReference (LObj.getClass (), LObj.getId ());
    ...
    EntityManager.merge (FObj);
    ...
    FLHFacade.create (FObj, LObj);
  }
}

@ TransactionAttributeType.REQUIRED
class FLHFacade {

  createFLH (FObj, LObj) {
    FLH FLHObj = yeni FLH ();
    FLHObj.setF (FObj);
    FLHObj.setL (LObj);
    ....
    EntityManager.persist (FLHObj);
    ...
  }
}

Şu istisnai alıyordum "java.lang.IllegalArgumentException: Bilinmeyen varlık: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"

Bir süre araştırdıktan sonra, sonunda EntityManager.getReference () yöntemini kullandığım için, yöntem bir proxy döndürürken yukarıdaki istisnayı aldığımı anladım.

Bu, beni meraklandırıyor zaman yerine EntityManager.find () metodunun EntityManager.getReference () yöntemini kullanmak tavsiye edilir ?

EntityManager.getReference (), aranan varlığı bulamazsa, kendi başına çok uygun olan bir EntityNotFoundException oluşturur. EntityManager.find () yöntemi, varlığı bulamazsa yalnızca boş döndürür.

İşlem sınırları ile ilgili olarak, yeni bulunan varlığı yeni bir işleme geçirmeden önce find () yöntemini kullanmanız gerektiği gibi geliyor bana. GetReference () yöntemini kullanırsanız, muhtemelen yukarıdaki istisna dışında benimkine benzer bir duruma düşersiniz.


Söylemeyi unuttum, JPA sağlayıcısı olarak Hibernate kullanıyorum.
SibzTer

Yanıtlar:


152

Veritabanı durumuna erişmem gerekmediğinde genellikle getReference yöntemini kullanıyorum (getter yöntemini kastediyorum). Sadece durumu değiştirmek için (ayarlayıcı yöntemi demek istiyorum). Bildiğiniz gibi getReference, otomatik kirli denetim adı verilen güçlü bir özellik kullanan bir proxy nesnesi döndürür. Aşağıdakileri varsayalım

public class Person {

    private String name;
    private Integer age;

}


public class PersonServiceImpl implements PersonService {

    public void changeAge(Integer personId, Integer newAge) {
        Person person = em.getReference(Person.class, personId);

        // person is a proxy
        person.setAge(newAge);
    }

}

Bul yöntemini çağırırsam , perde arkasında JPA sağlayıcısı arayacaktır

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

İ ararsanız getReference yöntemi, JPA sağlayıcı, perde arkasında, arayacak

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Ve nedenini biliyorsun ???

GetReference'ı çağırdığınızda, bir proxy nesnesi alacaksınız. Bunun gibi bir şey (JPA sağlayıcısı bu proxy'yi uygulamakla ilgilenir)

public class PersonProxy {

    // JPA provider sets up this field when you call getReference
    private Integer personId;

    private String query = "UPDATE PERSON SET ";

    private boolean stateChanged = false;

    public void setAge(Integer newAge) {
        stateChanged = true;

        query += query + "AGE = " + newAge;
    }

}

Bu yüzden işlem gerçekleştirmeden önce, JPA sağlayıcısı kişi varlığını güncellemek VEYA DEĞİLDİR için stateChanged bayrağını görecektir. Güncelleme ifadesinden sonra herhangi bir satır güncellenmezse, JPA sağlayıcısı JPA spesifikasyonuna göre EntityNotFoundException oluşturur.

Saygılarımızla,


4
EclipseLink 2.5.0 kullanıyorum ve yukarıda belirtilen sorgular doğru değil. Hangisini / hangisini kullanırsam kullanayım her zaman SELECTönce bir yayınlar . Daha da kötüsü, NON-LAZY ilişkilerinden geçiyor (yeni yayınlıyor ), ancak tek bir varlıktaki tek bir alanı güncellemek istiyorum. UPDATEfind()getReference()SELECTSELECTS
Dejan Milosevic

1
@Arthur Ronald, getReference tarafından çağrılan varlıkta bir Sürüm açıklaması varsa ne olur?
David Hofmann

@DejanMilosevic ile aynı sorunu yaşıyorum: getReference () aracılığıyla elde edilen bir varlığı kaldırırken, o varlık üzerinde bir SELECT (SEÇİM) verilir ve o varlığın tüm LAZY ilişkilerini geçer, böylece birçok SELECTS (EclipseLink 2.5.0 ile) yayınlar.
Stéphane Appercel

27

Bu makalede açıkladığım gibi, aşağıdaki diyagramda gösterildiği gibi bir ana Postvarlığınız ve bir çocuğunuz olduğunu varsayarak PostComment:

görüntü açıklamasını buraya girin

İlişkilendirmeyi findayarlamaya çalışırken ararsanız @ManyToOne post:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.find(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

Hazırda bekletme aşağıdaki ifadeleri çalıştıracaktır:

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE p.id = 1

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

SELECT sorgusu bu sefer işe yaramaz çünkü alınacak Post varlığına ihtiyacımız yok. Sadece temeldeki post_id Yabancı Anahtar sütununu ayarlamak istiyoruz.

Şimdi, getReferencebunun yerine kullanırsanız:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.getReference(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

Bu sefer, Hibernate yalnızca INSERT ifadesini yayınlayacak:

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

Bunun aksine find, getReferenceyalnızca tanımlayıcı kümesine sahip bir varlık Proxy'si döndürür. Proxy'ye erişirseniz, EntityManager hala açık olduğu sürece ilişkili SQL ifadesi tetiklenecektir.

Ancak bu durumda, Proxy varlığına erişmemize gerek yoktur. Yabancı Anahtarı yalnızca temeldeki tablo kaydına yaymak istiyoruz, bu nedenle bu kullanım örneği için bir Proxy yüklemek yeterlidir.

Bir Proxy yüklerken, EntityManager kapatıldıktan sonra Proxy referansına erişmeye çalışırsanız bir LazyInitializationException oluşturulabileceğinin farkında olmanız gerekir. İle ilgili daha fazla ayrıntı için bu makaleyeLazyInitializationException göz atın .


1
Bunu bilmemize izin verdiğiniz için teşekkürler Vlad! Ancak javadoc'a göre bu rahatsız edici görünmektedir: "Kalıcılık sağlayıcı çalışma zamanının getReference çağrıldığında EntityNotFoundException oluşturmasına izin verilir". Bu, SELECT olmadan mümkün değildir (en azından satır varlığını kontrol etmek için), değil mi? Dolayısıyla, nihai olarak bir SEÇME, uygulamaya bağlıdır.
adrhc

3
Hazırladığınız kullanım örneği için Hibernate, hibernate.jpa.compliance.proxykonfigürasyon özelliğini sunar , böylece JPA uyumluluğunu veya daha iyi veri erişim performansını seçebilirsiniz.
Vlad Mihalcea

@VladMihalcea getReference, PK setiyle yeni model örneğini ayarlamak yeterliyse neden gerekli? Neyi kaçırıyorum?
rilaby

Bu, yalnızca Hibernarea'da desteklenir, çapraz geçilirse ilişkilendirmeyi yüklemenize izin vermez.
Vlad Mihalcea

8

Bir referans 'yönetildiğinden', ancak hidrate olmadığından, bir varlığı önce belleğe yüklemenize gerek kalmadan kimliğe göre kaldırmanıza da izin verebilir.

Yönetilmeyen bir varlığı kaldıramayacağınız için, tüm alanları find (...) veya createQuery (...) kullanarak yüklemek, yalnızca hemen silmek için aptalca.

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);

7

EntityManager.find () yöntemi yerine EntityManager.getReference () yönteminin kullanılması ne zaman tavsiye edilir?

EntityManager.getReference()gerçekten hataya açık bir yöntemdir ve bir istemci kodunun onu kullanması gereken çok az durum vardır.
Şahsen, onu kullanmaya hiç ihtiyacım olmadı.

EntityManager.getReference () ve EntityManager.find (): ek yük açısından fark yok

Kabul edilen yanıta katılmıyorum ve özellikle:

Bul yöntemini çağırırsam , perde arkasında JPA sağlayıcısı arayacaktır

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

İ ararsanız getReference yöntemi, JPA sağlayıcı, perde arkasında, arayacak

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Hibernate 5 ile aldığım davranış değil ve javadoc'u getReference()böyle bir şey söylemiyor:

Durumu tembel olarak getirilebilecek bir örnek alın. İstenen örnek veritabanında yoksa, örnek durumuna ilk erişildiğinde EntityNotFoundException atılır. (Kalıcılık sağlayıcı çalışma zamanının getReference çağrıldığında EntityNotFoundException'ı atmasına izin verilir.) Uygulama, varlık yöneticisi açıkken uygulama tarafından erişilmediği sürece, örnek durumunun ayrılma üzerine kullanılabilir olmasını beklememelidir.

EntityManager.getReference() varlığı iki durumda almak için bir sorguyu yedekler:

1) varlık Persistence bağlamında depolanıyorsa, bu birinci düzey önbellektir.
Ve bu davranışa özgü değildir EntityManager.getReference(), EntityManager.find()eğer varlık Persistence bağlamında depolanıyorsa varlığı almak için bir sorgu da yedekleyecektir.

İlk noktayı herhangi bir örnekle kontrol edebilirsiniz.
Ayrıca gerçek Hazırda Bekletme uygulamasına da güvenebilirsiniz.
Aslında, varlığı yüklemek için sınıfın yöntemine EntityManager.getReference()güvenir . İşte uygulaması:createProxyIfNecessary()org.hibernate.event.internal.DefaultLoadEventListener

private Object createProxyIfNecessary(
        final LoadEvent event,
        final EntityPersister persister,
        final EntityKey keyToLoad,
        final LoadEventListener.LoadType options,
        final PersistenceContext persistenceContext) {
    Object existing = persistenceContext.getEntity( keyToLoad );
    if ( existing != null ) {
        // return existing object or initialized proxy (unless deleted)
        if ( traceEnabled ) {
            LOG.trace( "Entity found in session cache" );
        }
        if ( options.isCheckDeleted() ) {
            EntityEntry entry = persistenceContext.getEntry( existing );
            Status status = entry.getStatus();
            if ( status == Status.DELETED || status == Status.GONE ) {
                return null;
            }
        }
        return existing;
    }
    if ( traceEnabled ) {
        LOG.trace( "Creating new proxy for entity" );
    }
    // return new uninitialized proxy
    Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
    persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
    persistenceContext.addProxy( keyToLoad, proxy );
    return proxy;
}

İlginç olan kısım:

Object existing = persistenceContext.getEntity( keyToLoad );

2) Varlığı etkili bir şekilde manipüle etmezsek , javadoc'un tembel olarak getirilenine yankılanır .
Aslında, varlığın etkili bir şekilde yüklenmesini sağlamak için, bunun üzerinde bir yöntem çağırmak gerekir.
Öyleyse kazanç, bir varlığı kullanmaya ihtiyaç duymadan yüklemek istediğimiz bir senaryo ile ilgili mi? Uygulamalar çerçevesinde, bu ihtiyaç gerçekten nadirdir ve getReference()ayrıca sonraki bölümü okursanız davranış da çok yanıltıcıdır.

EntityManager.find () neden EntityManager.getReference () yerine tercih edilmelidir?

Genel gider açısından , bir önceki noktada tartışılandan getReference()daha iyi değil find().
Öyleyse neden birini veya diğerini kullanalım?

Çağırma getReference(), tembel olarak getirilen bir varlığı döndürebilir.
Burada tembel getirme, varlığın ilişkilerine değil, varlığın kendisine atıfta bulunur.
Bu, eğer çağırırsak getReference()ve ardından Persistence bağlamı kapatılırsa, varlık asla yüklenemeyebilir ve bu nedenle sonuç gerçekten tahmin edilemez. Örneğin, proxy nesnesi serileştirilmişse, nullserileştirilmiş sonuç olarak bir başvuru alabilirsiniz veya proxy nesnesinde bir yöntem çağrılırsa LazyInitializationException, atılan gibi bir istisna .

Bu , bir hata durumu varlık mevcut değilken asla gerçekleştirilemeyeceğinden, veritabanında bulunmayan bir örneği işlemek EntityNotFoundExceptioniçin ana nedenin bunun atılması olduğu anlamına gelir getReference().

EntityManager.find()EntityNotFoundExceptionvarlık bulunmazsa fırlatma hırsına sahip değildir. Davranışı hem basit hem de nettir. Her zaman yüklü bir varlık veya null(varlık bulunamazsa) döndürdüğü için asla şaşırmayacaksınız, ancak asla etkili bir şekilde yüklenemeyen bir vekil şeklinde bir varlık döndürmeyeceksiniz .
Bu yüzden EntityManager.find()çoğu durumda tercih edilmelidir.


Kabul edilen cevap + Vlad Mihalcea cevabı + Vlad Mihalcea'ya yaptığım yorumla karşılaştırıldığında sebebiniz yanıltıcı.
adrhc

1
Pro JPA2 şunu belirtir: "getReference () 'in kullanılabileceği çok özel durum göz önüne alındığında, find () hemen hemen her durumda kullanılmalıdır".
JL_SO

Bu soruyu destekleyin çünkü kabul edilen cevaba gerekli bir tamamlayıcıdır ve testlerim varlık vekilinin bir özelliğini ayarlarken, kabul edilen cevabın söylediğinin aksine, veritabanından getirildiğini gösterdi. Sadece Vlad'ın belirttiği vaka testlerimi geçti.
João Fé

2

Seçili yanıta katılmıyorum ve davidxxx'in doğru bir şekilde işaret ettiği gibi, getReference, seçme olmadan dinamik güncellemelerin bu tür davranışını sağlamaz. Bu cevabın geçerliliğiyle ilgili bir soru sordum, buraya bakın - hazırda bekletme JPA'sının getReference () 'sinden sonra setter'ı kullanarak select'i yayınlamadan güncelleme yapılamaz .

Dürüst olmak gerekirse, bu işlevi gerçekten kullanan kimseyi görmedim. HERHANGİ BİR YER. Ve neden bu kadar olumlu oy verildiğini anlamıyorum.

Şimdi her şeyden önce, bir hazırda bekletme proxy nesnesinde, bir ayarlayıcıda veya alıcıda ne çağırırsanız çağırın, bir SQL çalıştırılır ve nesne yüklenir.

Ama sonra düşündüm, peki ya JPA getReference () proxy'si bu işlevi sağlamazsa. Sadece kendi vekilimi yazabilirim.

Şimdi, birincil anahtarlarda seçimlerin bir sorgunun alabileceği kadar hızlı olduğunu ve gerçekten kaçınılması gereken çok uzun sürmesi gereken bir şey olmadığını iddia edebiliriz. Ancak, şu ya da bu nedenle bunu kaldıramayanlarımız için, aşağıda böyle bir vekilin uygulaması var. Ancak uygulamayı görmeden önce, kullanımını ve kullanımının ne kadar basit olduğunu görün.

KULLANIM

Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);

Ve bu, aşağıdaki sorguyu tetikler -

UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;

ve eklemek isteseniz bile, PersistenceService.save (new Order ("a", 2)) yapabilirsiniz; ve olması gerektiği gibi bir eki ateşler.

UYGULAMA

Bunu pom.xml dosyanıza ekleyin -

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

Bu sınıfı dinamik proxy oluşturacak şekilde yapın -

@SuppressWarnings("unchecked")
public class ProxyHandler {

public static <T> T getReference(Class<T> classType, Object id) {
    if (!classType.isAnnotationPresent(Entity.class)) {
        throw new ProxyInstantiationException("This is not an entity!");
    }

    try {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classType);
        enhancer.setCallback(new ProxyMethodInterceptor(classType, id));
        enhancer.setInterfaces((new Class<?>[]{EnhancedProxy.class}));
        return (T) enhancer.create();
    } catch (Exception e) {
        throw new ProxyInstantiationException("Error creating proxy, cause :" + e.getCause());
    }
}

Tüm yöntemlerle bir arayüz oluşturun -

public interface EnhancedProxy {
    public String getJPQLUpdate();
    public HashMap<String, Object> getModifiedFields();
}

Şimdi, bu yöntemleri proxy'nize uygulamanıza izin verecek bir engelleyici yapın -

import com.anil.app.exception.ProxyInstantiationException;
import javafx.util.Pair;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Anil Kumar
*/
public class ProxyMethodInterceptor implements MethodInterceptor, EnhancedProxy {

private Object target;
private Object proxy;
private Class classType;
private Pair<String, Object> primaryKey;
private static HashSet<String> enhancedMethods;

ProxyMethodInterceptor(Class classType, Object id) throws IllegalAccessException, InstantiationException {
    this.classType = classType;
    this.target = classType.newInstance();
    this.primaryKey = new Pair<>(getPrimaryKeyField().getName(), id);
}

static {
    enhancedMethods = new HashSet<>();
    for (Method method : EnhancedProxy.class.getDeclaredMethods()) {
        enhancedMethods.add(method.getName());
    }
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    //intercept enhanced methods
    if (enhancedMethods.contains(method.getName())) {
        this.proxy = obj;
        return method.invoke(this, args);
    }
    //else invoke super class method
    else
        return proxy.invokeSuper(obj, args);
}

@Override
public HashMap<String, Object> getModifiedFields() {
    HashMap<String, Object> modifiedFields = new HashMap<>();
    try {
        for (Field field : classType.getDeclaredFields()) {

            field.setAccessible(true);

            Object initialValue = field.get(target);
            Object finalValue = field.get(proxy);

            //put if modified
            if (!Objects.equals(initialValue, finalValue)) {
                modifiedFields.put(field.getName(), finalValue);
            }
        }
    } catch (Exception e) {
        return null;
    }
    return modifiedFields;
}

@Override
public String getJPQLUpdate() {
    HashMap<String, Object> modifiedFields = getModifiedFields();
    if (modifiedFields == null || modifiedFields.isEmpty()) {
        return null;
    }
    StringBuilder fieldsToSet = new StringBuilder();
    for (String field : modifiedFields.keySet()) {
        fieldsToSet.append(field).append(" = :").append(field).append(" and ");
    }
    fieldsToSet.setLength(fieldsToSet.length() - 4);
    return "UPDATE "
            + classType.getSimpleName()
            + " SET "
            + fieldsToSet
            + "WHERE "
            + primaryKey.getKey() + " = " + primaryKey.getValue();
}

private Field getPrimaryKeyField() throws ProxyInstantiationException {
    for (Field field : classType.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(Id.class))
            return field;
    }
    throw new ProxyInstantiationException("Entity class doesn't have a primary key!");
}
}

Ve istisna sınıfı -

public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
    super(message);
}

Bu proxy kullanılarak kaydedilecek bir hizmet -

@Service
public class PersistenceService {

@PersistenceContext
private EntityManager em;

@Transactional
private void save(Object entity) {
    // update entity for proxies
    if (entity instanceof EnhancedProxy) {
        EnhancedProxy proxy = (EnhancedProxy) entity;
        Query updateQuery = em.createQuery(proxy.getJPQLUpdate());
        for (Entry<String, Object> entry : proxy.getModifiedFields().entrySet()) {
            updateQuery.setParameter(entry.getKey(), entry.getValue());
        }
        updateQuery.executeUpdate();
    // insert otherwise
    } else {
        em.persist(entity);
    }

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