Skip to content

Instantly share code, notes, and snippets.

@lefou
Created May 3, 2024 11:36
Show Gist options
  • Save lefou/bd625e7e1a00dfeb63546e600ebeed4e to your computer and use it in GitHub Desktop.
Save lefou/bd625e7e1a00dfeb63546e600ebeed4e to your computer and use it in GitHub Desktop.
package someorg.aspects;
import de.tototec.utils.functional.Try;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.aspectj.lang.Aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import someorg.support.dao.jpa.NoTransactionBelow;
import someorg.support.dao.jpa.NoTransactionOnThisLevel;
import someorg.util.jpa.InjectEntityManager;
@Configurable(autowire = Autowire.BY_TYPE)
@Aspect("percflow(EntityPersistenceAspect.transactions())")
public class EntityPersistenceAspect {
public static EntityPersistenceAspect aspectOf() {
return Aspects.aspectOf(EntityPersistenceAspect.class);
}
private Logger log = LoggerFactory.getLogger(EntityPersistenceAspect.class);
private PlatformTransactionManager transactionManager;
private TransactionTemplate txTemplate;
private EntityManager entityManager;
@Autowired
public void setTransactionManager(PlatformTransactionManager transactionManager) {
log.debug("Setting transaction manager [{}] into self [{}]", transactionManager, this);
this.transactionManager = transactionManager;
this.txTemplate = new TransactionTemplate(transactionManager);
}
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
@Pointcut(
"(" // 1
+ "(" // (2
+ "execution(* someorg.support.dao.jpa.JpaDao+.*(..))"
+ " && "
+ "!execution(static * someorg.support.dao.jpa.JpaDao+.*(..))"
+ ")" // 2)
+ " && "
+ "!(" // (2
+ "(" // (3
+ "execution(* someorg.support.dao.jpa.JpaDao.*(..))"
+ " || "
+ "execution(* someorg.support.dao.jpa.EntityDao.*(..))"
+ ")" // 3)
+ " && "
+ "!execution(* someorg.support.dao.ObjectRelationalDao.*(..))"
+ ")" // 2)
+ ")" // 1)
)
void entityDaoOperations() {}
@Pointcut("someorg.aspects.SystemArchitectureAspect.businessServices()")
void businessServices() {}
@Pointcut(
"(" // (1
+ "entityDaoOperations()"
+ " || "
+ "businessServices()"
+ ")" // 1)
+ " && "
+ "!noTransactionBoundaries()"
+ " && "
+ "!neverTransactionBoundaries()"
+ " && "
+ "!cflowbelow(neverTransactionBoundaries())"
)
void sessionBoundaries() {}
@Pointcut(
"execution(@someorg.support.dao.jpa.NoTransactionOnThisLevel * *(..)) || execution(* (@someorg.support.dao.jpa.NoTransactionOnThisLevel *).*(..))"
)
void noTransactionBoundaries() {}
// never match TransactionCallback methods, as these are obviously already managed
// execution(* TransactionCallback+.*(..));
@Pointcut(
"execution(@someorg.support.dao.jpa.NoTransactionBelow * *(..)) || execution(* (@someorg.support.dao.jpa.NoTransactionBelow *).*(..))"
)
void neverTransactionBoundaries() {}
/**
* Note: only first level
*/
@Pointcut(
"!injectEntityManager() && sessionBoundaries() && !cflowbelow(sessionBoundaries())"
)
void transactions() {}
/**
* Execute methods in a transactional context.
*/
@Around("transactions()")
public Object aroundTransactions(final ProceedingJoinPoint joinPoint) {
return txTemplate.execute(transactionStatus -> {
log.debug("Executing AspectJ-wrapped Spring-managed transaction for: {}", joinPoint.getStaticPart());
return Try.of(() -> joinPoint.proceed()).get();
});
}
@Pointcut("execution(@someorg.util.jpa.InjectEntityManager jakarta.persistence.EntityManager getEntityManager())")
void injectEntityManager() {}
/**
* This is an experimental way, to inject the persistenceContext in any place.
*/
@Around("injectEntityManager()")
public EntityManager aroundInjectEntityManager(final ProceedingJoinPoint joinPoint) throws Throwable {
EntityManager em = (EntityManager) joinPoint.proceed();
if (em == null) {
log.debug("Returning AspectJ-configured entity manager: {}", entityManager);
em = entityManager;
}
return em;
}
// // FIXME: use more fine-grained pointcut to avoid them on annotations
// @Pointcut("call(* jakarta.persistence ..*.*(..))")
// void persistenceApiCall() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment