2015-04-07 10 views
11

Próbuję zapisać do wielu baz danych przy użyciu hibernacji. Zamknąłem sesje zapisu i odczytu/zapisu w jednym obiekcie sesji. Jednak, gdy idę zapisać otrzymuję wiele błędów, które obiekty są już powiązane z innej sesji: „Nielegalne próbę skojarzenia zbiór z dwóch otwartych sesji”Hibernuj Zapisz obiekt na wiele sesji

Oto mój kod:

public class MultiSessionObject implements Session { 

     private Session writeOnlySession; 
     private Session readWriteSession; 

     @Override 
     public void saveOrUpdate(Object arg0) throws HibernateException { 
       readWriteSession.saveOrUpdate(arg0); 
       writeOnlySession.saveOrUpdate(arg0); 
     } 
} 

Próbowałem eksmisji przedmiotu i zaczerwienienia; Jednak powoduje to problemy z "Wiersz został zaktualizowany lub usunięty przez inną transakcję" ... nawet jeśli obie sesje wskazują na różne bazy danych.

public class MultiSessionObject implements Session { 

     private Session writeOnlySession; 
     private Session readWriteSession; 

     @Override 
     public void saveOrUpdate(Object arg0) throws HibernateException { 
       readWriteSession.saveOrUpdate(arg0); 
       readWriteSession.flush(); 
       readWriteSession.evict(arg0); 

       writeOnlySession.saveOrUpdate(arg0); 
       writeOnlySession.flush(); 
       writeOnlySession.evict(arg0); 
     } 
} 

Oprócz powyższego, również próbowałem użyć funkcji powielania hibernacji. Było to również nieudane bez błędów.

Czy ktoś pomyślnie zapisał obiekt w dwóch bazach danych o tym samym schemacie?

+0

Czy jest możliwe, że zasady kaskadowe kolekcji powiązanych z odwzorowanym obiektem nie są poprawnie skonfigurowane? –

Odpowiedz

4

Próbuje ponownie dołączyć daną jednostkę do bieżącej sesji, więc serwery proxy (skojarzenia LAZY) są powiązane z sesją hibernacji. Spróbuj użyć merge instead of saveOrUpdate, ponieważ merge po prostu kopiuje stan odłączonej jednostki do nowo pobranego obiektu zarządzanego. W ten sposób dostarczone argumenty nigdy nie zostaną dołączone do sesji.

Kolejnym problemem jest zarządzanie transakcjami. Jeśli używasz transakcji związanych z wątkami, potrzebujesz dwóch jawnych transakcji, jeśli chcesz zaktualizować dwa źródła danych z tego samego wątku.

Spróbuj ustawić granice transakcji wyraźnie zbyt:

public class MultiSessionObject implements Session { 

    private Session writeOnlySession; 
    private Session readWriteSession; 

    @Override 
    public void saveOrUpdate(Object arg0) throws HibernateException { 

     Transaction readWriteSessionTx = null; 
     try { 
      readWriteSessionTx = readWriteSession.beginTransaction(); 
      readWriteSession.merge(arg0); 
      readWriteSessionTx.commit(); 
     } catch (RuntimeException e) { 
      if (readWriteSessionTx != null && readWriteSessionTx.isActive()) 
       readWriteSessionTx.rollback(); 
      throw e; 
     } 

     Transaction writeOnlySessionTx = null; 
     try { 
      writeOnlySessionTx = writeOnlySession.beginTransaction(); 
      writeOnlySession.merge(arg0); 
      writeOnlySessionTx.commit(); 
     } catch (RuntimeException e) { 
      if (writeOnlySessionTx != null && writeOnlySessionTx.isActive()) 
       writeOnlySessionTx.rollback(); 
      throw e; 
     } 
    } 
} 
+0

Nie robi błędów ukrywania scalania z duplikatami kluczy głównych? –

+0

Nie. To zawiedzie, podobnie jak saveOrUpdate. –

+0

Nie jestem tego pewien. Ukrywanie commitów wewnątrz obiektu saveOrUpdate przesłania założenia dotyczące otwartej transakcji, które już wykonali programiści. Czy nie ma innego sposobu na oderwanie przedmiotu od transakcji? –

1

Tak,

Problem jest dokładnie taki, jak ci to mówi. Sposobem na osiągnięcie tego jest traktowanie 2 różnych rzeczy za pomocą 2 różnych zatwierdzeń.

Utwórz composite Dao. W nim masz jeden z tych Dao w kolekcji jest tylko instancją twojego istniejącego kodu skonfigurowanego dla 2 różnych źródeł danych. Następnie, w twoim złożonym dao, kiedy wywołujesz save, faktycznie niezależnie zapisujesz oba.

Poza zespołem powiedziałeś, że to najlepszy wysiłek. To łatwe. Użyj spring-retry, aby utworzyć punkt cięcia wokół indywidualnych metod zapisu dao, aby spróbować kilka razy. W końcu się poddaj.

public interface Dao<T> { 

    void save(T type); 
} 

Utwórz nowe wystąpienia tego pliku przy użyciu pliku applicationContext.xml, w którym każda instancja wskazuje inną bazę danych. Kiedy już tam jesteś, użyj spring-retry, aby odtworzyć wycinkę wycinaną wokół metody zapisu. Przejdź do dołu, aby zobaczyć przykład kontekstu aplikacji.

public class RealDao<T> implements Dao<T> { 

    @Autowired 
    private Session session; 

    @Override 
    public void save(T type) { 
     // save to DB here 
    } 
} 

Kompozyt

public class CompositeDao<T> implements Dao<T> { 

    // these instances are actually of type RealDao<T> 
    private Set<Dao<T>> delegates; 

    public CompositeDao(Dao ... daos) { 
     this.delegates = new LinkedHashSet<>(Arrays.asList(daos)); 

    } 

    @Override 
    public void save(T stuff) { 
     for (Dao<T> delegate : delegates) { 
      try { 
       delegate.save(stuff); 
      } catch (Exception e) { 
       // skip it. Best effort 
      } 
     } 
    } 
} 

Każdy 'rzeczy' jest zapisany w jego własnym oddzielnym sesji, czy nie. Ponieważ sesja znajduje się w instancjach "RealDao", wtedy wiesz, że do czasu pierwszego ukończenia jest całkowicie zapisana lub nieudana. Hibernacja może chce, abyś miał inny identyfikator, aby wtedy mieszanie/równe były różne, ale nie sądzę.

2

Jak wspomniano w innych odpowiedzi, jeśli używasz Session wtedy prawdopodobnie trzeba oddzielić aktualizacje i 2 w dwóch różnych transakcji. Odłączone wystąpienie obiektu (po evict) powinno być ponownie użyte w drugiej operacji aktualizacji.

Innym podejściem jest użycie StatelessSession takiego (próbowałem prosty program, więc musiał obsługiwać transakcje. Zakładam, że masz do obsługi transakcji inaczej)

public static void main(final String[] args) throws Exception { 
     final StatelessSession session1 = HibernateUtil.getReadOnlySessionFactory().openStatelessSession(); 
     final StatelessSession session2 = HibernateUtil.getReadWriteSessionFactory().openStatelessSession(); 
     try { 
      Transaction transaction1 = session1.beginTransaction(); 
      Transaction transaction2 = session2.beginTransaction(); 
      ErrorLogEntity entity = (ErrorLogEntity) session1.get(ErrorLogEntity.class, 1); 
      entity.setArea("test"); 
      session1.update(entity); 
      session2.update(entity); 
      transaction1.commit(); 
      transaction2.commit(); 
      System.out.println("Entry details: " + entity); 
     } finally { 
      session1.close(); 
      session2.close(); 
      HibernateUtil.getReadOnlySessionFactory().close(); 
      HibernateUtil.getReadWriteSessionFactory().close(); 
     } 
    } 

Problem z StatelessSession jest to, że robi nie używać żadnej pamięci podręcznej i nie obsługuje kaskadowania powiązanych obiektów. Musisz sobie z tym poradzić ręcznie.

Powiązane problemy