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.