Как избежать загрузки ленивых двунаправленных отношений с помощью MOXy?

Мой вопрос является продолжением этого комментарий.

Я смешиваю аннотации JPA и JAXB (MOXy) в одном классе, что в большинстве случаев работает нормально. Как описано в связанном потоке, @XmlInverseReference предотвращает исключения цикла при упорядочивании двунаправленных отношений. Но чтобы обнаружить цикл, MOXy должен проверить обратную ссылку связанного объекта, что приводит к дополнительным SQL-запросам SELECT, если необходимо заполнить ленивое отношение.

Чтобы подробно проиллюстрировать проблему, рассмотрим этот выдуманный пример:

@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Phone {
    @ManyToOne
    @JoinColumn( name = "employeeID" )
    @XmlElement( name = "employee" )
    @XmlInverseReference( mappedBy = "phones" )
    private Employee employee;

    private String number;

    [...]
}


@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Employee {
    @OneToMany( mappedBy = "employee" )
    @XmlElementWrapper( name = "phones" )
    @XmlElement( name = "phone" )
    @XmlInverseReference( mappedBy = "employee" )
    private List<Phone> phones;

    private String name;

    [...]
}

Теперь я бы выполнял запросы к Phones с помощью такого метода JAX-RS (используя базовый EJB):

@Inject
private PhoneService phoneService;

@GET
@Path( "/phones" )
public List<Phone> getPhonesByNumber( @QueryParam( "number" ) String number ) {
    List<Phone> result = phoneService.getPhonesByNumber( number );

    return result;
}

Происходит следующее: запрос JPQL в PhoneService EJB запускает SQL SELECT для таблицы Phone (отфильтрованной по номеру), и если я использую запрос JOIN FETCH, я могу получить связанный Employee с помощью того же единственного оператора SELECT.

Когда метод JAX-RS возвращается, срабатывает сортировка JAXB, которая приводит к дополнительному SQL SELECT: он выбирает все Phone, employeeID которых указывает на Employee, который связан с первоначально запрошенными Phone. Таким образом, ленивое отношение от Employee к Phone теперь разрешено, предположительно потому, что MOXy должен иметь возможность определять, содержится ли исходный Phone в коллекции.

Я пробовал использовать доступ к свойствам JPA и доступ к полю JAXB для поля phones, как было предложено в другом потоке, но безрезультатно. Я также пробовал обнулить поле phones в связанном экземпляре Employee после получения результата из EJB, то есть когда мои объекты уже отсоединены, но это снова привело к немедленному SQL SELECT (похоже, EclipseLink сделает это всякий раз, когда манипуляции сделаны с IndirectList?). Единственное обходное решение, которое я смог найти, - это использовать MOXy @XmlNamedObjectGraphs с подграфом, исключающим поле phones. Но это непрактично, особенно если задействованные сущности имеют много атрибутов.

Поскольку мне может потребоваться запрос в другом направлении, например сотрудников по именам с соответствующими телефонами, я не могу просто пометить phones как @XmlTransient.

Есть ли у кого-нибудь элегантное решение для подавления этих лишних операторов SQL?


person Hein Blöd    schedule 11.05.2015    source источник


Ответы (2)


По моему опыту, самый простой способ выполнить то, что вы пытаетесь, - отсоединить все классы сущностей, прежде чем передавать их на уровень представления, такой как JAX-RS rest api. Вы даже можете использовать @OneToMany(mappedBy = "employee", cascade = CascadeType.DETACH) и EntityManager.detach (), чтобы отсоединить класс телефона и впоследствии отсоединить класс сотрудников или наоборот. Это гарантирует, что во время маршалинга вашей сущности Jax-RS не вызовет никаких операторов SELECT, которые вам обычно не нужны.

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

person Jake B    schedule 11.05.2015
comment
Спасибо за ответ, но, к сожалению, это не работает: как я уже сказал, мои объекты уже возвращены из EJB в отсоединенном состоянии; Я думаю, этого и следовало ожидать, поскольку я использую контекст сохранения и область транзакции по умолчанию, а мой EJB не имеет состояния. Таким образом, транзакция завершается, а вместе с ней и связанный с ней контекст персистентности, отсоединяя все сущности. Я проверил это с помощью EntityManager.contains () для своих объектов. Я даже пытался вручную выполнить EntityManager.clear () в методе EJB, но это ничего не меняет. - person Hein Blöd; 11.05.2015
comment
Я не совсем понимаю это, но мне кажется, что IndirectList, который EclipseLink использует внутри для ленивых отношений, все еще может запускать SQL SELECT после того, как транзакция уже завершена. Имеет ли это смысл? - person Hein Blöd; 11.05.2015
comment
Я только что нашел комментарий, подтверждающий мое предположение, здесь: в целом EclipseLink поддерживает доступ к отношениям LAZY после закрытия контекста сохранения. Он делает это, используя свой пул нетранзакционных соединений для чтения. - person Hein Blöd; 11.05.2015

Я собрал некоторую информацию об EclipseLink из этих три темы. Важные биты:

Отсоединенные объекты получают соединение, необходимое для прохождения LAZY-отношения от EntityManagerFactory, и могут использовать его, пока EntityManagerFactory открыт. Соединение, используемое не в транзакционном, и когда вы хотите использовать объект в транзакции, его нужно будет правильно объединить.

 

Это особенность реализации TopLink, при которой отсоединенные экземпляры, созданные из операций чтения, не являющихся tx, по-прежнему имеют доступ в своих прокси-серверах для получения дополнительных отсоединенных экземпляров. Если бы объект был отсоединен посредством сериализации, это было бы невозможно.

 

Если вы хотите, чтобы TopLink Essentials не обрабатывал ленивые отношения после закрытия EM, я бы порекомендовал заполнить запрос на улучшение в GlassFish.

Однако я не смог найти такой запрос на улучшение, не говоря уже о реализованной возможности отключить эту функцию (в индивидуальном порядке).

Я могу придумать пять возможных обходных путей, каждый со своими недостатками:

  1. Просто не смешивайте аннотации JAXB и JPA в одном и том же классе: вместо этого используйте другой набор классов JAXB, созданных дополнительно, и выполните явное сопоставление между двумя представлениями. Это может быть немного дороже, если из запроса будет возвращено много сущностей.

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

  3. Используйте JAXB Marshaller.Listener, чтобы исключить все неустановленные IndirectContainers.

  4. Поскольку предполагается, что сериализация нарушит эту функцию EclipseLink для отсоединенных объектов, сериализуйте их перед их маршалингом. Хотя кажется неудобным и даже более дорогим.

  5. Это ближе всего к эмуляции отключения функции, но также выглядит хакерским: откройте упаковку IndirectContainer и содержащуюся в ней ValueHolderInterface и установите для них значение null. Образец кода:

(...)

import org.eclipse.persistence.indirection.IndirectContainer;

// entities must already be detached here, otherwise SQL UPDATEs will be triggered!
Employee e = phone.getEmployee();
IndirectContainer container = (IndirectContainer) e.getPhones();
container.setValueHolder( null );
e.setPhones( null );
person Hein Blöd    schedule 03.09.2015