2013-02-24 5 views
4

У меня проблемы с прокси-объектами в Грайле. Если предположить, что у меня есть следующийGrails. Hibernate ленивый погрузка нескольких объектов

class Order { 
    @ManyToMany(fetch = FetchType.EAGER) 
    @JoinTable(name="xxx", joinColumns = {@JoinColumn(name = "xxx")}, inverseJoinColumns = {@JoinColumn(name = "yyy")}) 
    @OrderBy("id") 
     @Fetch(FetchMode.SUBSELECT) 
     private List<OrderItem> items; 
    } 

class Customer { 
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true) 
    @JoinColumn(name = "xxx",insertable = false, nullable = false) 
    private OrderItem lastItem; 

    private Long lastOrderId; 
} 

И внутри некоторого класса контроллера

//this all happens during one hibernate session. 
    def currentCustomer = Customer.findById(id) 
//at this point currentCustomer.lastItem is a javassist proxy 

def lastOrder = Order.findById(current.lastOrderId) 
//lastOrder.items is a proxy 
//Some sample actions to initialise collections 
lastOrder.items.each { println "${it.id}"} 

После итерации lastOrder.items все еще содержит прокси-сервер currentCustomer.lastItem. Например, если есть 4 элементы коллекции lastOrder.items, это выглядит следующим образом:

  • объект
  • объект
  • Javassist прокси (все поля равны нулю, включая идентификатор поля). Это тот же объект, что и в currentCustomer.lastItem.
  • объект

Кроме того, этот прокси-объект имеет все свойства, установленные в нулевое значение, и это не инициализируется, когда добытчики вызываются. Мне нужно вручную вызвать GrailsHibernateUtils.unwrapIdProxy() на каждый элемент внутри lastOrder.items, чтобы убедиться, что внутри нет прокси-серверов (что в основном приводит к извлечению EAGER).

Этот один прокси-объект приводит к некоторым действительно странным Исключениям, которые трудно отследить на этапе тестирования.

Интересный факт: если я изменяю порядок операций (сначала загружаем заказ и второй клиент), каждый элемент внутри lastOrder.items инициализируется.

Вопрос: Есть ли способ сообщить Hibernate, что он должен инициализировать коллекции, когда они касаются, независимо от того, какие элементы из коллекции уже проксированы в сеансе?

+0

Если 'является Оно деталь A.' прокси затем 'Println«$ {it.id } "' не инициирует инициализацию, для этого вам нужно получить свойство non-ID. –

+0

@IanRoberts Любая ссылка на официальную документацию? Причина с моей точки зрения для прокси-объекта действительно имеет смысл инициализировать поле Id. Особенно в тех случаях, когда используется ManyToOne (внешний ключ хранится в родительском объекте) – WeMakeSoftware

+0

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

ответ

4

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

Когда вы загружаете Customer, он попадает в кэш Session вместе с любыми объектами, которые его загружают. Это включает прокси-объект для объекта OrderItem, потому что у вас есть FetchType.LAZY. Hibernate разрешает только одному экземпляру быть связанным с каким-либо конкретным идентификатором, поэтому любые дальнейшие операции, которые будут действовать на OrderItem с этим ID , всегда будут использовать этот прокси-сервер. Если вы попросили тот же Session получить этот конкретный OrderItem другим способом, так как вы загружаете его Order, то у Order будет прокси-сервер из-за правил идентификации на уровне сеанса.

Именно поэтому он «работает», когда вы меняете порядок. Сначала загрузите Order, его коллекция - FetchType.EAGER, и поэтому он (и кеш первого уровня) имеет полностью реализованные экземпляры OrderItem. Теперь загрузите Customer, у которого есть lastItem, установленный в один из уже загруженных экземпляров OrderItem и presto, у вас есть реальный OrderItem, а не прокси.

Вы можете ознакомиться с правилами удостоверения задокументированных в Hibernate manual:

Для объектов, подключенных к конкретной сессии ... идентичности JVM идентичности базы данных гарантируется Hibernate.

Все, что сказал, даже если вы получите OrderItem прокси, он должен работать нормально до тех пор, ассоциированная Session все еще активен. Я не обязательно ожидал, что поле идентификатора прокси-сервера будет отображаться как заполненное в отладчике или аналогичном, просто потому, что прокси обрабатывает вещи «специальным» способом (т. Е. Это не POJO). Но он должен реагировать на вызовы методов так же, как и базовый класс. Поэтому, если у вас есть метод OrderItem.getId(), он обязательно должен вернуть идентификатор при вызове и аналогичным образом по любому другому методу. Поскольку он лениво инициализирован, некоторые из этих вызовов могут потребовать запрос базы данных.

Возможно, единственная реальная проблема здесь состоит в том, что ее запутывает, чтобы любой конкретный OrderItem мог быть прокси-сервером или нет. Может быть, вы хотите просто изменить отношения, чтобы они либо ленивы, либо оба нетерпеливы?

Для чего это стоит, немного странно, что у вас есть отношения ManyToMany как EAGER и ManyToOne как LAZY. Это в точности противоположно обычным настройкам, поэтому я, по крайней мере, подумаю об изменении его (хотя, очевидно, я не знаю всего вашего прецедента). Один из способов подумать об этом: если OrderItem так дорого вытаскивать, что это проблема при запросе на Customer, то, конечно, слишком дорого загружать все из них сразу? Или, наоборот, если это достаточно дешево, чтобы загрузить все из них, конечно, это достаточно дешево, чтобы просто захватить его, когда вы получите Customer?

+0

, что, вероятно, объясняет ситуацию. Weird Exceptions - свойство id является нулевым для прокси-объекта, а сам прокси не инициализируется при доступе через набор элементов – WeMakeSoftware

+0

@Funtik ok, я обновил свой ответ тем, что я думаю об этом, и как вы должны двигаться вперед. удачи. – sharakan

+0

сначала кажется странным, но когда мы загружаем объект Order в 95% случаев, нам нужны OrderItems. С другой стороны, Клиент используется практически во всех случаях (поэтому мы пытаемся оптимизировать удары БД). – WeMakeSoftware

0

Я думаю, что вы можете заставить жадную загрузку так или с помощью

def lastOrder = Order.withCriteria(uniqueResult: true) { 
     eq('id', current.lastOrderId) 
     items{} 
    } 

или используя HQL запрос с «загрузить все»

+0

Я хочу наоборот - ленивый выбор с загрузкой по требованию – WeMakeSoftware

 Смежные вопросы

  • Нет связанных вопросов^_^