Когда использовать EntityManager.find() vs EntityManager.getReference()

Я столкнулся с ситуацией (которая, по моему мнению, странная, но вполне нормально), где я использую EntityManager.getReference(LObj.getClass(), LObj.getId()), чтобы получить объект базы данных, а затем передать возвращенный объект сохраняется в другой таблице.

Итак, в основном поток был таким:

class TFacade{

  createT(FObj, AObj) {
    T TObj = new 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 = new FLH();
    FLHObj.setF(FObj);
    FLHObj.setL(LObj);
    ....
    EntityManager.persist(FLHObj);
    ...
  }
}

Я получал следующее исключение: "java.lang.IllegalArgumentException: Неизвестный объект: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"

Посмотрев на него некоторое время, я, наконец, понял, что это связано с тем, что я использовал метод EntityManager.getReference(), который получал вышеупомянутое исключение, поскольку метод возвращал прокси.

Это заставляет меня задаться вопросом, когда целесообразно использовать метод EntityManager.getReference() вместо метода EntityManager.find()?

EntityManager.getReference() выдает исключение EntityNotFoundException, если он не может найти поиск объекта, который очень удобен сам по себе. EntityManager.find() просто возвращает null, если он не может найти объект.

Что касается границ транзакций, для меня звучит так же, как вам нужно будет использовать метод find(), прежде чем передавать вновь найденный объект в новую транзакцию. Если вы используете метод getReference(), вы, вероятно, попадете в ситуацию, похожую на мою, с указанным выше исключением.

Ответ 1

Обычно я использую метод getReference, когда мне не нужно обращаться к состоянию базы данных (я имею в виду метод getter). Просто изменить состояние (я имею в виду метод setter). Как вы знаете, getReference возвращает прокси-объект, который использует мощную функцию, называемую автоматической грязной проверкой. Предположим, что

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

}

Если я вызову метод find, поставщик JPA, за кулисами, вызовет

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Если я вызову метод getReference, поставщик JPA, за кулисами, вызовет

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

И вы знаете, почему???

Когда вы вызываете getReference, вы получите прокси-объект. Что-то вроде этого (поставщик JPA берет на себя реализацию этого прокси)

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

}

Таким образом, перед фиксацией транзакции поставщик JPA увидит флаг stateChanged, чтобы обновить объект OR NOT. Если никакие строки не обновляются после инструкции обновления, поставщик JPA будет бросать EntityNotFoundException в соответствии со спецификацией JPA.

С уважением,

Ответ 2

Поскольку ссылка "управляется", но не гидратирована, она также позволяет удалить объект по идентификатору без необходимости его сначала загружать в память.

Поскольку вы не можете удалить неуправляемый объект, просто глупо загружать все поля, используя find (...) или createQuery (...), чтобы сразу удалить его.

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