/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.transaction;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.jboss.logging.Logger;
/**
* @author Vlad Mihalcea
*/
public class TransactionUtil {
private static final Logger log = Logger.getLogger( TransactionUtil.class );
/**
* Hibernate transaction function
*
* @param <T> function result
*/
@FunctionalInterface
public interface HibernateTransactionFunction<T>
extends Function<Session, T> {
/**
* Before transaction completion function
*/
default void beforeTransactionCompletion() {
}
/**
* After transaction completion function
*/
default void afterTransactionCompletion() {
}
}
/**
* Hibernate transaction function without return value
*/
@FunctionalInterface
public interface HibernateTransactionConsumer extends Consumer<Session> {
/**
* Before transaction completion function
*/
default void beforeTransactionCompletion() {
}
/**
* After transaction completion function
*/
default void afterTransactionCompletion() {
}
}
/**
* JPA transaction function
*
* @param <T> function result
*/
@FunctionalInterface
public interface JPATransactionFunction<T>
extends Function<EntityManager, T> {
/**
* Before transaction completion function
*/
default void beforeTransactionCompletion() {
}
/**
* After transaction completion function
*/
default void afterTransactionCompletion() {
}
}
/**
* JPA transaction function without return value
*/
@FunctionalInterface
public interface JPATransactionVoidFunction
extends Consumer<EntityManager> {
/**
* Before transaction completion function
*/
default void beforeTransactionCompletion() {
}
/**
* After transaction completion function
*/
default void afterTransactionCompletion() {
}
}
/**
* Execute function in a JPA transaction
*
* @param factorySupplier EntityManagerFactory supplier
* @param function function
* @param properties properties for entity manager bootstrapping
* @param <T> result type
*
* @return result
*/
public static <T> T doInJPA(
Supplier<EntityManagerFactory> factorySupplier,
JPATransactionFunction<T> function,
Map properties) {
T result = null;
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = properties == null ?
factorySupplier.get().createEntityManager():
factorySupplier.get().createEntityManager(properties);
function.beforeTransactionCompletion();
txn = entityManager.getTransaction();
txn.begin();
result = function.apply( entityManager );
txn.commit();
}
catch ( Throwable e ) {
if ( txn != null && txn.isActive() ) {
txn.rollback();
}
throw e;
}
finally {
function.afterTransactionCompletion();
if ( entityManager != null ) {
entityManager.close();
}
}
return result;
}
/**
* Execute function in a JPA transaction
*
* @param factorySupplier EntityManagerFactory supplier
* @param function function
* @param <T> result type
*
* @return result
*/
public static <T> T doInJPA(
Supplier<EntityManagerFactory> factorySupplier,
JPATransactionFunction<T> function) {
return doInJPA( factorySupplier, function, null );
}
/**
* Execute function in a JPA transaction without return value
*
* @param factorySupplier EntityManagerFactory supplier
* @param function function
* @param properties properties for entity manager bootstrapping
*/
public static void doInJPA(
Supplier<EntityManagerFactory> factorySupplier,
JPATransactionVoidFunction function,
Map properties) {
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = properties == null ?
factorySupplier.get().createEntityManager():
factorySupplier.get().createEntityManager(properties);
function.beforeTransactionCompletion();
txn = entityManager.getTransaction();
txn.begin();
function.accept( entityManager );
if ( !txn.getRollbackOnly() ) {
txn.commit();
}
else {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
}
catch ( Throwable t ) {
if ( txn != null && txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
throw t;
}
finally {
function.afterTransactionCompletion();
if ( entityManager != null ) {
entityManager.close();
}
}
}
/**
* Execute function in a JPA transaction without return value
*
* @param factorySupplier EntityManagerFactory supplier
* @param function function
*/
public static void doInJPA(
Supplier<EntityManagerFactory> factorySupplier,
JPATransactionVoidFunction function) {
doInJPA( factorySupplier, function, null );
}
/**
* Execute function in a Hibernate transaction
*
* @param factorySupplier SessionFactory supplier
* @param function function
* @param <T> result type
*
* @return result
*/
public static <T> T doInHibernate(
Supplier<SessionFactory> factorySupplier,
HibernateTransactionFunction<T> function) {
T result = null;
Session session = null;
Transaction txn = null;
try {
session = factorySupplier.get().openSession();
function.beforeTransactionCompletion();
txn = session.beginTransaction();
result = function.apply( session );
if ( !txn.getRollbackOnly() ) {
txn.commit();
}
else {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
}
catch ( Throwable t ) {
if ( txn != null && txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
throw t;
}
finally {
function.afterTransactionCompletion();
if ( session != null ) {
session.close();
}
}
return result;
}
/**
* Execute function in a Hibernate transaction without return value
*
* @param factorySupplier SessionFactory supplier
* @param function function
*/
public static void doInHibernate(
Supplier<SessionFactory> factorySupplier,
HibernateTransactionConsumer function) {
Session session = null;
Transaction txn = null;
try {
session = factorySupplier.get().openSession();
function.beforeTransactionCompletion();
txn = session.beginTransaction();
function.accept( session );
if ( !txn.getRollbackOnly() ) {
txn.commit();
}
else {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
}
catch ( Throwable t ) {
if ( txn != null && txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
throw t;
}
finally {
function.afterTransactionCompletion();
if ( session != null ) {
session.close();
}
}
}
/**
* Execute function in a Hibernate transaction
*
* @param sessionBuilderSupplier SessionFactory supplier
* @param function function
* @param <T> result type
*
* @return result
*/
public static <T> T doInHibernateSessionBuilder(
Supplier<SessionBuilder> sessionBuilderSupplier,
HibernateTransactionFunction<T> function) {
T result = null;
Session session = null;
Transaction txn = null;
try {
session = sessionBuilderSupplier.get().openSession();
function.beforeTransactionCompletion();
txn = session.beginTransaction();
result = function.apply( session );
if ( !txn.getRollbackOnly() ) {
txn.commit();
}
else {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
}
catch ( Throwable t ) {
if ( txn != null && txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
throw t;
}
finally {
function.afterTransactionCompletion();
if ( session != null ) {
session.close();
}
}
return result;
}
/**
* Execute function in a Hibernate transaction without return value
*
* @param sessionBuilderSupplier SessionFactory supplier
* @param function function
*/
public static void doInHibernateSessionBuilder(
Supplier<SessionBuilder> sessionBuilderSupplier,
HibernateTransactionConsumer function) {
Session session = null;
Transaction txn = null;
try {
session = sessionBuilderSupplier.get().openSession();
function.beforeTransactionCompletion();
txn = session.beginTransaction();
function.accept( session );
if ( !txn.getRollbackOnly() ) {
txn.commit();
}
else {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
}
catch ( Throwable t ) {
if ( txn != null && txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception e) {
log.error( "Rollback failure", e );
}
}
throw t;
}
finally {
function.afterTransactionCompletion();
if ( session != null ) {
session.close();
}
}
}
/**
* Set Session or Statement timeout
* @param session Hibernate Session
*/
public static void setJdbcTimeout(Session session) {
setJdbcTimeout( session, TimeUnit.SECONDS.toMillis( 1 ) );
}
/**
* Set Session or Statement timeout
* @param session Hibernate Session
*/
public static void setJdbcTimeout(Session session, long millis) {
session.doWork( connection -> {
if ( Dialect.getDialect() instanceof PostgreSQL81Dialect ) {
try (Statement st = connection.createStatement()) {
//Prepared Statements fail for SET commands
st.execute(String.format( "SET statement_timeout TO %d", millis / 10));
}
}
else if( Dialect.getDialect() instanceof MySQLDialect ) {
try (PreparedStatement st = connection.prepareStatement("SET GLOBAL innodb_lock_wait_timeout = ?")) {
st.setLong( 1, TimeUnit.MILLISECONDS.toSeconds( millis ) );
st.execute();
}
}
else if( Dialect.getDialect() instanceof H2Dialect ) {
try (PreparedStatement st = connection.prepareStatement("SET LOCK_TIMEOUT ?")) {
st.setLong( 1, millis / 10 );
st.execute();
}
}
else {
try {
connection.setNetworkTimeout( Executors.newSingleThreadExecutor(), (int) millis );
}
catch (Throwable ignore) {
ignore.fillInStackTrace();
}
}
} );
}
}