2010-11-17 32 views
11

Używam spring + hibernate. Wszystkie moje HibernateDAO używają bezpośrednio sessionFactory.Spring, @Transactional i Hibernate Lazy Loading

Mam warstwę aplikacji -> warstwa serwisowa -> warstwa DAO i wszystkie kolekcje są załadowane leniwie.

Problem polega na tym, że czasami w warstwie aplikacji (która zawiera GUI/swing) ładuję encję przy użyciu metody warstwy usługi (która zawiera adnotację @Transactional) i chcę używać lazly własności tego obiektu, ale obviusly sesja jest już zamknięta.

Jaki jest najlepszy sposób rozwiązania tego problemu?

EDIT

próbuję użyć MethodInterceptor, mój pomysł jest napisanie AroundAdvice dla wszystkich moich podmiotów i użyć adnotacji, więc na przykład:

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

Myślisz, że ta praca ? Problem polega na tym, jak zarejestrować przechwytywanie dla wszystkich moich jednostek bez tego w pliku xml? Jest na to sposób z adnotacją?

Odpowiedz

3

Hibernate niedawno wprowadzono profile pobierania, które (oprócz dostrajania wydajności) są idealne do rozwiązywania takich problemów. To pozwala (w czasie wykonywania) wybrać różne strategie ładowania i inicjalizacji.

http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles

Edit (dodany rozdział o tym, jak ustawić profil pobrać za pomocą przechwytywania):

Zanim zaczniesz: Sprawdź, czy pobrać profile rzeczywiście będzie pracować dla Ciebie. Nie korzystałem z nich osobiście i widzę, że obecnie ograniczają się do dołączania pobrań. Zanim zmarnujesz czas na implementację i podłączenie przechwytywacza, spróbuj ręcznie ustawić profil pobierania i sprawdź, czy rzeczywiście rozwiązuje on Twój problem.

Istnieje wiele sposobów na ustawienie przechwytywaczy na wiosnę (zgodnie z preferencjami), ale najbardziej prostym sposobem byłoby zaimplementowanie metody MethodInterceptor (patrz http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around). Niech to mieć setter dla pobrać profil chcesz i setter dla fabryki sesji Hibernate:

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

Wreszcie włączyć przechwytujących na wiosnę config. Można to zrobić na kilka sposobów i prawdopodobnie masz już konfigurację AOP, do której możesz ją dodać. Zobacz http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema.

Jeśli jesteś nowy w AOP, sugeruję najpierw wypróbowanie "starej" metody ProxyFactory (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html /aop-api.html#aop-api-proxying-intf), ponieważ łatwiej jest zrozumieć, jak to działa. Oto niektóre próbki XML, aby zacząć:

<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> 
+0

@DaGGeRRz: to nie jest przydatne, jaka jest różnica między dwiema metodami w DAO, takimi jak loadLazly i loadEager i używaniem profilu pobierania? Nie ma różnicy, muszę napisać metodę differente, jeśli muszę załadować określony obiekt, który jest leniwy załadowany. – blow

+1

Można na przykład zawinąć usługę/dao interceptorem, który ustawia profil pobierania "gui" po wywołaniu z gui. Daj mi znać, jeśli potrzebujesz więcej informacji na ten temat. – DaGGeRRz

+0

@DaGGeRRz: na pewno dziękuję, to brzmi interesująco. – blow

1
  1. utworzyć metodę w warstwie usługowej, która zwraca obiekt leniwy załadowane dla tego podmiotu
  2. Zmień sprowadzić chętni :)
  3. Jeśli to możliwe rozszerzyć swoją transakcję w warstwie aplikacji

(tylko wtedy, gdy czekamy na kogoś, kto wie, o czym mówią)

+0

dziękuję, 1. jest nieco nudny, muszę napisać metodę za każdym razem chcę załadować leniwe obiektu, 2. jest zbyt ekspansywna w perfomance. Może 3. jest rozwiązaniem ... – blow

1

Niestety, trzeba przerobić zarządzanie sesją. Jest to poważny problem w przypadku Hibernacji i Wiosny, a to gigantyczny problem.

Zasadniczo potrzebna jest warstwa aplikacji do utworzenia nowej sesji, gdy otrzyma ona obiekt Hibernate, oraz do zarządzania tym i zamknięcia sesji prawidłowo. Te rzeczy są trudne i nietrywialne; jednym z najlepszych sposobów zarządzania tym jest pośredniczenie w sesjach przez fabrykę dostępną z poziomu twojej warstwy aplikacji, ale nadal musisz być w stanie zakończyć sesję poprawnie, więc musisz być świadomy potrzeb w zakresie cyklu życia danych.

To jest najczęstsza skarga dotycząca używania Spring i Hibernate w ten sposób; tak naprawdę, jedynym sposobem, aby nim zarządzać, jest dokładne zorientowanie się, jakie są twoje cykle życia danych.

+0

Czy mogę utworzyć sesję za każdym razem, gdy trafię na leniwy obiekt za pomocą sprężynowego AOP? – blow

+0

@blow: tak, możesz i to jest bardzo dobry pomysł; problem staje się wiedzą, kiedy zakończyć sesję (tj. po zakończeniu modyfikacji obiektu). W niektórych przypadkach jest to łatwe; w innych, zaskakująco trudne. –

Powiązane problemy