preface

This chapter learns the principles of ShardingJDBC transactions.

  • Transaction class
  • Local transactions
  • Global transactions: transaction engine, transaction manager, declarative transactions
  • How does ShardingJDBC combine with SpringAOP to implement annotated transactions

Support transaction types

public enum TransactionType {
    LOCAL, XA, BASE
}
Copy the code

Currently sharding-JDBC is divided into three types of transactions:

  • LOCAL: indicates that distributed transactions are not supported.
  • XA: XA transaction, two-phase commit, strong consistency. The atomikos framework is implemented by default.
  • BASE: BASE flexible transactions, ultimately consistent, implemented using Seata.

Local affairs

Local transactions in the user performs ShardingConnection. SetAutoCommit (false) will be recorded. See ShardingConnection parent AbstractConnectionAdapter# setAutoCommit implementation.

// ShardingConnection Holds cached connections
private final Multimap<String, Connection> cachedConnections = LinkedHashMultimap.create();
@Override
public void setAutoCommit(final boolean autoCommit) throws SQLException {
    this.autoCommit = autoCommit;
    setAutoCommitForLocalTransaction(autoCommit);
}

private void setAutoCommitForLocalTransaction(final boolean autoCommit) throws SQLException {
    // Log this method call
    recordMethodInvocation(Connection.class, "setAutoCommit".new Class[]{boolean.class}, new Object[]{autoCommit});
    // If there is a cached connection, execute it directly
    forceExecuteTemplate.execute(cachedConnections.values(), connection -> connection.setAutoCommit(autoCommit));
}
Copy the code

The recordMethodInvocation is a method implemented by the abstract parent WrapperAdapter class, which records a method invocation.

The user performs ShardingPrepareStatement. The execute (or executeUpdate), will create a real database connection, then all will replay recordMethodInvocation record of reflection calls. See AbstractConnectionAdapter# createConnections.

private List<Connection> createConnections(final String dataSourceName, final ConnectionMode connectionMode, final DataSource dataSource, final int connectionSize) throws SQLException {
    if (1 == connectionSize) {
        Connection connection = createConnection(dataSourceName, dataSource);
        // Replay the connection method call
        replayMethodsInvocation(connection);
        return Collections.singletonList(connection);
    }
    // Connection restriction
    if (ConnectionMode.CONNECTION_STRICTLY == connectionMode) {
        return createConnections(dataSourceName, dataSource, connectionSize);
    }
    // Memory limit
    synchronized (dataSource) {
        returncreateConnections(dataSourceName, dataSource, connectionSize); }}private List<Connection> createConnections(final String dataSourceName, final DataSource dataSource, final int connectionSize) throws SQLException {
    List<Connection> result = new ArrayList<>(connectionSize);
    for (int i = 0; i < connectionSize; i++) {
            Connection connection = createConnection(dataSourceName, dataSource);
            // Replay the connection method call
            replayMethodsInvocation(connection);
            result.add(connection);
    }
    return result;
}
Copy the code

Replaymethod Invocation is a method implemented by the Abstract parent WrapperAdapter that replays the recorded method invocation via reflection. Create a ShardingDataSource.

Finally, when the user calls the COMMIT method of ShardingConnection, the commit method is executed over all the actual connections. See ShardingConnection AbstractConnectionAdapter commit to implement the parent class.

@Override
public void commit(a) throws SQLException {
    forceExecuteTemplate.execute(cachedConnections.values(), Connection::commit);
}
Copy the code

Transaction management engine

ShardingTransactionManagerEngine transaction management engine, is responsible for creating the transaction manager and the transaction manager initialization, and provide access to external transaction manager.

The time to load the transaction management engine

When creating a ShardingDataSource, you need to create a ShardingRuntimeContext context. And ShardingRuntimeContext created when creating the ShardingTransactionManagerEngine and execution engine initialization method.

public final class ShardingRuntimeContext extends MultipleDataSourcesRuntimeContext<ShardingRule> {
    
    private final CachedDatabaseMetaData cachedDatabaseMetaData;
    
    private final ShardingTransactionManagerEngine shardingTransactionManagerEngine;
    
    public ShardingRuntimeContext(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Properties props, final DatabaseType databaseType) throws SQLException {
        super(dataSourceMap, shardingRule, props, databaseType);
        cachedDatabaseMetaData = createCachedDatabaseMetaData(dataSourceMap);
        / / instantiate ShardingTransactionManagerEngine
        shardingTransactionManagerEngine = new ShardingTransactionManagerEngine();
        / / initialize ShardingTransactionManagerEngineshardingTransactionManagerEngine.init(databaseType, dataSourceMap); }}Copy the code

Load and initialize the transaction management engine

ShardingTransactionManagerEngine structure through the SPI mechanism when loading ShardingTransactionManager instance, into the transactionManagerMap. In the same ShardingDataSource, one TransactionType, there would be only one ShardingTransactionManager instance, can generally think ShardingTransactionManager instances exist in singleton.

public final class ShardingTransactionManagerEngine {
    // TransactionType - ShardingTransactionManager
    private final Map<TransactionType, ShardingTransactionManager> transactionManagerMap = new EnumMap<>(TransactionType.class);
    
    public ShardingTransactionManagerEngine(a) {
        loadShardingTransactionManager();
    }

    / / SPI load ShardingTransactionManager
    private void loadShardingTransactionManager(a) {
        for (ShardingTransactionManager each : ServiceLoader.load(ShardingTransactionManager.class)) {
            / / the same TransactionType get loaded into the first ShardingTransactionManager implementation
            if (transactionManagerMap.containsKey(each.getTransactionType())) {
                continue; } transactionManagerMap.put(each.getTransactionType(), each); }}}Copy the code

ShardingTransactionManagerEngine initialization, circulation perform all ShardingTransactionManager init method, the original of the DataSource encapsulated as ResourceDataSource incoming.

public void init(final DatabaseType databaseType, final Map<String, DataSource> dataSourceMap) {
    for (Entry<TransactionType, ShardingTransactionManager> entry : transactionManagerMap.entrySet()) {
        Collection<ResourceDataSource> resourceDataSources = getResourceDataSources(dataSourceMap);
        / / execution ShardingTransactionManager init methodentry.getValue().init(databaseType, resourceDataSources); }}Copy the code

The ResourceDataSource encapsulates the real DataSource and logical DataSource names, with an additional unique identifier for the DataSource.

@Getter
public final class ResourceDataSource {
    // Logical data source name
    private final String originalName;
    // Unique identifier
    private String uniqueResourceName;
    // Real data source
    private final DataSource dataSource;
    
    public ResourceDataSource(final String originalName, final DataSource dataSource) {
        this.originalName = originalName;
        this.dataSource = dataSource;
        this.uniqueResourceName = ResourceIDGenerator.getInstance().nextId() + originalName; }}Copy the code

What does the transaction management engine do

The main function of the transaction management engine is to return the corresponding transaction manager (policy) based on the externally provided transaction type. Null if it is a local transaction.

public ShardingTransactionManager getTransactionManager(final TransactionType transactionType) {
    ShardingTransactionManager result = transactionManagerMap.get(transactionType);
    if(TransactionType.LOCAL ! = transactionType) { Preconditions.checkNotNull(result,"Cannot find transaction manager of [%s]", transactionType);
    }
    return result;
}
Copy the code

Transaction manager

ShardingTransactionManager transaction manager, for the local transaction, whether the XA or BASE, needs to implement this interface provide distributed transaction management.

public interface ShardingTransactionManager extends AutoCloseable {
    
    void init(DatabaseType databaseType, Collection<ResourceDataSource> resourceDataSources);
    
    TransactionType getTransactionType(a);
    
    boolean isInTransaction(a);
    
    Connection getConnection(String dataSourceName) throws SQLException;
    
    void begin(a);
    
    void commit(a);
    
    void rollback(a);
}
Copy the code

1, the method

  • Init: transaction manager initialization, called when the ShardingDataSource is created.
  • GetTransactionType: Returns the supported transaction type and provides it to the transaction management engine to do the policy.
  • IsInTransaction: Determines whether a transaction is in progress.
  • GetConnection: Obtains the Connection.
  • Begin, commit, and rollback: start, commit, and rollback transactions.

Focus on the call timing of a few methods.

isInTransaction

IsInTransaction is called when ShardingConnection sets autoCOMMIT and is primarily used to determine whether the transaction manager’s COMMIT and BEGIN methods need to be executed.

public final class ShardingConnection extends AbstractConnectionAdapter {
    
    private final TransactionType transactionType;
    
    private final ShardingTransactionManager shardingTransactionManager;
    
    @Override
    public void setAutoCommit(final boolean autoCommit) throws SQLException {
        // Local transaction
        if (TransactionType.LOCAL == transactionType) {
            super.setAutoCommit(autoCommit);
            return;
        }
        // Start or commit a transaction repeatedly, without performing any action
        if(autoCommit && ! shardingTransactionManager.isInTransaction() || ! autoCommit && shardingTransactionManager.isInTransaction()) {return;
        }
        // Commit the transaction
        if (autoCommit && shardingTransactionManager.isInTransaction()) {
            shardingTransactionManager.commit();
            return;
        }
        // Start the transaction
        if (!autoCommit && !shardingTransactionManager.isInTransaction()) {
            closeCachedConnections();
            shardingTransactionManager.begin();
        }
    }
}
Copy the code

getConnection

The getConnection method must be executed after the three steps of parsing, routing, and rewriting, and the Connection needs to be obtained during the execution phase. ShardingPreparedStatement# initPreparedStatementExecutor steps of the execute method will get connected.

public boolean execute(a) throws SQLException {
  try {
      // Resource cleanup
      clearPrevious();
      // Parse route rewrite
      prepare();
      / / initialize PreparedStatementExecutor this step will get connected
      initPreparedStatementExecutor();
      / / SQL execution
      return preparedStatementExecutor.execute();
  } finally {
      // Resource cleanupclearBatch(); }}Copy the code

If caching of ShardingConnection AbstractConnectionAdapter# cachedConnections enough to execute SQL, will eventually call ShardingConnection# createConnection.

protected Connection createConnection(final String dataSourceName, final DataSource dataSource) throws SQLException {
    return isInShardingTransaction() ? shardingTransactionManager.getConnection(dataSourceName) : dataSource.getConnection();
}
Copy the code

Begin, COMMIT, and rollback

Begin: Triggered when the user calls ShardingConnect #setAutoCommit.

Commit: Triggered when the user calls shardingConnect #commit.

public void commit(a) throws SQLException {
    if (TransactionType.LOCAL == transactionType) {
        super.commit();
    } else{ shardingTransactionManager.commit(); }}Copy the code

Rollback: Triggered when the user calls shardingConnect #rollback.

public void rollback(a) throws SQLException {
  if (TransactionType.LOCAL == transactionType) {
      super.rollback();
  } else{ shardingTransactionManager.rollback(); }}Copy the code

2, implementation,

XAShardingTransactionManager

XAShardingTransactionManager responsible for XA transaction management implementation.

public final class XAShardingTransactionManager implements ShardingTransactionManager {
    // Logical datasource name -xatransactiondatasource
    private final Map<String, XATransactionDataSource> cachedDataSources = new HashMap<>();
    // THE SPI mechanism loads the implementation of XATransactionManager
    private final XATransactionManager xaTransactionManager = XATransactionManagerLoader.getInstance().getTransactionManager();
    
    @Override
    public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
        for (ResourceDataSource each : resourceDataSources) {
            XATransactionDataSource dataSource = new XATransactionDataSource(databaseType, each.getUniqueResourceName(), each.getDataSource(), xaTransactionManager);
            cachedDataSources.put(each.getOriginalName(), dataSource);
        }
        xaTransactionManager.init();
    }
    
    @Override
    public TransactionType getTransactionType(a) {
        return TransactionType.XA;
    }
    
    @Override
    public boolean isInTransaction(a) {
        returnStatus.STATUS_NO_TRANSACTION ! = xaTransactionManager.getTransactionManager().getStatus(); }@Override
    public Connection getConnection(final String dataSourceName) throws SQLException {
        return cachedDataSources.get(dataSourceName).getConnection();
    }
    
    @Override
    public void begin(a) {
        xaTransactionManager.getTransactionManager().begin();
    }
    
    @Override
    public void commit(a) {
        xaTransactionManager.getTransactionManager().commit();
    }
    
    @Override
    public void rollback(a) {
        xaTransactionManager.getTransactionManager().rollback();
    }
    
    @Override
    public void close(a) throws Exception {
        for(XATransactionDataSource each : cachedDataSources.values()) { each.close(); } cachedDataSources.clear(); xaTransactionManager.close(); }}Copy the code

XAShardingTransactionManager transaction control (the begin, commit and rollback) is entrusted org. Apache. Shardingsphere., xa. Spi. XATransactionM Anager held javax.mail. Transaction. TransactionManager processing. XATransactionManager sharding – is in fact the JDBC adapter layer to the jta transaction processing, by the realization of the SPI load XATransactionManager, default is AtomikosTransactionManager (ignoring the code).

XAShardingTransactionManager for connection, it is entrusted org. Apache. Shardingsphere. Transaction. Xa. Jta. The datasource. XATransactionDataSource implementation.

public final class XATransactionDataSource implements AutoCloseable {
  // Database type
  private final DatabaseType databaseType;
  // Uniquely identifies the data source
  private final String resourceName;
  // The actual data source
  private final DataSource dataSource;
  // javax.sql.XADataSource
  private XADataSource xaDataSource;
  // org.apache.shardingsphere.transaction.xa.spi.XATransactionManager
  private XATransactionManager xaTransactionManager;

  public XATransactionDataSource(final DatabaseType databaseType, final String resourceName, final DataSource dataSource, final XATransactionManager xaTransactionManager) {
      this.databaseType = databaseType;
      this.resourceName = resourceName;
      this.dataSource = dataSource;
      this.xaDataSource = XADataSourceFactory.build(databaseType, dataSource);
      this.xaTransactionManager = xaTransactionManager;
      xaTransactionManager.registerRecoveryResource(resourceName, xaDataSource);
  }

  public Connection getConnection(a) throws SQLException, SystemException, RollbackException {
      // ...
      // The actual data source (HikariDataSource) gets the actual connection (HikariProxyConnection)
      Connection result = dataSource.getConnection();
      // javax.sql.XADataSource + java.sql.Connection + org.apache.shardingsphere.spi.database.type.DatabaseType - > javax.sql.XAConnection
      XAConnection xaConnection = XAConnectionFactory.createXAConnection(databaseType, xaDataSource, result);
      // org.apache.shardingsphere.transaction.xa.spi.XATransactionManager -> javax.transaction.TransactionManager -> javax.transaction.Transaction
      final Transaction transaction = xaTransactionManager.getTransactionManager().getTransaction();
      // ...
      // Actual connection
      returnresult; }}Copy the code

You can see that the getConnection method of the XATransactionDataSource converts the java.sql+ ShardingSphere combination to the JTA related implementation, still returning the actual connection. (Although the connection returned by the connection pool is also a proxy object, this is the target connection for Sharding-JDBC.)

SeataATShardingTransactionManager

SeataATShardingTransactionManager for BASE affairs management implementation.

public final class SeataATShardingTransactionManager implements ShardingTransactionManager {
    
    private final Map<String, DataSource> dataSourceMap = new HashMap<>();
    
    private final String applicationId;
    
    private final String transactionServiceGroup;
    
    private final boolean enableSeataAT;
    
    public SeataATShardingTransactionManager(a) {
        FileConfiguration configuration = new FileConfiguration("seata.conf");
        enableSeataAT = configuration.getBoolean("sharding.transaction.seata.at.enable".true);
        applicationId = configuration.getConfig("client.application.id");
        transactionServiceGroup = configuration.getConfig("client.transaction.service.group"."default");
    }
    
    @Override
    public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
        if (enableSeataAT) {
            // Initialize the client
            initSeataRPCClient();
            for (ResourceDataSource each : resourceDataSources) {
                // Convert the original dataSource to seata's own dataSource proxy instance
                dataSourceMap.put(each.getOriginalName(), newDataSourceProxy(each.getDataSource())); }}}private void initSeataRPCClient(a) {
        TMClient.init(applicationId, transactionServiceGroup);
        RMClient.init(applicationId, transactionServiceGroup);
    }
    
    @Override
    public TransactionType getTransactionType(a) {
        return TransactionType.BASE;
    }
    
    @Override
    public boolean isInTransaction(a) {
        return null! = RootContext.getXID(); }@Override
    public Connection getConnection(final String dataSourceName) throws SQLException {
        return dataSourceMap.get(dataSourceName).getConnection();
    }
    
    @Override
    public void begin(a) {
        GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
        globalTransaction.begin();
        SeataTransactionHolder.set(globalTransaction);
    }
    
    @Override
    public void commit(a) {
        try {
            SeataTransactionHolder.get().commit();
        } finally{ SeataTransactionHolder.clear(); RootContext.unbind(); }}@Override
    public void rollback(a) {
        try {
            SeataTransactionHolder.get().rollback();
        } finally{ SeataTransactionHolder.clear(); RootContext.unbind(); }}@Override
    public void close(a) { dataSourceMap.clear(); SeataTransactionHolder.clear(); TmRpcClient.getInstance().destroy(); RmRpcClient.getInstance().destroy(); }}Copy the code
  • Init: RPCClient is built to convert the original DataSource into Seata’s own DataSource agent.
  • GetConnection: Returns the Connection created by Seata’s DataSourceProxy (ConnectionProxy- Seata’s DataSource proxy).
  • IsInTransaction: Determines whether Seata is in a global transaction by the presence of its XID.
  • Begin, COMMIT, and rollback: Use ThreadLocal to obtain current global transactions and perform operations.

Four, TransactionTypeHolder

TransactionTypeHolder holds the global transaction type of the current thread through ThreadLocal.

public final class TransactionTypeHolder {
    
    private static final ThreadLocal<TransactionType> CONTEXT = ThreadLocal.withInitial(() -> TransactionType.LOCAL);
    
    public static TransactionType get(a) {
        return CONTEXT.get();
    }
    
    public static void set(final TransactionType transactionType) {
        CONTEXT.set(transactionType);
    }
    
    public static void clear(a) { CONTEXT.remove(); }}Copy the code

If you want to open a global transaction, need in the code through TransactionTypeHolder. Set method Settings, the following official case.

void insert(a) throws SQLException {
    // Set global transaction type to XA
    TransactionTypeHolder.set(TransactionType.XA);
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);
        PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO t_order (user_id, status) VALUES (? ,?) "); doInsert(preparedStatement); connection.commit(); }}Copy the code

First by TransactionTypeHolder. Set (TransactionType. XA) set the current global transaction type of thread. Then when ShardingDataSource obtains ShardingConnection, it obtains the corresponding transaction manager from the transaction engine according to the current transaction type.

public final class ShardingConnection extends AbstractConnectionAdapter {
    private final ShardingTransactionManager shardingTransactionManager;
    public ShardingConnection(final Map<String, DataSource> dataSourceMap, final ShardingRuntimeContext runtimeContext, final TransactionType transactionType) {
        // ...
        // Get the transaction manager based on the transaction typeshardingTransactionManager = runtimeContext.getShardingTransactionManagerEngine().getTransactionManager(transactionType); }}Copy the code

Then either commit or rollback will, depending on the type of the current thread’s global transaction go transaction manager ShardingTransactionManager do processing.

public final class ShardingConnection extends AbstractConnectionAdapter {
    
    private final TransactionType transactionType;
    
    private final ShardingTransactionManager shardingTransactionManager;
    @Override
    public void commit(a) throws SQLException {
        if (TransactionType.LOCAL == transactionType) {
            super.commit();
        } else{ shardingTransactionManager.commit(); }}@Override
    public void rollback(a) throws SQLException {
        if (TransactionType.LOCAL == transactionType) {
            super.rollback();
        } else{ shardingTransactionManager.rollback(); }}}Copy the code

ShardingTransactionType annotations and SpringAOP

Setting the global transaction type can be done by encoding or using annotations +AOP.

Walk / / org. Springframework. Transaction. The interceptor. TransactionInterceptor
@Transactional
Walk / / org. Apache. Shardingsphere. Transaction. Spring. ShardingTransactionTypeInterceptor
@ShardingTransactionType(TransactionType.XA)
public TransactionType insert(final int count) {
    return jdbcTemplate.execute("INSERT INTO t_order_xa (user_id, status) VALUES (? ,?) ", (PreparedStatementCallback<TransactionType>) preparedStatement -> {
        doInsert(count, preparedStatement);
        return TransactionTypeHolder.get();
    });
}
Copy the code

ThreadLocal+ annotations +AOP is a common requirement, and ShardingJDBC is a good example of introducing ShardingTransactionType annotations in combination with SpringAOP as a starter.

ShardingJDBC injected ShardingTransactionTypeScanner, only to realize the demand. And let’s see how it does that.

@Bean
public ShardingTransactionTypeScanner shardingTransactionTypeScanner(a) {
    return new ShardingTransactionTypeScanner();
}
Copy the code

1, the Advice

Let’s take a look at ShardingJDBC’s implementation of Advice Advice, which implements the MethodInterceptor interface.

public final class ShardingTransactionTypeInterceptor implements MethodInterceptor {
    
    @Override
    public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
        ShardingTransactionType shardingTransactionType = getAnnotation(methodInvocation);
        / / set the ThreadLocal
        TransactionTypeHolder.set(shardingTransactionType.value());
        return methodInvocation.proceed();
    }
    
    private ShardingTransactionType getAnnotation(final MethodInvocation invocation) {
        // Get the Class of the target objectClass<? > targetClass = AopUtils.getTargetClass(invocation.getThis());// Get annotations on methods
        ShardingTransactionType result = getMethodAnnotation(invocation, targetClass);
        // Use the method annotation value first and the class annotation value second
        return null! = result ? result : targetClass.getAnnotation(ShardingTransactionType.class); }private ShardingTransactionType getMethodAnnotation(final MethodInvocation invocation, finalClass<? > targetClass) {
        // May be a bridge method
        Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
        // Convert to the original method
        final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
        returnuserDeclaredMethod.getAnnotation(ShardingTransactionType.class); }}Copy the code

Here are a few of Spring’s toolclass methods to learn. For example, there is now a SuperClass interface and a SubClass implementation class.

public interface SuperClass<T> {
    T method(T t);
}

public class SubClass implements SuperClass<String> {

    @ShardingTransactionType
    @Override
    public String method(String s) {
        return s + "xxx"; }}Copy the code

1, AopUtils# getTargetClass

Gets the Class of the target object based on the proxy object.

SuperClass superClass = new SubClass();
//class org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SubClass
System.out.println(AopUtils.getTargetClass(superClass));
Copy the code

2, ClassUtils# getMostSpecificMethod

Find the method of the target Class based on the current method and the target Class.

SuperClass superClass = new SubClass();
//class org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SubClass
System.out.println(AopUtils.getTargetClass(superClass));

Method invocationMethod = superClass.getClass().getDeclaredMethod("method", Object.class);

Method method01 = ClassUtils.getMostSpecificMethod(invocationMethod, AopUtils.getTargetClass(superClass));
// public java.lang.Object org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SubClass.method(java.lang.Object)
System.out.println(method01);
Method method02 = ClassUtils.getMostSpecificMethod(invocationMethod, SuperClass.class);
// public abstract java.lang.Object org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SuperClass.method(java.lang.Object)
System.out.println(method02);
Copy the code

For ttf_subclass. Class. GetDeclaredMethod (” method “, the Object class) this method as an example, when the second parameter getMostSpecificMethod ttf_subclass transfer. The class, If the second argument is passed to superclass. class, the Method corresponding to the SuperClass is returned. The meaning of this Method is to convert the parameter Method to the corresponding Method of the target Class.

3, BridgeMethodResolver# findBridgedMethod

Check whether the input Method is a bridge Method, if so, return the actual Method; If not, Method is returned as is.

What is the bridging method?

The compiler’s introduction of bridging methods is related to type erasure of generics. In fact, SuperClass compiles a method whose input and output arguments are Object. Subclasses also need to implement such a method, which is generated by the compiler. Execute javap -c -p -v SubClass > subclass.txt to see the bytecode.

public java.lang.String method(java.lang.String); descriptor: (Ljava/lang/String;) Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 7: aload_1 8: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;) Ljava/lang/StringBuilder; 11: ldc #5 // String xxx 13: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;) Ljava/lang/StringBuilder; 16: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 19: areturn LineNumberTable: line 13: 0 LocalVariableTable: Start Length Slot Name Signature 0 20 0 this Lorg/apache/shardingsphere/example/transaction/xa/spring/boot/SubClass; 0 20 1 s Ljava/lang/String; RuntimeVisibleAnnotations: 0: #24() public java.lang.Object method(java.lang.Object); descriptor: (Ljava/lang/Object;) Ljava/lang/Object; flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: checkcast #7 // class java/lang/String 5: invokevirtual #8 // Method method:(Ljava/lang/String;) Ljava/lang/String; 8: areturn LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lorg/apache/shardingsphere/example/transaction/xa/spring/boot/SubClass; RuntimeVisibleAnnotations: 0: #24()Copy the code

You can see that the bridge method corresponding to Object is actually a strong pass in with a String parameter, and then calls the actual method of the String parameter, and returns the result. It basically means something like this.

public class SubClass implements SuperClass {

    @ShardingTransactionType
    public String method(String s) {
        return s + "xxx";
    }
    @Override
    public Object method(Object t) {
        returnmethod((String)t); }}Copy the code

Back to BridgeMethodResolver. FindBridgedMethod this Method, which is easy to understand, if the reference Method is the Method of generic erased do transformation Method (bridge), is converted into a generic before erasing methods (the actual Method). Take the following example.

Method01 is a bridge method
Method method01 = SubClass.class.getMethod("method", Object.class);
// true
System.out.println(method01.isBridge());
// find Method corresponding to SubClass method01
Method specificMethod = ClassUtils.getMostSpecificMethod(method01, SubClass.class);
// public java.lang.Object org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SubClass.method(java.lang.Object)
// Bridge method
System.out.println(specificMethod);
// Convert the bridge method to the actual method
Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// public java.lang.String org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SubClass.method(java.lang.String)
// The actual method
System.out.println(userDeclaredMethod);
Copy the code

4, AopUtils# getMostSpecificMethod

Documentation comments ClassUtils. GetMostSpecificMethod method are as follows.

Given a method, which may come from an interface, and a target class used in the current reflective invocation, find the corresponding target method if there is one. E.g. the method may be {@code IFoo.bar()} and the target class may be {@code DefaultFoo}. In this case, the method may be {@code DefaultFoo.bar()}. This enables attributes on that method to be found. NOTE: In contrast to {@link org.springframework.aop.support.AopUtils#getMostSpecificMethod}, this method does not resolve Java 5 bridge methods automatically. Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod} if bridge method resolution is desirable (e.g. for obtaining metadata from the original method definition). NOTE: Since the Spring 3.1.1. if Java security settings disallow reflective access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation will fall back to returning the originally provided method.

This method does not support the conversion of the bridge method. If you want to obtain the actual method of the bridge method, there are two methods. One is call ClassUtils. After getMostSpecificMethod through BridgeMethodResolver# findBridgedMethod method transformation, The other option is to call AopUtils#getMostSpecificMethod directly.

Method method01 = SubClass.class.getMethod("method", Object.class);
// public java.lang.String org.apache.shardingsphere.example.transaction.xa.spring.boot.XAOrderService$SubClass.method(java.lang.String)
System.out.println(AopUtils.getMostSpecificMethod(method01, SubClass.class));
Copy the code

2, Advisor

PointCut + Advice = Advisor ShardingTransactionTypeAdvisor inheritance AbstractPointcutAdvisor, realize the point of tangency and notice the get method.

public final class ShardingTransactionTypeAdvisor extends AbstractPointcutAdvisor {
    
    private final Pointcut transactionTypePointcut;
    
    private final Advice transactionTypeInterceptor;
    
    ShardingTransactionTypeAdvisor() {
        Pointcut classPointcut = new ComposablePointcut(AnnotationMatchingPointcut.forClassAnnotation(ShardingTransactionType.class));
        Pointcut methodPointcut = new ComposablePointcut(AnnotationMatchingPointcut.forMethodAnnotation(ShardingTransactionType.class));
        transactionTypePointcut = new ComposablePointcut(classPointcut).union(methodPointcut);
        transactionTypeInterceptor = new ShardingTransactionTypeInterceptor();
        setOrder(Ordered.LOWEST_PRECEDENCE - 1);
    }
    
    @Override
    public Pointcut getPointcut(a) {
        return transactionTypePointcut;
    }
    
    @Override
    public Advice getAdvice(a) {
        returntransactionTypeInterceptor; }}Copy the code

Pointcut AnnotationMatchingPointcut# forClassAnnotation through annotation Class structure. ComposablePointcut can compose multiple Pointcuts and perform various intersection and union operations. Here ShardingTransactionTypeAdvisor implementation is take ShardingTransactionType annotations and posing a tangent point set of classes and methods.

In addition, ShardingTransactionTypeAdvisor directly use ShardingTransactionTypeInterceptor as Advice.

For the entire Advisor, Order is set to order.lowest_precedence-1. Why?

Notice the Spring transaction annotation type automatic configuration, ProxyTransactionManagementConfiguration# transactionAdvisor affairs section below.

@Bean(name = {"org.springframework.transaction.config.internalTransactionAdvisor"})
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(a) {
    BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
    advisor.setTransactionAttributeSource(this.transactionAttributeSource());
    advisor.setAdvice(this.transactionInterceptor());
    advisor.setOrder((Integer)this.enableTx.getNumber("order"));
    return advisor;
}
Copy the code

The enclosing enableTx. GetNumber (” order “) is actually EnableTransactionManagement annotation order attribute, a default is Ordered LOWEST_PRECEDENCE. Configure ShardingTransactionTypeAdvisor orderOrdered. LOWEST_PRECEDENCE – 1 for Ordered. LOWEST_PRECEDENCE – 1, Such TransactionTypeHolder. Set (TransactionType. XA) set the global transaction type of method execution will before SpringBoot automatic configuration of the transaction.

public @interface EnableTransactionManagement {
	int order(a) default Ordered.LOWEST_PRECEDENCE;
}
Copy the code

3, BeanPostProcessor

SpringAOP agent entry is generated in BeanPostProcessor# postProcessAfterInitialization stage, AbstractAutoProxyCreator# postProcessAfterInitialization will perform, create a proxy object, and the proxy object implements the Advised interface.

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  if(bean ! =null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (!this.earlyProxyReferences.contains(cacheKey)) {
          returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // ...

    // Advice & Advisor
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if(specificInterceptors ! = DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // Create an agent that holds a chain of Advisors
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    // ...
}
Copy the code

ShardingTransactionTypeScanner inherited AbstractAdvisingBeanPostProcessor, In AbstractAutoProxyCreator# postProcessAfterInitialization executed after, only provides an advisor member variables, Can be achieved to add ShardingTransactionTypeAdvisor Advisor to intercept list.

public final class ShardingTransactionTypeScanner extends AbstractAdvisingBeanPostProcessor {
    
    public ShardingTransactionTypeScanner(a) {
        setBeforeExistingAdvisors(true);
        this.advisor = newShardingTransactionTypeAdvisor(); }}Copy the code

AbstractAdvisingBeanPostProcessor postProcessAfterInitialization method will ShardingTransactionTypeAdvisor joined the Advisor to intercept list.

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {

	protected Advisor advisor;
    
    public Object postProcessAfterInitialization(Object bean, String beanName) {
      // ...
      if (bean instanceof Advised) {
          Advised advised = (Advised)bean;
          if(! advised.isFrozen() &&this.isEligible(AopUtils.getTargetClass(bean))) {
              if (this.beforeExistingAdvisors) {
                  advised.addAdvisor(0.this.advisor);
              } else {
                  advised.addAdvisor(this.advisor);
              }

              returnbean; }}// ...}}Copy the code

conclusion

  • ShardingJDBC supports three transaction types: LOCAL (LOCAL), XA (strongly consistent two-phase commit), and BASE (ultimately consistent Seata implementation).
  • ShardingTransactionManagerEngine transaction management engine, is responsible for creating the transaction manager and the transaction manager initialization, and provide access to external transaction manager.
  • XAShardingTransactionManager ShardingTransactionManager transaction manager, responsible for XA transaction management implementation, SeataATShardingTransactionManager for BASE affairs management implementation.
  • The local transaction records the Connection setAutocommit method call through the WrapperAdapter and replays the method call until the actual connection is created. Finally, when the user calls the COMMIT method of ShardingConnection, the commit method is executed over all the actual connections.
  • TransactionTypeHolder operates on the global transaction type of the current thread through ThreadLocal. Set the global transaction type, can pass encoding (TransactionTypeHolder. Set), can also through annotations + AOP (@ ShardingTransactionType + ShardingTransactionTypeAdvisor).
  • ShardingJDBC using ShardingTransactionTypeScanner + ShardingTransactionTypeAdvisor + ShardingTransactionTypeInterceptor realized through setting all annotations Office transaction type.