Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Save Object to Multiple Sessions

I am attempting to write to multiple databases using hibernate. I have encapsulated write and read/write sessions within a single session object. However, when I go to save I get a lot of errors that the objects are already associated with another session: "Illegal attempt to associate a collection with two open sessions"

Here is my code:

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

I have tried evicting the object and flushing; however, that causes problems with "Row was updated or deleted by another transaction"... even though both sessions point to different databases.

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

In addition to the above, I have also attempted using the replicate functionality of hibernate. This was also unsuccessful without errors.

Has anyone successfully saved an object to two databases that have the same schema?

like image 596
Dave C Avatar asked Apr 07 '15 19:04

Dave C


3 Answers

The saveOrUpdate tries to reattach a given Entity to the current running Session, so Proxies (LAZY associations) are bound to the Hibernate Session. Try using merge instead of saveOrUpdate, because merge simply copies a detached entity state to a newly retrieved managed entity. This way, the supplied arguments never gets attached to a Session.

Another problem is Transaction Management. If you use Thread-bound Transaction, then you need two explicit transactions if you want to update two DataSources from the same Thread.

Try to set the transaction boundaries explicitly too:

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;
        }
   }
}
like image 198
Vlad Mihalcea Avatar answered Oct 22 '22 23:10

Vlad Mihalcea


As mentioned in other answers, if you are using Session then you probably need to separate the 2 updates and in two different transactions. The detached instance of entity (after evict) should be able to be reused in the second update operation.

Another approach is to use StatelessSession like this (I tried a simple program so had to handle the transactions. I assume you have to handle the transactions differently)

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

The issue with StatelessSession is that it does not use any cache and does not support cascading of associated objects. You need to handle that manually.

like image 3
hemant1900 Avatar answered Oct 22 '22 22:10

hemant1900


Yeah,

The problem is exactly what it's telling you. The way to successfully achieve this is to treat it like 2 different things with 2 different commits.

Create a composite Dao. In it you have a

Collection<Dao>

Each of those Dao in the collection is just an instance of your existing code configured for 2 different data sources. Then, in your composite dao, when you call save, you actually independently save to both.

Out-of-band you said you it's best effort. So, that's easy enough. Use spring-retry to create a point cut around your individual dao save methods so that they try a few times. Eventually give up.

public interface Dao<T> {

    void save(T type);
}

Create new instances of this using a applicationContext.xml where each instance points to a different database. While you're in there use spring-retry to play a retry point-cut around your save method. Go to the bottom for the application context example.

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

    @Autowired
    private Session session;

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

The composite

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

Each 'stuff' is saved in it's own seperate session or not. As the session is on the 'RealDao' instances, then you know that, by the time the first completes it's totally saved or failed. Hibernate might want you to have a different ID for then so that hash/equals are different but I don't think so.

like image 1
Christian Bongiorno Avatar answered Oct 22 '22 22:10

Christian Bongiorno