Created
January 21, 2016 13:58
-
-
Save vbezhenar/5625ba87e5bb8f862668 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.persistence.EntityManager; | |
import javax.persistence.EntityManagerFactory; | |
import java.sql.SQLException; | |
import java.util.Random; | |
public class EntityManagerUtil { | |
@FunctionalInterface | |
public interface TransactionalAction { | |
void action(EntityManager entityManager) throws Exception; | |
} | |
@FunctionalInterface | |
public interface TransactionalFunction<T> { | |
T apply(EntityManager entityManager) throws Exception; | |
} | |
@FunctionalInterface | |
public interface RetryRequired { | |
boolean required(Exception e); | |
} | |
@FunctionalInterface | |
public interface RetryIntervalFunction { | |
int interval(int currentRetryNumber, int maxRetryCount); | |
} | |
public static final int DEFAULT_RETRY_COUNT = 10; | |
private static EntityManagerFactory entityManagerFactory; | |
public static void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { | |
EntityManagerUtil.entityManagerFactory = entityManagerFactory; | |
} | |
public static <T> T txFunction(TransactionalFunction<T> function, | |
RetryRequired retryRequired, | |
int maxRetryCount, RetryIntervalFunction retryIntervalFunction) { | |
EntityManager entityManager = entityManagerFactory.createEntityManager(); | |
entityManager.getTransaction().begin(); | |
int retryNumber = 0; | |
while (true) { | |
T result = null; | |
Exception exception = null; | |
try { | |
result = function.apply(entityManager); | |
} catch (Exception e) { | |
exception = e; | |
entityManager.getTransaction().setRollbackOnly(); | |
} | |
try { | |
if (entityManager.getTransaction().getRollbackOnly()) { | |
entityManager.getTransaction().rollback(); | |
} else { | |
entityManager.getTransaction().commit(); | |
} | |
entityManager.close(); | |
} catch (Exception e) { | |
if (exception != null) { | |
exception.addSuppressed(e); | |
} else { | |
exception = e; | |
} | |
} | |
if (exception != null) { | |
if (retryNumber >= maxRetryCount) { | |
throw runtimeException(exception); | |
} | |
if (retryRequired.required(exception)) { | |
try { | |
Thread.sleep(retryIntervalFunction.interval(retryNumber, maxRetryCount)); | |
} catch (InterruptedException ignored) { | |
Thread.currentThread().interrupt(); | |
throw runtimeException(exception); | |
} | |
retryNumber++; | |
} else { | |
throw runtimeException(exception); | |
} | |
} else { | |
return result; | |
} | |
} | |
} | |
public static <T> T txFunction(TransactionalFunction<T> function) { | |
return txFunction(function, | |
EntityManagerUtil::defaultRetryRequired, | |
DEFAULT_RETRY_COUNT, EntityManagerUtil::defaultRetryInterval); | |
} | |
public static void txAction(TransactionalAction action) { | |
txFunction(new TransactionActionWrapper(action), | |
EntityManagerUtil::defaultRetryRequired, | |
DEFAULT_RETRY_COUNT, EntityManagerUtil::defaultRetryInterval); | |
} | |
private static RuntimeException runtimeException(Exception e) { | |
return e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e.getMessage(), e); | |
} | |
private static final Random DEFAULT_RETRY_INTERVAL_RANDOM = new Random(); | |
public static int defaultRetryInterval(int currentRetryNumber, int maxRetryCount) { | |
return (maxRetryCount - currentRetryNumber) * 1000 + DEFAULT_RETRY_INTERVAL_RANDOM.nextInt(1000); | |
} | |
public static boolean defaultRetryRequired(Throwable exception) { | |
if (exception == null) { | |
return false; | |
} | |
if (exception instanceof SQLException) { | |
SQLException sqlException = (SQLException) exception; | |
if ("40P01".equals(sqlException.getSQLState())) { | |
return true; | |
} | |
} | |
if (defaultRetryRequired(exception.getCause())) { | |
return true; | |
} | |
for (Throwable suppressedException : exception.getSuppressed()) { | |
if (defaultRetryRequired(suppressedException)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
public static class TransactionActionWrapper implements TransactionalFunction<Void> { | |
private final TransactionalAction action; | |
public TransactionActionWrapper(TransactionalAction action) { | |
this.action = action; | |
} | |
@Override | |
public Void apply(EntityManager entityManager) throws Exception { | |
action.action(entityManager); | |
return null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment