Spring, @Transactional и Hibernate Lazy Загрузка
Я использую spring + hibernate. Все мои HibernateDAO используют непосредственно sessionFactory.
У меня есть уровень приложения → уровень сервиса → слой DAO, и все коллекции загружаются с лёгкой загрузкой.
Итак, проблема в том, что когда-то на прикладном уровне (который содержит GUI/swing) я загружаю объект, используя метод уровня сервиса (который содержит аннотацию @Transactional), и я хочу использовать свойство lazly этого объекта, но obviusly сессия уже закрыта.
Каков наилучший способ решить эту проблему?
ИЗМЕНИТЬ
Я пытаюсь использовать MethodInterceptor, моя идея - написать AroundAdvice для всех моих объектов и использовать аннотацию, например, например:
// Custom annotation, say that session is required for this method
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SessionRequired {
// An AroundAdvice to intercept method calls
public class SessionInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation mi) throws Throwable {
bool sessionRequired=mi.getMethod().isAnnotationPresent(SessionRequired.class);
// Begin and commit session only if @SessionRequired
if(sessionRequired){
// begin transaction here
}
Object ret=mi.proceed();
if(sessionRequired){
// commit transaction here
}
return ret;
}
}
// An example of entity
@Entity
public class Customer implements Serializable {
@Id
Long id;
@OneToMany
List<Order> orders; // this is a lazy collection
@SessionRequired
public List<Order> getOrders(){
return orders;
}
}
// And finally in application layer...
public void foo(){
// Load customer by id, getCustomer is annotated with @Transactional
// this is a lazy load
Customer customer=customerService.getCustomer(1);
// Get orders, my interceptor open and close the session for me... i hope...
List<Order> orders=customer.getOrders();
// Finally use the orders
}
Как вы думаете, может ли это работать?
Проблема в том, как зарегистрировать этот перехватчик для всех моих объектов, не делая этого в XML файле?
Есть способ сделать это с помощью аннотации?
Ответы
Ответ 1
Hibernate недавно представил профили извлечения, которые (в дополнение к настройке производительности) идеально подходят для решения таких проблем. Он позволяет (во время выполнения) выбирать между различными стратегиями загрузки и инициализации.
http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles
Изменить (добавлен раздел о том, как настроить профиль выборки с помощью перехватчика):
Прежде чем приступить к работе: убедитесь, что профили извлечения действительно будут работать для вас. Я сам их не использовал и вижу, что в настоящее время они ограничены, чтобы присоединиться к выборкам. Прежде чем тратить время на внедрение и подключение перехватчика, попробуйте вручную установить профиль выборки и убедитесь, что он действительно решает вашу проблему.
Существует множество способов установки перехватчиков в Spring (в соответствии с предпочтением), но самым прямым способом было бы реализовать MethodInterceptor (см. http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around). Пусть у него есть сеттер для профиля выборки, который вы хотите, и настройки для сеанса Hibernate factory:
public class FetchProfileInterceptor implements MethodInterceptor {
private SessionFactory sessionFactory;
private String fetchProfile;
... setters ...
public Object invoke(MethodInvocation invocation) throws Throwable {
Session s = sessionFactory.openSession(); // The transaction interceptor has already opened the session, so this returns it.
s.enableFetchProfile(fetchProfile);
try {
return invocation.proceed();
} finally {
s.disableFetchProfile(fetchProfile);
}
}
}
Наконец, включите перехватчик в конфигурации Spring. Это можно сделать несколькими способами, и у вас, вероятно, уже есть настройка АОП, к которой вы можете добавить. См. http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema.
Если вы новичок в AOP, я бы попробовал сначала попробовать "старый" ProxyFactory (http://static.springsource.org/ spring/docs/3.0.x/spring-framework- reference/html/aop-api.html # aop-api-proxying-intf), потому что легче понять, как это работает. Вот несколько примеров XML, чтобы вы начали:
<bean id="fetchProfileInterceptor" class="x.y.zFetchProfileInterceptor">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="fetchProfile" ref="gui-profile"/>
</bean>
<bean id="businessService" class="x.y.x.BusinessServiceImpl">
<property name="dao" .../>
...
</bean>
<bean id="serviceForSwinGUI"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="x.y.z.BusinessServiceInterface/>
<property name="target" ref="businessService"/>
<property name="interceptorNames">
<list>
<value>existingTransactionInterceptorBeanName</value>
<value>fetchProfileInterceptor</value>
</list>
</property>
</bean>
Ответ 2
- Создайте метод на уровне сервиса, который возвращает объект с леними нагрузками для этого объекта
- Измените выборку:
- Если возможно, переведите транзакцию в прикладной уровень
(просто пока мы ждем кого-то, кто знает, о чем они говорят)
Ответ 3
Вам, к сожалению, нужно переделать управление сеансом. Это серьезная проблема при работе с Hibernate и Spring, и это гигантская проблема.
По сути, вам нужно, чтобы ваш прикладной уровень создавал новый сеанс, когда он получает объект Hibernate, и управлять этим и правильно закрывать сеанс. Этот материал сложный и нетривиальный; одним из лучших способов управлять этим является посредничество в сеансах с помощью factory, доступного на вашем прикладном уровне, но вы все равно должны иметь возможность закончить сеанс должным образом, поэтому вам нужно знать о потребностях жизненного цикла ваших данных.
Этот материал является наиболее распространенной жалобой на использование Spring и Hibernate таким образом; на самом деле, единственный способ справиться с этим - получить хороший контроль над тем, каковы ваши жизненные циклы данных.