Created
May 3, 2024 11:36
-
-
Save lefou/bd625e7e1a00dfeb63546e600ebeed4e to your computer and use it in GitHub Desktop.
This file contains 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
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