net.leanix.dropkit.persistence.TransactionHandler Maven / Gradle / Ivy
package net.leanix.dropkit.persistence;
import com.google.inject.Inject;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import javax.inject.Singleton;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.context.internal.ManagedSessionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A helper class used to wrap the execution of code within a hibernate transaction or session.
*
*/
@Singleton
public class TransactionHandler {
private static final Logger LOG = LoggerFactory.getLogger(TransactionHandler.class);
protected final SessionFactory sessionFactory;
@Inject
public TransactionHandler(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public boolean hasSession() {
return ManagedSessionContext.hasBind(sessionFactory);
}
public Session getSession() {
return sessionFactory.getCurrentSession();
}
/**
* Execute a block of code within a hibernate session. If the hibernate session for the current thread is already opened before,
* the bock of code uses this session. Otherwise, a new session is opened, the code is executed and the session will be closed at the end.
* @param supplier
* @return
* @throws Exception
*/
public T runInSession(Supplier supplier) {
if (hasSession()) {
// this can happened, if the code block is called from a resource that have the @UnitOfWork annotation.
LOG.debug("using existing session");
return supplier.get();
}
Session session = openSession();
LOG.debug("opened Hibernate session {}", System.identityHashCode(session));
try {
return supplier.get();
} catch (Throwable t) {
LOG.debug("we got a throwable here", t);
throw t;
} finally {
closeSession(session);
LOG.debug("closed Hibernate session {}", System.identityHashCode(session));
}
}
/**
* Executes a block of code within a transaction.
*
* @param supplier
* @return
* @throws Exception
*/
public K runInTransaction(Supplier supplier) {
Session session = openSession();
Transaction transaction = session.beginTransaction();
boolean success = false;
K result = null;
try {
result = supplier.get();
success = true;
} catch (Throwable e) {
throw e;
} finally {
if (success) {
try {
transaction.commit();
} catch (Throwable t) {
LOG.error("can not commit transaction", t);
} finally {
// close session anyway to avoid db connection leaks
closeSession(session);
}
} else {
try {
transaction.rollback();
} catch (Throwable t) {
LOG.error("can not rollback transaction", t);
} finally {
// if you make rollback, clear is needed
session.clear();
// close session anyway to avoid db connection leaks
closeSession(session);
}
}
}
return result;
}
/**
* Calls the callable within a transaction.
* If the {@code callable} throws an {@link Exception} the exception is catched
* and rethrown as {@link RuntimeException} with the exception as the cause.
*
* @param callable
* @param
* @return
* @deprecated Please use {@linkplain #runInTransaction(Supplier)} instead, which provides a more clear exception handling.
*/
public V callInTransaction(Callable callable) {
return runInTransaction(() -> {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
/**
* Executes a runnable within a transaction.
*
* @param runnable
*/
public void executeInTransaction(Runnable runnable) {
runInTransaction(() -> {
runnable.run();
return null;
});
}
/**
* Opens a new session and binds the factory to ManagedSessionContext.
*
* @return
*/
private synchronized Session openSession() {
Session openedSession = sessionFactory.openSession();
ManagedSessionContext.bind(openedSession);
return openedSession;
}
/**
* Closes the session and removes the binding.
*
* Calling this is a must, so best call it in a finally block.
*/
private synchronized void closeSession(Session openedSession) {
openedSession.close();
ManagedSessionContext.unbind(sessionFactory);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy