diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 327e2588095..4d291c9a98b 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -24,6 +24,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6232](https://github.com/apache/incubator-seata/pull/6232)] convert to utf8mb4 if mysql column is json type - [[#6278](https://github.com/apache/incubator-seata/pull/6278)] fix ProtocolV1SerializerTest failed - [[#6324](https://github.com/apache/incubator-seata/pull/6324)] fix Parse protocol file failed +- [[#6331](https://github.com/apache/incubator-seata/pull/6331)] fixed the problem that TCC nested transactions cannot add TwoPhaseBusinessAction and GlobalTransactional annotations at the same time - [[#6354](https://github.com/apache/incubator-seata/pull/6354)] fix dynamic degradation does not work properly - [[#6363](https://github.com/apache/incubator-seata/pull/6363)] fix known problems of docker image - [[#6372](https://github.com/apache/incubator-seata/pull/6372)] fix initializing the sql file postgresql.sql index name conflict diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 5c60b573d6d..afa94b86b42 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -24,6 +24,7 @@ - [[#6232](https://github.com/apache/incubator-seata/pull/6232)] 修复在mysql的json类型下出现Cannot create a JSON value from a string with CHARACTER SET 'binary'问题 - [[#6278](https://github.com/apache/incubator-seata/pull/6278)] 修复 ProtocolV1SerializerTest 失败问题 - [[#6324](https://github.com/apache/incubator-seata/pull/6324)] 修复 Parse protocol file failed +- [[#6331](https://github.com/apache/incubator-seata/pull/6331)] 修复TCC嵌套事务不能同时添加TwoPhaseBusinessAction和GlobalTransactional两个注解的问题 - [[#6354](https://github.com/apache/incubator-seata/pull/6354)] 修复动态升降级不能正常工作问题 - [[#6363](https://github.com/apache/incubator-seata/pull/6363)] 修复docker镜像中的已知问题 - [[#6372](https://github.com/apache/incubator-seata/pull/6372)] 修复初始化sql文件postgresql.sql 索引名称冲突问题 diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/InvocationHandlerType.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/InvocationHandlerType.java new file mode 100644 index 00000000000..7d909105a09 --- /dev/null +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/InvocationHandlerType.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.integration.tx.api.interceptor; + +/** + * The InvocationHandlerType enum + */ +public enum InvocationHandlerType { + + GlobalTransactional, TwoPhaseAnnotation + +} diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/NestInterceptorHandlerWrapper.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/NestInterceptorHandlerWrapper.java new file mode 100644 index 00000000000..0abb2fc4bd2 --- /dev/null +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/NestInterceptorHandlerWrapper.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.integration.tx.api.interceptor; + +import java.lang.reflect.Method; +import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; + + +public class NestInterceptorHandlerWrapper implements InvocationWrapper { + + private ProxyInvocationHandler proxyInvocationHandler; + + private InvocationWrapper invocation; + + public NestInterceptorHandlerWrapper(ProxyInvocationHandler proxyInvocationHandler, InvocationWrapper invocation) { + this.proxyInvocationHandler = proxyInvocationHandler; + this.invocation = invocation; + } + + @Override + public Method getMethod() { + return invocation.getMethod(); + } + + @Override + public Object getProxy() { + return invocation.getProxy(); + } + + @Override + public Object getTarget() { + return invocation.getTarget(); + } + + @Override + public Object[] getArguments() { + return invocation.getArguments(); + } + + @Override + public Object proceed() throws Throwable { + return proxyInvocationHandler.invoke(invocation); + } +} diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/AbstractProxyInvocationHandler.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/AbstractProxyInvocationHandler.java index f6c3a533859..76429b7a505 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/AbstractProxyInvocationHandler.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/AbstractProxyInvocationHandler.java @@ -18,6 +18,7 @@ import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.integration.tx.api.interceptor.InvocationWrapper; +import org.apache.seata.integration.tx.api.interceptor.NestInterceptorHandlerWrapper; public abstract class AbstractProxyInvocationHandler implements ProxyInvocationHandler { @@ -26,11 +27,16 @@ public abstract class AbstractProxyInvocationHandler implements ProxyInvocationH protected int order = Integer.MAX_VALUE; + protected ProxyInvocationHandler nextInvocationHandlerChain; + @Override public Object invoke(InvocationWrapper invocation) throws Throwable { if (CollectionUtils.isNotEmpty(getMethodsToProxy()) && !getMethodsToProxy().contains(invocation.getMethod().getName())) { return invocation.proceed(); } + if (nextInvocationHandlerChain != null) { + invocation = new NestInterceptorHandlerWrapper(nextInvocationHandlerChain, invocation); + } return doInvoke(invocation); } @@ -44,4 +50,8 @@ public int getOrder() { return this.order; } + @Override + public void setNextProxyInvocationHandler(ProxyInvocationHandler next) { + this.nextInvocationHandlerChain = next; + } } diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java index 03f618ab51d..ac942636dff 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java @@ -16,6 +16,15 @@ */ package org.apache.seata.integration.tx.api.interceptor.handler; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + import com.google.common.eventbus.Subscribe; import org.apache.seata.common.exception.ShouldNeverHappenException; import org.apache.seata.common.thread.NamedThreadFactory; @@ -32,6 +41,7 @@ import org.apache.seata.core.model.GlobalLockConfig; import org.apache.seata.integration.tx.api.annotation.AspectTransactional; import org.apache.seata.integration.tx.api.event.DegradeCheckEvent; +import org.apache.seata.integration.tx.api.interceptor.InvocationHandlerType; import org.apache.seata.integration.tx.api.interceptor.InvocationWrapper; import org.apache.seata.integration.tx.api.interceptor.SeataInterceptorPosition; import org.apache.seata.integration.tx.api.util.ClassUtils; @@ -51,15 +61,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import static org.apache.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION; import static org.apache.seata.common.DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_TM_DEGRADE_CHECK; @@ -410,4 +411,9 @@ public SeataInterceptorPosition getPosition() { return SeataInterceptorPosition.BeforeTransaction; } + + @Override + public String type() { + return InvocationHandlerType.GlobalTransactional.name(); + } } diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/ProxyInvocationHandler.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/ProxyInvocationHandler.java index 8238d8526ba..1dbe24a61da 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/ProxyInvocationHandler.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/ProxyInvocationHandler.java @@ -31,4 +31,11 @@ public interface ProxyInvocationHandler extends SeataInterceptor { SeataInterceptorPosition getPosition(); + String type(); + + default int order() { + return 0; + } + + void setNextProxyInvocationHandler(ProxyInvocationHandler next); } diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java index 2827f9dc7dc..c93574e8189 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java @@ -16,13 +16,17 @@ */ package org.apache.seata.integration.tx.api.interceptor.parser; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.apache.seata.common.loader.EnhancedServiceLoader; import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; -import java.util.ArrayList; -import java.util.List; - /** * @author leezongjie */ @@ -53,15 +57,46 @@ protected void initInterfaceParser() { } } + /** + * Create an interceptor chain that supports adding multiple interceptors.Create an interceptor chain that supports adding multiple interceptors. + * The entry order of the facets can be specified through {@link ProxyInvocationHandler # order()}. + * It is not allowed to load multiple interceptors of the same type, such as two-stage annotations for TCC and Saga that cannot exist simultaneously. The type can be specified through {@link ProxyInvocationHandler # type()}.It is not allowed to load multiple interceptors of the same type, such as two-stage annotations for TCC and Saga that cannot exist simultaneously. The type can be specified through {@link ProxyInvocationHandler # type()}. + * + * @param target + * @param objectName + * @return + * @throws Exception + */ @Override public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objectName) throws Exception { + List invocationHandlerList = new ArrayList<>(); + Set invocationHandlerRepeatCheck = new HashSet<>(); + for (InterfaceParser interfaceParser : ALL_INTERFACE_PARSERS) { ProxyInvocationHandler proxyInvocationHandler = interfaceParser.parserInterfaceToProxy(target, objectName); if (proxyInvocationHandler != null) { - return proxyInvocationHandler; + if (!invocationHandlerRepeatCheck.add(proxyInvocationHandler.type())) { + throw new RuntimeException("there is already an annotation of type " + proxyInvocationHandler.type() + " for class: " + target.getClass().getName()); + } + invocationHandlerList.add(proxyInvocationHandler); } } - return null; + + Collections.sort(invocationHandlerList, Comparator.comparingInt(ProxyInvocationHandler::order)); + + ProxyInvocationHandler first = null; + ProxyInvocationHandler last = null; + for (ProxyInvocationHandler proxyInvocationHandler : invocationHandlerList) { + if (first == null) { + first = proxyInvocationHandler; + } + if (last != null) { + last.setNextProxyInvocationHandler(proxyInvocationHandler); + } + last = proxyInvocationHandler; + } + + return first; } @Override diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java index beacda7de11..8192ff876bd 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java @@ -19,7 +19,6 @@ import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; - import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.common.util.ReflectionUtil; @@ -31,7 +30,6 @@ import org.apache.seata.spring.annotation.GlobalTransactional; import org.apache.seata.tm.api.FailureHandlerHolder; - public class GlobalTransactionalInterceptorParser implements InterfaceParser { protected final Set methodsToProxy = new HashSet<>(); diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java index ffee8848e4f..9b50104c9e0 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - import org.apache.seata.common.Constants; import org.apache.seata.common.DefaultValues; import org.apache.seata.common.holder.ObjectHolder; @@ -31,17 +30,16 @@ import org.apache.seata.core.model.BranchType; import org.apache.seata.integration.tx.api.fence.config.CommonFenceConfig; import org.apache.seata.integration.tx.api.interceptor.ActionInterceptorHandler; +import org.apache.seata.integration.tx.api.interceptor.InvocationHandlerType; import org.apache.seata.integration.tx.api.interceptor.InvocationWrapper; import org.apache.seata.integration.tx.api.interceptor.SeataInterceptorPosition; import org.apache.seata.integration.tx.api.interceptor.TwoPhaseBusinessActionParam; import org.apache.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler; import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; import org.slf4j.MDC; - import static org.apache.seata.common.ConfigurationKeys.TCC_ACTION_INTERCEPTOR_ORDER; import static org.apache.seata.common.Constants.BEAN_NAME_SPRING_FENCE_CONFIG; - public class TccActionInterceptorHandler extends AbstractProxyInvocationHandler { private static final int ORDER_NUM = ConfigurationFactory.getInstance().getInt(TCC_ACTION_INTERCEPTOR_ORDER, @@ -167,4 +165,13 @@ public SeataInterceptorPosition getPosition() { return SeataInterceptorPosition.Any; } + @Override + public int order() { + return 1; + } + + @Override + public String type() { + return InvocationHandlerType.TwoPhaseAnnotation.name(); + } } diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java index ffe88d6ad19..740690b19db 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; - import org.apache.seata.common.util.ReflectionUtil; import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; import org.apache.seata.integration.tx.api.interceptor.parser.DefaultResourceRegisterParser; @@ -34,7 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - public class TccActionInterceptorParser implements InterfaceParser { private static final Logger LOGGER = LoggerFactory.getLogger(TccActionInterceptorParser.class); diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/BranchSessionMock.java b/tcc/src/test/java/org/apache/seata/rm/tcc/BranchSessionMock.java new file mode 100644 index 00000000000..7f60557c73b --- /dev/null +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/BranchSessionMock.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.rm.tcc; + +import org.apache.seata.core.model.BranchType; + +public class BranchSessionMock { + + private String xid; + + private long branchId; + + private String resourceGroupId; + + private String resourceId; + + + private BranchType branchType; + + + private String applicationData; + + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public long getBranchId() { + return branchId; + } + + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + public String getResourceGroupId() { + return resourceGroupId; + } + + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public BranchType getBranchType() { + return branchType; + } + + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + public String getApplicationData() { + return applicationData; + } + + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } +} diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/NestTccAction.java b/tcc/src/test/java/org/apache/seata/rm/tcc/NestTccAction.java new file mode 100644 index 00000000000..19fe35a8f86 --- /dev/null +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/NestTccAction.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.rm.tcc; + +import org.apache.seata.rm.tcc.api.BusinessActionContext; +import org.apache.seata.rm.tcc.api.LocalTCC; +import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; + +/** + * The interface Tcc action. + */ +@LocalTCC +public interface NestTccAction { + + /** + * Prepare boolean. + * + * @param actionContext the action context + * @return the boolean + */ + @TwoPhaseBusinessAction(name = "tccNestActionForTest") + boolean prepare(BusinessActionContext actionContext, int count); + + @TwoPhaseBusinessAction(name = "tccNestActionForTest") + boolean prepareNestRequiredNew(BusinessActionContext actionContext, int count); + + /** + * Commit boolean. + * + * @param actionContext the action context + * @return the boolean + */ + boolean commit(BusinessActionContext actionContext); + + /** + * Rollback boolean. + * + * @param actionContext the action context + * @return the boolean + */ + boolean rollback(BusinessActionContext actionContext); +} diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/NestTccActionImpl.java b/tcc/src/test/java/org/apache/seata/rm/tcc/NestTccActionImpl.java new file mode 100644 index 00000000000..4b34d7534e7 --- /dev/null +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/NestTccActionImpl.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.rm.tcc; + + +import org.apache.seata.rm.tcc.api.BusinessActionContext; +import org.apache.seata.spring.annotation.GlobalTransactional; +import org.apache.seata.tm.api.transaction.Propagation; + + +public class NestTccActionImpl implements NestTccAction { + + private TccAction tccAction; + private boolean isCommit; + + @Override + @GlobalTransactional() + public boolean prepare(BusinessActionContext actionContext, int count) { + tccAction.prepare(actionContext); + return count > 1; + } + + @GlobalTransactional(propagation = Propagation.REQUIRES_NEW) + @Override + public boolean prepareNestRequiredNew(BusinessActionContext actionContext, int count) { + tccAction.prepare(actionContext); + return count > 1; + } + + @Override + public boolean commit(BusinessActionContext actionContext) { + isCommit = true; + return true; + } + + @Override + public boolean rollback(BusinessActionContext actionContext) { + return true; + } + + public void setTccAction(TccAction tccAction) { + this.tccAction = tccAction; + } + + public boolean isCommit() { + return isCommit; + } +} diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/TccAction.java b/tcc/src/test/java/org/apache/seata/rm/tcc/TccAction.java index 8f3c29011f5..ec9ea949cf9 100644 --- a/tcc/src/test/java/org/apache/seata/rm/tcc/TccAction.java +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/TccAction.java @@ -21,11 +21,8 @@ import org.apache.seata.rm.tcc.api.LocalTCC; import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; -import java.util.List; - /** * The interface Tcc action. - * */ @LocalTCC public interface TccAction { @@ -34,16 +31,10 @@ public interface TccAction { * Prepare boolean. * * @param actionContext the action context - * @param a the a - * @param b the b - * @param tccParam the tcc param * @return the boolean */ - @TwoPhaseBusinessAction(name = "tccActionForTest", commitMethod = "commit", rollbackMethod = "rollback", commitArgsClasses = {BusinessActionContext.class, TccParam.class, Integer.class}, rollbackArgsClasses = {BusinessActionContext.class, TccParam.class}) - boolean prepare(BusinessActionContext actionContext, - @BusinessActionContextParameter("a") int a, - @BusinessActionContextParameter(paramName = "b", index = 0) List b, - @BusinessActionContextParameter(isParamInProperty = true) TccParam tccParam); + @TwoPhaseBusinessAction(name = "tccActionForTest") + boolean prepare(BusinessActionContext actionContext); /** * Commit boolean. @@ -51,8 +42,15 @@ boolean prepare(BusinessActionContext actionContext, * @param actionContext the action context * @return the boolean */ - boolean commit(BusinessActionContext actionContext, - @BusinessActionContextParameter("tccParam") TccParam param, @Param("a") Integer a); + boolean commit(BusinessActionContext actionContext); + + /** + * Commit boolean. + * + * @param actionContext the action context + * @return the boolean + */ + boolean commitWithArg(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") TccParam param, @Param("a") Integer a); /** * Rollback boolean. @@ -60,5 +58,8 @@ boolean commit(BusinessActionContext actionContext, * @param actionContext the action context * @return the boolean */ - boolean rollback(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") TccParam param); + boolean rollback(BusinessActionContext actionContext); + + boolean rollbackWithArg(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") TccParam param); + } diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/TccActionImpl.java b/tcc/src/test/java/org/apache/seata/rm/tcc/TccActionImpl.java index d49ad3f6d70..d63e67a5561 100644 --- a/tcc/src/test/java/org/apache/seata/rm/tcc/TccActionImpl.java +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/TccActionImpl.java @@ -17,9 +17,6 @@ package org.apache.seata.rm.tcc; import org.apache.seata.rm.tcc.api.BusinessActionContext; -import org.apache.seata.rm.tcc.api.BusinessActionContextParameter; - -import java.util.List; /** * The type Tcc action. @@ -27,23 +24,38 @@ */ public class TccActionImpl implements TccAction { + private boolean isCommit; + + @Override - public boolean prepare(BusinessActionContext actionContext, - int a, - List b, - TccParam TccParam ) { + public boolean prepare(BusinessActionContext actionContext) { return true; } @Override - public boolean commit(BusinessActionContext actionContext, - @BusinessActionContextParameter("tccParam") TccParam param, @Param("a") Integer a) { + public boolean commit(BusinessActionContext actionContext) { + isCommit = true; return true; } @Override - public boolean rollback(BusinessActionContext actionContext, - @BusinessActionContextParameter("tccParam") TccParam param) { + public boolean commitWithArg(BusinessActionContext actionContext, TccParam param, Integer a) { + return false; + } + + + @Override + public boolean rollback(BusinessActionContext actionContext) { return true; } + + @Override + public boolean rollbackWithArg(BusinessActionContext actionContext, TccParam param) { + return false; + } + + + public boolean isCommit() { + return isCommit; + } } diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java b/tcc/src/test/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java index 79fda999831..0f193535537 100644 --- a/tcc/src/test/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java @@ -16,14 +16,48 @@ */ package org.apache.seata.rm.tcc.interceptor.parser; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.seata.core.context.RootContext; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.core.model.TransactionManager; import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; +import org.apache.seata.integration.tx.api.interceptor.parser.DefaultInterfaceParser; +import org.apache.seata.integration.tx.api.util.ProxyUtil; +import org.apache.seata.rm.DefaultResourceManager; +import org.apache.seata.rm.tcc.BranchSessionMock; +import org.apache.seata.rm.tcc.NestTccAction; +import org.apache.seata.rm.tcc.NestTccActionImpl; import org.apache.seata.rm.tcc.NormalTccActionImpl; +import org.apache.seata.rm.tcc.TCCResourceManager; +import org.apache.seata.rm.tcc.TccAction; +import org.apache.seata.rm.tcc.TccActionImpl; +import org.apache.seata.tm.TransactionManagerHolder; +import org.apache.seata.tm.api.GlobalTransaction; +import org.apache.seata.tm.api.GlobalTransactionContext; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class TccActionInterceptorParserTest { + @BeforeAll + public static void init() throws IOException { + System.setProperty("config.type", "file"); + System.setProperty("config.file.name", "file.conf"); + System.setProperty("txServiceGroup", "default_tx_group"); + System.setProperty("service.vgroupMapping.default_tx_group", "default"); + } + @Test void parserInterfaceToProxy() { @@ -38,4 +72,298 @@ void parserInterfaceToProxy() { Assertions.assertNotNull(proxyInvocationHandler); } + + + @Test + public void testNestTcc_should_commit() throws Exception { + //given + RootContext.unbind(); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.TCC, resourceManager); + + TransactionManagerHolder.set(transactionManager); + + TccActionImpl tccAction = new TccActionImpl(); + TccAction tccActionProxy = ProxyUtil.createProxy(tccAction); + Assertions.assertNotNull(tccActionProxy); + + NestTccActionImpl nestTccAction = new NestTccActionImpl(); + nestTccAction.setTccAction(tccActionProxy); + + //when + ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(nestTccAction, nestTccAction.getClass().getName()); + + //then + Assertions.assertNotNull(proxyInvocationHandler); + + + //when + NestTccAction nestTccActionProxy = ProxyUtil.createProxy(nestTccAction); + //then + Assertions.assertNotNull(nestTccActionProxy); + + + // transaction commit test + GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); + + try { + tx.begin(60000, "testBiz"); + + boolean result = nestTccActionProxy.prepare(null, 2); + + Assertions.assertTrue(result); + + if (result) { + tx.commit(); + } else { + tx.rollback(); + } + } catch (Exception exx) { + tx.rollback(); + throw exx; + } + + Assertions.assertTrue(nestTccAction.isCommit()); + Assertions.assertTrue(tccAction.isCommit()); + + } + + + @Test + public void testNestTcc_should_rollback() throws Exception { + //given + RootContext.unbind(); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.TCC, resourceManager); + + TransactionManagerHolder.set(transactionManager); + + TccActionImpl tccAction = new TccActionImpl(); + TccAction tccActionProxy = ProxyUtil.createProxy(tccAction); + Assertions.assertNotNull(tccActionProxy); + + NestTccActionImpl nestTccAction = new NestTccActionImpl(); + nestTccAction.setTccAction(tccActionProxy); + + //when + ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(nestTccAction, nestTccAction.getClass().getName()); + + //then + Assertions.assertNotNull(proxyInvocationHandler); + + + //when + NestTccAction nestTccActionProxy = ProxyUtil.createProxy(nestTccAction); + //then + Assertions.assertNotNull(nestTccActionProxy); + + + // transaction commit test + GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); + + try { + tx.begin(60000, "testBiz"); + + boolean result = nestTccActionProxy.prepare(null, 1); + + Assertions.assertFalse(result); + + if (result) { + tx.commit(); + } else { + tx.rollback(); + } + } catch (Exception exx) { + tx.rollback(); + throw exx; + } + + Assertions.assertFalse(nestTccAction.isCommit()); + Assertions.assertFalse(tccAction.isCommit()); + + } + + + @Test + public void testNestTcc_required_new_should_rollback_commit() throws Exception { + //given + RootContext.unbind(); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.TCC, resourceManager); + + TransactionManagerHolder.set(transactionManager); + + TccActionImpl tccAction = new TccActionImpl(); + TccAction tccActionProxy = ProxyUtil.createProxy(tccAction); + Assertions.assertNotNull(tccActionProxy); + + NestTccActionImpl nestTccAction = new NestTccActionImpl(); + nestTccAction.setTccAction(tccActionProxy); + + //when + ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(nestTccAction, nestTccAction.getClass().getName()); + + //then + Assertions.assertNotNull(proxyInvocationHandler); + + //when + NestTccAction nestTccActionProxy = ProxyUtil.createProxy(nestTccAction); + //then + Assertions.assertNotNull(nestTccActionProxy); + + + // transaction commit test + GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); + + try { + tx.begin(60000, "testBiz"); + + boolean result = nestTccActionProxy.prepareNestRequiredNew(null, 1); + + Assertions.assertFalse(result); + + if (result) { + tx.commit(); + } else { + tx.rollback(); + } + } catch (Exception exx) { + tx.rollback(); + throw exx; + } + + Assertions.assertTrue(nestTccAction.isCommit()); + Assertions.assertTrue(tccAction.isCommit()); + + } + + + + @Test + public void testNestTcc_required_new_should_both_commit() throws Exception { + //given + RootContext.unbind(); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.TCC, resourceManager); + + TransactionManagerHolder.set(transactionManager); + + TccActionImpl tccAction = new TccActionImpl(); + TccAction tccActionProxy = ProxyUtil.createProxy(tccAction); + Assertions.assertNotNull(tccActionProxy); + + NestTccActionImpl nestTccAction = new NestTccActionImpl(); + nestTccAction.setTccAction(tccActionProxy); + + //when + ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(nestTccAction, nestTccAction.getClass().getName()); + + //then + Assertions.assertNotNull(proxyInvocationHandler); + + //when + NestTccAction nestTccActionProxy = ProxyUtil.createProxy(nestTccAction); + //then + Assertions.assertNotNull(nestTccActionProxy); + + + // transaction commit test + GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); + + try { + tx.begin(60000, "testBiz"); + + boolean result = nestTccActionProxy.prepareNestRequiredNew(null, 2); + + Assertions.assertTrue(result); + + if (result) { + tx.commit(); + } else { + tx.rollback(); + } + } catch (Exception exx) { + tx.rollback(); + throw exx; + } + + Assertions.assertTrue(nestTccAction.isCommit()); + Assertions.assertTrue(tccAction.isCommit()); + + } + + + + private static Map> applicationDataMap = new ConcurrentHashMap<>(); + + + private static TransactionManager transactionManager = new TransactionManager() { + @Override + public String begin(String applicationId, String transactionServiceGroup, String name, int timeout) throws TransactionException { + return UUID.randomUUID().toString(); + } + + @Override + public GlobalStatus commit(String xid) throws TransactionException { + commitAll(xid); + return GlobalStatus.Committed; + } + + @Override + public GlobalStatus rollback(String xid) throws TransactionException { + + rollbackAll(xid); + + return GlobalStatus.Rollbacked; + } + + @Override + public GlobalStatus getStatus(String xid) throws TransactionException { + return GlobalStatus.Begin; + } + + @Override + public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException { + return globalStatus; + } + }; + + + private static ResourceManager resourceManager = new TCCResourceManager() { + + @Override + public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException { + + long branchId = System.currentTimeMillis(); + + List branches = applicationDataMap.computeIfAbsent(xid, s -> new ArrayList<>()); + BranchSessionMock branchSessionMock = new BranchSessionMock(); + branchSessionMock.setXid(xid); + branchSessionMock.setBranchType(branchType); + branchSessionMock.setResourceId(resourceId); + branchSessionMock.setApplicationData(applicationData); + branchSessionMock.setBranchId(branchId); + + branches.add(branchSessionMock); + + return branchId; + } + }; + + public static void commitAll(String xid) throws TransactionException { + + List branches = applicationDataMap.computeIfAbsent(xid, s -> new ArrayList<>()); + for (BranchSessionMock branch : branches) { + resourceManager.branchCommit(branch.getBranchType(), branch.getXid(), branch.getBranchId(), branch.getResourceId(), branch.getApplicationData()); + } + } + + public static void rollbackAll(String xid) throws TransactionException { + + List branches = applicationDataMap.computeIfAbsent(xid, s -> new ArrayList<>()); + for (BranchSessionMock branch : branches) { + resourceManager.branchRollback(branch.getBranchType(), branch.getXid(), branch.getBranchId(), branch.getResourceId(), branch.getApplicationData()); + } + } + } diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/resource/parser/TccRegisterResourceParserTest.java b/tcc/src/test/java/org/apache/seata/rm/tcc/resource/parser/TccRegisterResourceParserTest.java index a11e12f8b1f..b7f651afae9 100644 --- a/tcc/src/test/java/org/apache/seata/rm/tcc/resource/parser/TccRegisterResourceParserTest.java +++ b/tcc/src/test/java/org/apache/seata/rm/tcc/resource/parser/TccRegisterResourceParserTest.java @@ -16,13 +16,14 @@ */ package org.apache.seata.rm.tcc.resource.parser; +import java.lang.reflect.Method; + +import org.apache.seata.rm.tcc.TccAction; import org.apache.seata.rm.tcc.TccParam; import org.apache.seata.rm.tcc.api.BusinessActionContext; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.lang.reflect.Method; - class TccRegisterResourceParserTest { @@ -30,14 +31,13 @@ class TccRegisterResourceParserTest { @Test public void testGetTwoPhaseArgs() throws Exception { - Class tccActionImpl = Class.forName("org.apache.seata.rm.tcc.TccActionImpl"); Class[] argsCommitClasses = new Class[]{BusinessActionContext.class, TccParam.class, Integer.class}; - Method commitMethod = tccActionImpl.getMethod("commit", argsCommitClasses); + Method commitMethod = TccAction.class.getMethod("commitWithArg", argsCommitClasses); Assertions.assertThrows(IllegalArgumentException.class, () -> { tccRegisterResourceParser.getTwoPhaseArgs(commitMethod, argsCommitClasses); }); Class[] argsRollbackClasses = new Class[]{BusinessActionContext.class, TccParam.class}; - Method rollbackMethod = tccActionImpl.getMethod("rollback", argsRollbackClasses); + Method rollbackMethod = TccAction.class.getMethod("rollbackWithArg", argsRollbackClasses); String[] keys = tccRegisterResourceParser.getTwoPhaseArgs(rollbackMethod, argsRollbackClasses); Assertions.assertNull(keys[0]); Assertions.assertEquals("tccParam", keys[1]);