26 tháng 7, 2017

Quản lý transaction trong Spring Framework - Spring @Transaction

Spring là một framework Java được sử dụng phổ biến nhất hiện nay, nó mang đến rất nhiều tính năng và nhiều phần bổ trợ cho các ứng dụng Java. Tuy nhiên, hầu hết mọi người đều có khuynh hướng sử dụng những tính năng này mà không thực sự hiểu cơ chế bên dưới của chúng.

1. Cách sử dụng và trường hợp sử dụng

@Transaction(value = "myTransactionManager", propagation = Propagation.REQUIRED)
public void myMethod() {
...
}

Thuộc tính value của annotation @Transaction không bắt buộc phải được khai báo. Nếu không khai báo thuộc tính này thì mặc định Spring sẽ tìm kiếm một bean bất kì được khai báo bên trong context có tên là "transactionManager" (đây là convention mặc định trong Spring).

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

2. Cấu hình sử dụng transaction trong Spring context

Muốn annotation @Transaction có thể hoạt động được, ta sẽ phải khai báo thẻ <tx:annotation-driven> (trong đó tx là rút gọn của namespace "http://www.springframework.org/schema/tx" hoặc cũng có thể tạm hiểu đây là một alias cho namespace này).

3. Phân tích code

3.1. Khai báo spring bean

Ở phần này, chúng ta sẽ xem xét cách mà Spring context xử lý khi khai báo sử dụng thẻ <tx:annotation-driven>

1, org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser

/**
* Parses the '<code></code>' tag. Will
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
* with the container as necessary.
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
  String mode = element.getAttribute("mode");
  if ("aspectj".equals(mode)) {
    // mode="aspectj"
    registerTransactionAspect(element, parserContext); 
  } else {
    // mode="proxy"
    // DEFAULT MODE
    AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
  }
  return null;
}

Hầu hết, chúng ta sẽ đều đi vào block else của block điều kiện if-else trong đoạn code bên trên (mode="proxy"), vì vậy chúng ta sẽ gọi AopAutoProxyConfigurer.configureAutoProxyCreator()

private static class AopAutoProxyConfigurer {
  public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
    AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
    if (!parserContext.getRegisty().containsBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME)) {
      Object eleSource = parserContext.extractSource(element);  
    }
    
    // Create the TransactionAttributeSource definition.
    RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationTransactionAttributeSource.class);
    sourceDef.setSource(eleSource);
    sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      
    // The bean AnnotationTransactionAttributeSource is created and registed dynamically here
    String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

    // Create the TransactionInterceptor definition.
    // Point A
    RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
    interceptorDef.setSource(eleSource);
    interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    // Set the declared transaction manager in   to the transactionInterceptor, when available
    registerTransactionManager(element, interceptorDef);
    interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));

    //  The bean TransactionInterceptor is created and registed dynamically here
    String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
 
    // Create the TransactionAttributeSourceAdvisor definition.
 
    // This bean is an AOP definition
    RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
    advisorDef.setSource(eleSource);
    advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
 
    // Inject the bean AnnotationTransactionAttributeSource into the AOP definition
    // Point B
    advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
 
    // Definition of advice bean = TransactionInterceptor previously declared
    advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
    if (element.hasAttribute("order")) {
       advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
    }
    //  The bean BeanFactoryTransactionAttributeSourceAdvisor is created and registed dynamically here
    parserContext.getRegistry().registerBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME, advisorDef);
 
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
    compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
    compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
    compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, TRANSACTION_ADVISOR_BEAN_NAME));
    parserContext.registerComponent(compositeDef);
  }

  // Retrieve the transactionManager attribute defined on  when available
  // Example
  private static void registerTransactionManager(Element element, BeanDefinition def) {
    def.getPropertyValues().add("transactionManagerBeanName",
            TxNamespaceHandler.getTransactionManagerName(element));
  }
}

Tạo lớp transaction interceptor:

// Point A
RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);

Khai báo transaction manager bằng thẻ <tx:annotation-driven>, transaction manager sẽ được tìm kiếm bên trong Spring context và kèm theo là transaction interceptor đã được định nghĩa trước đó:

// Set the declared transaction manager in   to the transactionInterceptor, when available
registerTransactionManager(element, interceptorDef);

Cuối cùng chúng ta sẽ đăng kí interceptorDef bean với Spring context:

//  The bean TransactionInterceptor is created and registed dynamically here
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

Khai báo bean TransactionAttributeSourceAdvisor và đăng kí với Spring context

// This bean is an AOP definition
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
 
// Inject the bean AnnotationTransactionAttributeSource into the AOP definition
// Point B
advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
 
// Definition of advice bean = TransactionInterceptor previously declared
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
  advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
//  The bean BeanFactoryTransactionAttributeSourceAdvisor is created and registed dynamically here
parserContext.getRegistry().registerBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME, advisorDef);

3.2. Phân tích @Transactional

1, org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor

Trong phần này, chúng ta sẽ xem cách mà annotaion @Transactional được phân tích trong suốt quá trình runtime bởi Spring để nhận lấy các thuộc tính liên quan đến một giao dịch (transaction)

// Injected at Point B
private TransactionAttributeSource transactionAttributeSource;
 
// Define a pointcut here for AOP, injecting the transactionAttributeSource that was
// set in AopAutoProxyConfigurer
// see Point B
private final TransactionAttributeSourcePointcut pointcut =
  new TransactionAttributeSourcePointcut() {
    @Override
    protected TransactionAttributeSource getTransactionAttributeSource() {
      return transactionAttributeSource;
    }
  };
Ở đây, Spring định nghĩa ra một pointcut advisor được xử lý bởi lớp TransactionAttributeSourcePointcut. transactionAttributeSource được sử dụng thông qua anonymous class bên trong phương thức được overide getTransactionAttributeSource().

2, Lớp trừu tượng org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut

abstract class org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut
 
  // Determine whether a call on a particular method matches the poincut
  // If it matches then the advice bean will be called
  // The advice bean that has been registered for this pointcut is the
  // TransactionInterceptor class (see Point A)
  public boolean matches(Method method, Class targetClass) {
    TransactionAttributeSource tas = getTransactionAttributeSource();
 
    // Call getTransactionAttribute of the injected transactionAttributeSoirce
    // (see Point C)
    return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
  }
Lớp trừu tượng này chỉ định nghĩa hành vi mặc định của phương thức matches() để kiểm tra xem các join point có phù hợp với point cut không.

3, org.springframework.transaction.annotation.AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource

// Should be false in a context of J2SE
private static final boolean ejb3Present = ClassUtils.isPresent("javax.ejb.TransactionAttribute",
                                AnnotationTransactionAttributeSource.class.getClassLoader());
 
  // Default constructor
  public AnnotationTransactionAttributeSource() {
    this(true);
  }
 
  // publicMethodsOnly = true because this bean has been registered dynamically
  // by AopAutoProxyConfigurer with no argument so the default constructor above applies
  //
  // ejb3Present = false
  public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
    this.publicMethodsOnly = publicMethodsOnly;
    this.annotationParsers = new LinkedHashSet(2);
    this.annotationParsers.add(new SpringTransactionAnnotationParser());
    if (ejb3Present) {
      this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
    }
  }
Hàm khởi tạo thứ hai của lớp AnnotationTransactionAttributeSource đăng kí SpringTransactionAnnotationParser là trình phân tích cú pháp mặc định cho annotation @Transaction

4, org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource

Point C & Point D

// Point C
public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) {
  // First, see if we have a cached value.
  Object cacheKey = getCacheKey(method, targetClass);
  Object cached = this.attributeCache.get(cacheKey);
  if (cached != null) {
    ....
    ....
    // Not interesting code
  } else {
    // We need to work it out.
    // (see Point D below)
    TransactionAttribute txAtt = computeTransactionAttribute(method, targetClass);
    // Put it in the cache.
    if (txAtt == null) {
      this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Adding transactional method '" + method.getName()
           + "' with attribute: " + txAtt);
      }
      this.attributeCache.put(cacheKey, txAtt);
    }
    return txAtt;
  }
}
 
// Point D
private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) {
  // Don't allow no-public methods as required.
  // Here allowPublicMethodsOnly() will return true because we set the attribute
  // publicMethodOnly = true in the constructor of AnnotationTransactionAttribute
  if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    return null;
  }
 
  // Ignore CGLIB subclasses - introspect the actual user class.
  Class userClass = ClassUtils.getUserClass(targetClass);
 
  // The method may be on an interface, but we need attributes from the target class.
  // If the target class is null, the method will be unchanged.
     Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
 
  // If we are dealing with method with generic parameters, find the original method.
  specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
 
  // Find the @Transactional attributes of the method in the target class.
  // (see Point E)
  TransactionAttribute txAtt = findTransactionAttribute(specificMethod);
  if (txAtt != null) {
    return txAtt;
  }
  ...
  // Not interesting code
}

Phương thức getTransactionAttribute() của lớp trừu tượng này sẽ ủy nhiệm công việc cho phương thức computeTransactionAttribute().

Đầu tiên, nó sẽ xác định lớp mục tiêu (trong trường hợp annotation này được đặt bên trên một phương thức của interface) sau đó sẽ gọi phương thức findTransactionAttribute().

5, org.springframework.transaction.annotation.AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource

Point E

// Point E
protected TransactionAttribute findTransactionAttribute(Method method) {
  // See below
  return determineTransactionAttribute(method);
}
 
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {
  for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
    // (see Point F)
    TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);
    if (attr != null) {
      return attr;
    }
  }
  return null;
}

Thêm một lần nữa, công việc thực sự vẫn sẽ chưa được thực hiện ở đây, nhưng nó sẽ được ủy quyền cho lớp phân tích annotation là SpringTransactionAnnotationParser đã được đăng kí từ trước đó bên trong hàm khởi tạo của lớp AnnotationTransactionAttributeSource.

6, org.springframework.transaction.annotation.SpringTransactionAnnotationParser

Point F

// Point F
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
  Transactional ann = ae.getAnnotation(Transactional.class);
  if (ann == null) {
    for (Annotation metaAnn : ae.getAnnotations()) {
      // @Transactional annotation
      ann = metaAnn.annotationType().getAnnotation(Transactional.class);
      if (ann != null) {
        break;
      }
    }
  }
  if (ann != null) {
    //See below
    return parseTransactionAnnotation(ann);
  } else {
    return null;
  }
}
 
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
  RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
  rbta.setPropagationBehavior(ann.propagation().value());
  rbta.setIsolationLevel(ann.isolation().value());
  rbta.setTimeout(ann.timeout());
  rbta.setReadOnly(ann.readOnly());
 
  /* Set qualifier name in the case multiple transaction managers are used
   * bean id="myTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
   *    property name="entityManagerFactory" ref="myEntityManagerFactory" /
   * /bean
   *
   * @Transactional(value = "myTransactionManager")
   */
  rbta.setQualifier(ann.value());
  ArrayList rollBackRules = new ArrayList();
  ...
  // Not interesting code
  return rbta;
}

Trình phân tích cú pháp sẽ lấy toàn bộ thuộc tính của annotation @Transaction, gồm:

  • propagation behavior
  • isolation level
  • giá trị timeout của transaction
  • readOnly flag
  • và thuộc tính quan trọng nhất là: value, thuộc tính này sẽ xác định tên của bean transactionManager đã được khai báo bên trong Spring context và chịu trách nhiệm đối với transaction hiện tại.
Nếu bỏ qua thuộc tính value, thì giá trị sẽ mặc định là "transactionManager". Khi phải làm việc cùng với nhiều database hoặc chia ra nhiều datasource, có nhiều hơn một transactionManager được khai báo trong Spring context thì thuộc tính value sẽ rất quan trọng để giúp Spring có thể chọn đúng transactionManager cần thiết.

3.3. Transactional interceptor

1, org.springframework.transaction.interceptor.TransactionInterceptor extends TransactionAspectSupport

// The real job is done here
public Object invoke(final MethodInvocation invocation) throws Throwable {
 
  // Work out the target class: may be null.
  // The TransactionAttributeSource should be passed the target class
  // as well as the method, which may be from an interface.
  Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
 
  // If the transaction attribute is null, the method is non-transactional.
  // Similar to Point C
  final TransactionAttribute txAttr =
            getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass); 
 
  // (see Point G)
  final PlatformTransactionManager tm = determineTransactionManager(txAttr);
  final String joinpointIdentification = methodIdentification(invocation.getMethod(), targetClass);
 
  // The txAttr is not null but the transactionManager is NOT an instance 
  // of CallbackPreferringPlatformTransactionManager so we still enter the if block
  if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
 
    // Standard transaction demarcation with getTransaction() and commit/rollback calls.
    // (see Point H) 
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); 
 
    Object retVal = null;
    try {
      // This is an around advice: Invoke the next interceptor in the chain.
      // This will normally result in a target object being invoked.
      retVal = invocation.proceed();
    } catch (Throwable ex) {
      // target invocation exception
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
    }
    finally {
      cleanupTransactionInfo(txInfo);
    }
    // Commit after the method call returns
    // (see Point O)
    commitTransactionAfterReturning(txInfo);
    return retVal;
}

  • Đầu tiên, Spring lấy ra các thuộc tính của transaction
//Similar to Point C
final TransactionAttribute txAttr =
           getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
  • Sau đó, lấy ra transactionManager từ Spring context và các thuộc tính của transaction
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
  • Một transaction được tạo ra bởi entityManager
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
  • Gọi phương thức mục tiêu
retVal = invocation.proceed();
  • Sau khi phương thức được gọi trả về kết quả, transaction sẽ được commit
commitTransactionAfterReturning(txInfo);

2, public abstract class org.springframework.transaction.interceptor.TransactionAspectSupport

Point G & Point H

//Point G
protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
  if (this.transactionManager != null || this.beanFactory == null || txAttr == null) {
    return this.transactionManager;
  }
  String qualifier = txAttr.getQualifier();
     
  // Case when the transaction manager has been declared directly in the @Transactional annotation
  //  Example @Transactional(value = "myTransactionManager")
  if (StringUtils.hasLength(qualifier)) {
    return TransactionAspectUtils.getTransactionManager(this.beanFactory, qualifier);
  }
 
  // Case when the transaction manager has been declared in the tx:annotation-driven tag
  //  Example tx:annotation driven transaction-manager="myTransactionManager"
  else if (this.transactionManagerBeanName != null) {
    return this.beanFactory.getBean(this.transactionManagerBeanName,
                                    PlatformTransactionManager.class);
  }
  ...
  // Not interesting code      
}
 
// Point H
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, 
                                                      TransactionAttribute txAttr, 
                                                      final String joinpointIdentification) {
  // If no name specified, apply method identification as transaction name.
  // This is the default case
  if (txAttr != null && txAttr.getName() == null) {
    txAttr = new DelegatingTransactionAttribute(txAttr) {
               @Override
               public String getName() {
                 return joinpointIdentification;
               }
             };
  }
 
  TransactionStatus status = null;
  if (txAttr != null) {
    if (tm != null) {
      // Call to the AbstractPlatFormTransactionManager to start a transaction
      // (see Point I)
      status = tm.getTransaction(txAttr);
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                     "] because no transaction manager has been configured");
      }
    }
  }
  return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

Lớp này sẽ thực hiện hai nhiệm vụ chính:

  • Xác định transactionManager để quản lí transaction hiện tại, bằng cách sử dụng thuộc tính value thuộc annotation @Transaction hoặc sử dụng thuộc tính transaction-manager của thẻ <tx:annotation-driven>.
  • ủy quyền tạo transaction cho lớp AbstractPlatFormTransactionManager.

3, abstract class org.springframework.transaction.support.AbstractPlatformTransactionManager

Point I

//Point I
public final TransactionStatus getTransaction(TransactionDefinition definition) throws
TransactionException {
  // Retrieve the transaction from JpaTransactionManager.doGetTransaction()
  // (see Point J)
  bject transaction = doGetTransaction();
 
  // Cache debug flag to avoid repeated checks.
  boolean debugEnabled = logger.isDebugEnabled();
 
  if (definition == null) {
    // Use defaults if no transaction definition given.
    definition = new DefaultTransactionDefinition();
  }
 
  if (isExistingTransaction(transaction)) {
    // Existing transaction found -> check propagation behavior to find out how to behave.
    return handleExistingTransaction(definition, transaction, debugEnabled);
  }
 
  ...
 
  // No existing transaction found -> check propagation behavior to find out how to proceed.
  if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
          throw new IllegalTransactionStateException(
              "No existing transaction found for transaction marked with propagation 'mandatory'");
  }
  // Our case
  else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
    definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
    definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    SuspendedResourcesHolder suspendedResources = suspend(null);
    if (debugEnabled) {
      logger.debug("Creating new transaction with name [" + definition.getName() 
                   + "]: " + definition);
    }
    try {
      boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
      /*
       * Return a new DefaultTransactionStatus(transaction,             
       *                                       newTransaction = true,
       *                                       newSynchronization = true,
       *                                       definition.isReadOnly(),debugEnabled,
       *                                       suspendedResources)
       *  for a new transaction
       */
      DefaultTransactionStatus status = newTransactionStatus(
                  definition, transaction, true, newSynchronization, 
                  debugEnabled, suspendedResources);
              
      // Real job here, delegates call to JpaTransactionManager.doBegin()
      // (see Point K)
      doBegin(transaction, definition); 
              
      // Set some synchronization flags to the TransactionSynchronizationManager thread local
      prepareSynchronization(status, definition);
      return status;
    } catch (RuntimeException ex) {
      resume(null, suspendedResources);
      throw ex;
    } catch (Error err) {
        resume(null, suspendedResources);
        throw err;
    }
  } else {
    // TransactionDefinition = PROPAGATION_SUPPORTS or PROPAGATION_NOT_SUPPORTED
    //                         or PROPAGATION_NEVER
    // Create "empty" transaction: no actual transaction, but potentially synchronization.
    boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
  }
}

Phương thức getTransaction() tự ủy nhiệm việc tạo và bắt đầu transaction cho JpaTransactionManager

Chúng ta có thể thấy cách mà Spring quản lý các kiểu Propagation behavior khác nhau.

4, org.springframework.orm.jpa.JpaTransactionManager

Point J & Point K

// Point J
protected Object doGetTransaction() {
      JpaTransactionObject txObject = new JpaTransactionObject();
      txObject.setSavepointAllowed(isNestedTransactionAllowed());
 
      // Try to retrieve an EntityManagerHolder from the thread local map of 
      // TransactionSynchronizationManager using the EntityManagerFactory as search key
      // The EntityManagerFactory was injected in the JpaTransactionManager in the XML config file
      //
      // bean id="myTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
      //     property name="entityManagerFactory" ref="myEntityManagerFactory" 
      // bean
      //
      //
      
      // this EntityManagerHolder might be null when called the first time
      EntityManagerHolder emHolder = (EntityManagerHolder)
              TransactionSynchronizationManager.getResource(getEntityManagerFactory());
      if (emHolder != null) {
          if (logger.isDebugEnabled()) {
              logger.debug("Found thread-bound EntityManager [" +
                      emHolder.getEntityManager() + "] for JPA transaction");
          }
          // attach the EntityManagerHolder to the JpaTransactionObject
          // the flag false is set to the property newEntityManagerHolder
          txObject.setEntityManagerHolder(emHolder, false);
      }
 
      // The datasource is injected directly into the JpaTransactionManager 
      // after bean initialization (afterPropertySet())
      // by inspecting the injected EntityManagerFactory
      //
      //  bean id="myEntityManagerFactory" 
      //       class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
      //       property name="dataSource" ref="myDataSource"
      //       property name="persistenceUnitName" value="myPersistenceUnit"
      //  bean
      //
      //
 
      // this test always evaluates to true
      if (getDataSource() != null) {
          ConnectionHolder conHolder = (ConnectionHolder)
                  TransactionSynchronizationManager.getResource(getDataSource());
           // attach a connectionHolder to the JpaTransactionObject (to start JDBC transaction probably)
          txObject.setConnectionHolder(conHolder);
      }
      return txObject;
  }
 
//Point K
protected void doBegin(Object transaction, TransactionDefinition definition) {
      JpaTransactionObject txObject = (JpaTransactionObject) transaction;
 
      ...
 
      try {
          
          // The EntityManagerHolder can be null if not registered already in the 
          // thread local map of TransactionSynchronizationManager
          if (txObject.getEntityManagerHolder() == null ||
                  txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
              // Create a new EntityManager from the EntityManagerFactory 
              EntityManager newEm = createEntityManagerForTransaction();
              if (logger.isDebugEnabled()) {
                  logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
              }
 
              // attach the EntityManagerHolder to the JpaTransactionObject
              // newEntityManagerHolder = true
              // because the EntityManager has just been created from scratch
              txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
          }
 
          EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
          final int timeoutToUse = determineTimeout(definition);
 
         /* Delegate to JpaDialect for actual transaction begin, passing the EntityManager
          *  
          *   META-INF|persistence.xml
          *
          *   persistence-unit name="myPersistenceUnit" transaction-type="RESOURCE_LOCAL"
          *     provider 
          *         org.hibernate.ejb.HibernatePersistence 
          *     provider
          *     properties
          *         property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect"
          *     ... 
          */
 
          // (see Point L for HibernateJpaDialect)
          Object transactionData = getJpaDialect().beginTransaction(em,
                  new DelegatingTransactionDefinition(definition) {
                      @Override
                      public int getTimeout() {
                          return timeoutToUse;
                      }
                  });
 
          // Set transaction data to the JpaTransactionObject
          txObject.setTransactionData(transactionData);
 
          // Register transaction timeout.
          if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
              txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
          }
 
          // Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
          if (getDataSource() != null) {
              // Retrieve the underlying JDBC connection by calling the JPA Dialect class   
              ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
              if (conHandle != null) {
                  ConnectionHolder conHolder = new ConnectionHolder(conHandle);
                  if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
                      conHolder.setTimeoutInSeconds(timeoutToUse);
                  }
                  if (logger.isDebugEnabled()) {
                      logger.debug("Exposing JPA transaction as JDBC transaction [" + conHolder.getConnectionHandle() + "]");
                  }
 
                  // Set the JDBC connection to the current Threadlocal resources map, the 
                  // datasource being the key                    
                  TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
                  
                  // Set JDBC connection holder to the JpaTransactionObject
                  txObject.setConnectionHolder(conHolder);
              }
              else {
                  if (logger.isDebugEnabled()) {
                      logger.debug("Not exposing JPA transaction [" + em 
                                   + "] as JDBC transaction because JpaDialect [" +
                              getJpaDialect() + "] does not support JDBC Connection retrieval");
                  }
              }
          }
 
          // If the EntityManager has been created from scratch (see Point L)
          if (txObject.isNewEntityManagerHolder()) {
 
               // register the EntityManagerHolder to the current Threadlocal resources map, the EntityManagerFactory being the key
              TransactionSynchronizationManager.bindResource(
                      getEntityManagerFactory(), txObject.getEntityManagerHolder());
          }
          txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
      }
 
      catch (TransactionException ex) {
          closeEntityManagerAfterFailedBegin(txObject);
          throw ex;
      }
      catch (Exception ex) {
          closeEntityManagerAfterFailedBegin(txObject);
          throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
      }
  }

Công việc quan trọng nhất sẽ được hoàn thành bên trong lớp này.

Point J: doGetTransaction()

  • Đầu tiên, Spring sẽ thử tìm trong map ThreadLocal TransactionSynchronizationManager để tìm xem nếu có một entityManager đang tồn tại thì sẽ sử dụng entityManager factory như là khóa tìm kiếm.

1 nhận xét: