Automatically register XA Resource Spring Boot

2019-07-28 04:12发布

I'm trying to implement XA transactions in my Spring Boot app across Hazelcast and JPA persisting to PostgreSQL. Putting the Atomikos Spring Boot starter in my pom.xml got it to load the JtaTransactionManager to be used with the @Transactional annotations, but the Hazelcast XA Resource is not being enlisted with the transaction.

How do I get Spring Boot to automatically enlist my XA Resources with the JTA UserTransaction as part of the AOP transaction interceptor that's using the JtaTransactionManager?

1条回答
家丑人穷心不美
2楼-- · 2019-07-28 05:09

I solved this by using an annotation and AspectJ Aspect as described here. Also see this for defining the pointcuts to match either class or method level annotations, and you may need to do this:

@EnableTransactionManagement(order = Ordered.HIGHEST_PRECEDENCE) 

to have the transaction interceptor happen before this code is called.

@Aspect
@Component
public class XAResourceAspect {
  @Autowired
  JtaTransactionManager jtaTransactionManager;

  @Autowired
  ApplicationContext applicationContext;

  @Pointcut("within(@XAResource *)")
  public void beanAnnotatedWithAnnotation() {}

  @Pointcut("execution(public * *(..))")
  public void publicMethod() {}

 @Pointcut("publicMethod() && beanAnnotatedWithAnnotation()")
  public void publicMethodInsideAnnotatedClass() {}

  private ThreadLocal<Map<Transaction, Set<String>>> enlistedResources = new ThreadLocal<>();

  @Around("@annotation(ppi.nestup.v3.annotation.XAResource) || publicMethodInsideAnnotatedClass()")
  public Object enlistResources(ProceedingJoinPoint joinPoint) throws Throwable {
    boolean setThreadLocal = false;
    Transaction transaction = jtaTransactionManager.getTransactionManager().getTransaction();
    if (transaction != null) {
      Map<Transaction, Set<String>> transactionMap = enlistedResources.get();
      LOG.info("Enlisting resources for joinpoint " + joinPoint + " and transaction " + transaction);
      if (transactionMap == null) {
        transactionMap = new HashMap<>();
        enlistedResources.set(transactionMap);
        setThreadLocal = true;
        LOG.info("Created new ThreadLocal for transaction " + transaction);
      } else {
        LOG.info("Found existing ThreadLocal " + transactionMap);
      }
      transactionMap.computeIfAbsent(transaction, k -> new HashSet<>());
      MethodSignature signature = (MethodSignature) joinPoint.getSignature();
      Method method = signature.getMethod();
      Class withinType = joinPoint.getSourceLocation().getWithinType();

      XAResource annotation = method.getAnnotation(XAResource.class);
      if (annotation == null) {
        annotation = (XAResource) withinType.getAnnotation(XAResource.class);
      }
      String[] resourceNames = annotation.value();
      for (String name : resourceNames) {
        if (!transactionMap.get(transaction).contains(name)) {
          javax.transaction.xa.XAResource resource =
            (javax.transaction.xa.XAResource) applicationContext.getBean(name);
          try {
            transaction.enlistResource(resource);
          } catch (IllegalStateException e) {
            LOG.error("Caught exception trying to enlist resource " + name + " for transaction " + transaction + " and joinpoint " + joinPoint);
            e.printStackTrace();
          }
          transactionMap.get(transaction).add(name);
        }
      }
    }

    Object proceed = joinPoint.proceed();
    if (setThreadLocal) {
      LOG.info("Removing threadlocal");
      enlistedResources.remove();
    }
    return proceed;
  }
}

I haven't done a lot of testing of this yet, but it's working so far.

查看更多
登录 后发表回答