chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
100
tm/src/main/java/io/seata/tm/DefaultTransactionManager.java
Normal file
100
tm/src/main/java/io/seata/tm/DefaultTransactionManager.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm;
|
||||
|
||||
import io.seata.core.exception.TmTransactionException;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.exception.TransactionExceptionCode;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.core.protocol.ResultCode;
|
||||
import io.seata.core.protocol.transaction.AbstractTransactionRequest;
|
||||
import io.seata.core.protocol.transaction.AbstractTransactionResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalReportRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalReportResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalStatusRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalStatusResponse;
|
||||
import io.seata.core.rpc.netty.TmNettyRemotingClient;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* The type Default transaction manager.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class DefaultTransactionManager implements TransactionManager {
|
||||
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
GlobalBeginRequest request = new GlobalBeginRequest();
|
||||
request.setTransactionName(name);
|
||||
request.setTimeout(timeout);
|
||||
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
|
||||
if (response.getResultCode() == ResultCode.Failed) {
|
||||
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
|
||||
}
|
||||
return response.getXid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
|
||||
globalCommit.setXid(xid);
|
||||
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
|
||||
return response.getGlobalStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
GlobalRollbackRequest globalRollback = new GlobalRollbackRequest();
|
||||
globalRollback.setXid(xid);
|
||||
GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback);
|
||||
return response.getGlobalStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getStatus(String xid) throws TransactionException {
|
||||
GlobalStatusRequest queryGlobalStatus = new GlobalStatusRequest();
|
||||
queryGlobalStatus.setXid(xid);
|
||||
GlobalStatusResponse response = (GlobalStatusResponse) syncCall(queryGlobalStatus);
|
||||
return response.getGlobalStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
GlobalReportRequest globalReport = new GlobalReportRequest();
|
||||
globalReport.setXid(xid);
|
||||
globalReport.setGlobalStatus(globalStatus);
|
||||
GlobalReportResponse response = (GlobalReportResponse) syncCall(globalReport);
|
||||
return response.getGlobalStatus();
|
||||
}
|
||||
|
||||
private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {
|
||||
try {
|
||||
return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);
|
||||
} catch (TimeoutException toe) {
|
||||
throw new TmTransactionException(TransactionExceptionCode.IO, "RPC timeout", toe);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
tm/src/main/java/io/seata/tm/TMClient.java
Normal file
50
tm/src/main/java/io/seata/tm/TMClient.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm;
|
||||
|
||||
import io.seata.core.rpc.netty.TmNettyRemotingClient;
|
||||
|
||||
/**
|
||||
* The type Tm client.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class TMClient {
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param transactionServiceGroup the transaction service group
|
||||
*/
|
||||
public static void init(String applicationId, String transactionServiceGroup) {
|
||||
init(applicationId, transactionServiceGroup, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param transactionServiceGroup the transaction service group
|
||||
* @param accessKey the access key
|
||||
* @param secretKey the secret key
|
||||
*/
|
||||
public static void init(String applicationId, String transactionServiceGroup, String accessKey, String secretKey) {
|
||||
TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup, accessKey, secretKey);
|
||||
tmNettyRemotingClient.init();
|
||||
}
|
||||
|
||||
}
|
||||
71
tm/src/main/java/io/seata/tm/TransactionManagerHolder.java
Normal file
71
tm/src/main/java/io/seata/tm/TransactionManagerHolder.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm;
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Default transaction manager.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class TransactionManagerHolder {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionManagerHolder.class);
|
||||
|
||||
private static class SingletonHolder {
|
||||
|
||||
private static TransactionManager INSTANCE = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
INSTANCE = EnhancedServiceLoader.load(TransactionManager.class);
|
||||
LOGGER.info("TransactionManager Singleton {}", INSTANCE);
|
||||
} catch (Throwable anyEx) {
|
||||
LOGGER.error("Failed to load TransactionManager Singleton! ", anyEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction manager.
|
||||
*
|
||||
* @return the transaction manager
|
||||
*/
|
||||
public static TransactionManager get() {
|
||||
if (SingletonHolder.INSTANCE == null) {
|
||||
throw new ShouldNeverHappenException("TransactionManager is NOT ready!");
|
||||
}
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a TM instance.
|
||||
*
|
||||
* @param mock commonly used for test mocking
|
||||
*/
|
||||
public static void set(TransactionManager mock) {
|
||||
SingletonHolder.INSTANCE = mock;
|
||||
}
|
||||
|
||||
private TransactionManagerHolder() {
|
||||
|
||||
}
|
||||
}
|
||||
119
tm/src/main/java/io/seata/tm/api/DefaultFailureHandlerImpl.java
Normal file
119
tm/src/main/java/io/seata/tm/api/DefaultFailureHandlerImpl.java
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.netty.util.HashedWheelTimer;
|
||||
import io.netty.util.Timeout;
|
||||
import io.netty.util.TimerTask;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.logger.StackTraceLogger;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Default failure handler.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class DefaultFailureHandlerImpl implements FailureHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFailureHandlerImpl.class);
|
||||
|
||||
/**
|
||||
* Retry 1 hours by default
|
||||
*/
|
||||
private static final int RETRY_MAX_TIMES = 6 * 60;
|
||||
|
||||
private static final long SCHEDULE_INTERVAL_SECONDS = 10;
|
||||
|
||||
private static final long TICK_DURATION = 1;
|
||||
|
||||
private static final int TICKS_PER_WHEEL = 8;
|
||||
|
||||
private HashedWheelTimer timer = new HashedWheelTimer(
|
||||
new NamedThreadFactory("failedTransactionRetry", 1),
|
||||
TICK_DURATION, TimeUnit.SECONDS, TICKS_PER_WHEEL);
|
||||
|
||||
@Override
|
||||
public void onBeginFailure(GlobalTransaction tx, Throwable cause) {
|
||||
LOGGER.warn("Failed to begin transaction. ", cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommitFailure(GlobalTransaction tx, Throwable cause) {
|
||||
LOGGER.warn("Failed to commit transaction[" + tx.getXid() + "]", cause);
|
||||
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Committed), SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {
|
||||
LOGGER.warn("Failed to rollback transaction[" + tx.getXid() + "]", originalException);
|
||||
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Rollbacked), SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRollbackRetrying(GlobalTransaction tx, Throwable originalException) {
|
||||
StackTraceLogger.warn(LOGGER, originalException, "Retrying to rollback transaction[{}]", new String[] {tx.getXid()});
|
||||
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.RollbackRetrying), SCHEDULE_INTERVAL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected class CheckTimerTask implements TimerTask {
|
||||
|
||||
private final GlobalTransaction tx;
|
||||
|
||||
private final GlobalStatus required;
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private boolean isStopped = false;
|
||||
|
||||
protected CheckTimerTask(final GlobalTransaction tx, GlobalStatus required) {
|
||||
this.tx = tx;
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Timeout timeout) throws Exception {
|
||||
if (!isStopped) {
|
||||
if (++count > RETRY_MAX_TIMES) {
|
||||
LOGGER.error("transaction [{}] retry fetch status times exceed the limit [{} times]", tx.getXid(), RETRY_MAX_TIMES);
|
||||
return;
|
||||
}
|
||||
isStopped = shouldStop(tx, required);
|
||||
timer.newTimeout(this, SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldStop(final GlobalTransaction tx, GlobalStatus required) {
|
||||
try {
|
||||
GlobalStatus status = tx.getStatus();
|
||||
LOGGER.info("transaction [{}] current status is [{}]", tx.getXid(), status);
|
||||
if (status == required || status == GlobalStatus.Finished) {
|
||||
return true;
|
||||
}
|
||||
} catch (TransactionException e) {
|
||||
LOGGER.error("fetch GlobalTransaction status error", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
262
tm/src/main/java/io/seata/tm/api/DefaultGlobalTransaction.java
Normal file
262
tm/src/main/java/io/seata/tm/api/DefaultGlobalTransaction.java
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import io.seata.tm.api.transaction.SuspendedResourcesHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_TM_COMMIT_RETRY_COUNT;
|
||||
import static io.seata.common.DefaultValues.DEFAULT_TM_ROLLBACK_RETRY_COUNT;
|
||||
|
||||
/**
|
||||
* The type Default global transaction.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class DefaultGlobalTransaction implements GlobalTransaction {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGlobalTransaction.class);
|
||||
|
||||
private static final int DEFAULT_GLOBAL_TX_TIMEOUT = 60000;
|
||||
|
||||
private static final String DEFAULT_GLOBAL_TX_NAME = "default";
|
||||
|
||||
private TransactionManager transactionManager;
|
||||
|
||||
private String xid;
|
||||
|
||||
private GlobalStatus status;
|
||||
|
||||
private GlobalTransactionRole role;
|
||||
|
||||
private static final int COMMIT_RETRY_COUNT = ConfigurationFactory.getInstance().getInt(
|
||||
ConfigurationKeys.CLIENT_TM_COMMIT_RETRY_COUNT, DEFAULT_TM_COMMIT_RETRY_COUNT);
|
||||
|
||||
private static final int ROLLBACK_RETRY_COUNT = ConfigurationFactory.getInstance().getInt(
|
||||
ConfigurationKeys.CLIENT_TM_ROLLBACK_RETRY_COUNT, DEFAULT_TM_ROLLBACK_RETRY_COUNT);
|
||||
|
||||
/**
|
||||
* Instantiates a new Default global transaction.
|
||||
*/
|
||||
DefaultGlobalTransaction() {
|
||||
this(null, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Default global transaction.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param status the status
|
||||
* @param role the role
|
||||
*/
|
||||
DefaultGlobalTransaction(String xid, GlobalStatus status, GlobalTransactionRole role) {
|
||||
this.transactionManager = TransactionManagerHolder.get();
|
||||
this.xid = xid;
|
||||
this.status = status;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin() throws TransactionException {
|
||||
begin(DEFAULT_GLOBAL_TX_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(int timeout) throws TransactionException {
|
||||
begin(timeout, DEFAULT_GLOBAL_TX_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(int timeout, String name) throws TransactionException {
|
||||
if (role != GlobalTransactionRole.Launcher) {
|
||||
assertXIDNotNull();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
assertXIDNull();
|
||||
String currentXid = RootContext.getXID();
|
||||
if (currentXid != null) {
|
||||
throw new IllegalStateException("Global transaction already exists," +
|
||||
" can't begin a new global transaction, currentXid = " + currentXid);
|
||||
}
|
||||
xid = transactionManager.begin(null, null, name, timeout);
|
||||
status = GlobalStatus.Begin;
|
||||
RootContext.bind(xid);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Begin new global transaction [{}]", xid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() throws TransactionException {
|
||||
if (role == GlobalTransactionRole.Participant) {
|
||||
// Participant has no responsibility of committing
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Ignore Commit(): just involved in global transaction [{}]", xid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
assertXIDNotNull();
|
||||
int retry = COMMIT_RETRY_COUNT <= 0 ? DEFAULT_TM_COMMIT_RETRY_COUNT : COMMIT_RETRY_COUNT;
|
||||
try {
|
||||
while (retry > 0) {
|
||||
try {
|
||||
status = transactionManager.commit(xid);
|
||||
break;
|
||||
} catch (Throwable ex) {
|
||||
LOGGER.error("Failed to report global commit [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage());
|
||||
retry--;
|
||||
if (retry == 0) {
|
||||
throw new TransactionException("Failed to report global commit", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (xid.equals(RootContext.getXID())) {
|
||||
suspend();
|
||||
}
|
||||
}
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("[{}] commit status: {}", xid, status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws TransactionException {
|
||||
if (role == GlobalTransactionRole.Participant) {
|
||||
// Participant has no responsibility of rollback
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Ignore Rollback(): just involved in global transaction [{}]", xid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
assertXIDNotNull();
|
||||
|
||||
int retry = ROLLBACK_RETRY_COUNT <= 0 ? DEFAULT_TM_ROLLBACK_RETRY_COUNT : ROLLBACK_RETRY_COUNT;
|
||||
try {
|
||||
while (retry > 0) {
|
||||
try {
|
||||
status = transactionManager.rollback(xid);
|
||||
break;
|
||||
} catch (Throwable ex) {
|
||||
LOGGER.error("Failed to report global rollback [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage());
|
||||
retry--;
|
||||
if (retry == 0) {
|
||||
throw new TransactionException("Failed to report global rollback", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (xid.equals(RootContext.getXID())) {
|
||||
suspend();
|
||||
}
|
||||
}
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("[{}] rollback status: {}", xid, status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuspendedResourcesHolder suspend() throws TransactionException {
|
||||
// In order to associate the following logs with XID, first get and then unbind.
|
||||
String xid = RootContext.getXID();
|
||||
if (xid != null) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Suspending current transaction, xid = {}", xid);
|
||||
}
|
||||
RootContext.unbind();
|
||||
return new SuspendedResourcesHolder(xid);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(SuspendedResourcesHolder suspendedResourcesHolder) throws TransactionException {
|
||||
if (suspendedResourcesHolder == null) {
|
||||
return;
|
||||
}
|
||||
String xid = suspendedResourcesHolder.getXid();
|
||||
RootContext.bind(xid);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Resumimg the transaction,xid = {}", xid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getStatus() throws TransactionException {
|
||||
if (xid == null) {
|
||||
return GlobalStatus.UnKnown;
|
||||
}
|
||||
status = transactionManager.getStatus(xid);
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void globalReport(GlobalStatus globalStatus) throws TransactionException {
|
||||
assertXIDNotNull();
|
||||
|
||||
if (globalStatus == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
status = transactionManager.globalReport(xid, globalStatus);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("[{}] report status: {}", xid, status);
|
||||
}
|
||||
|
||||
if (xid.equals(RootContext.getXID())) {
|
||||
suspend();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getLocalStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalTransactionRole getGlobalTransactionRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
private void assertXIDNotNull() {
|
||||
if (xid == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertXIDNull() {
|
||||
if (xid != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
tm/src/main/java/io/seata/tm/api/FailureHandler.java
Normal file
56
tm/src/main/java/io/seata/tm/api/FailureHandler.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
/**
|
||||
* Callback on failure.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface FailureHandler {
|
||||
|
||||
/**
|
||||
* On begin failure.
|
||||
*
|
||||
* @param tx the tx
|
||||
* @param cause the cause
|
||||
*/
|
||||
void onBeginFailure(GlobalTransaction tx, Throwable cause);
|
||||
|
||||
/**
|
||||
* On commit failure.
|
||||
*
|
||||
* @param tx the tx
|
||||
* @param cause the cause
|
||||
*/
|
||||
void onCommitFailure(GlobalTransaction tx, Throwable cause);
|
||||
|
||||
/**
|
||||
* On rollback failure.
|
||||
*
|
||||
* @param tx the tx
|
||||
* @param originalException the originalException
|
||||
*/
|
||||
void onRollbackFailure(GlobalTransaction tx, Throwable originalException);
|
||||
|
||||
/**
|
||||
* On rollback retrying
|
||||
*
|
||||
* @param tx the tx
|
||||
* @param originalException the originalException
|
||||
*/
|
||||
void onRollbackRetrying(GlobalTransaction tx, Throwable originalException);
|
||||
}
|
||||
134
tm/src/main/java/io/seata/tm/api/GlobalTransaction.java
Normal file
134
tm/src/main/java/io/seata/tm/api/GlobalTransaction.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.tm.api.transaction.SuspendedResourcesHolder;
|
||||
|
||||
/**
|
||||
* Global transaction.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface GlobalTransaction {
|
||||
|
||||
/**
|
||||
* Begin a new global transaction with default timeout and name.
|
||||
*
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
void begin() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Begin a new global transaction with given timeout and default name.
|
||||
*
|
||||
* @param timeout Global transaction timeout in MILLISECONDS
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
void begin(int timeout) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Begin a new global transaction with given timeout and given name.
|
||||
*
|
||||
* @param timeout Given timeout in MILLISECONDS.
|
||||
* @param name Given name.
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
void begin(int timeout, String name) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Commit the global transaction.
|
||||
*
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
void commit() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Rollback the global transaction.
|
||||
*
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
void rollback() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Suspend the global transaction.
|
||||
*
|
||||
* @return the SuspendedResourcesHolder which holds the suspend resources
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* @see SuspendedResourcesHolder
|
||||
*/
|
||||
SuspendedResourcesHolder suspend() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Resume the global transaction.
|
||||
*
|
||||
* @param suspendedResourcesHolder the suspended resources to resume
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
* @see SuspendedResourcesHolder
|
||||
*/
|
||||
void resume(SuspendedResourcesHolder suspendedResourcesHolder) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Ask TC for current status of the corresponding global transaction.
|
||||
*
|
||||
* @return Status of the corresponding global transaction.
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
* @see GlobalStatus
|
||||
*/
|
||||
GlobalStatus getStatus() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Get XID.
|
||||
*
|
||||
* @return XID. xid
|
||||
*/
|
||||
String getXid();
|
||||
|
||||
/**
|
||||
* report the global transaction status.
|
||||
*
|
||||
* @param globalStatus global status.
|
||||
*
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
void globalReport(GlobalStatus globalStatus) throws TransactionException;
|
||||
|
||||
/**
|
||||
* local status of the global transaction.
|
||||
*
|
||||
* @return Status of the corresponding global transaction.
|
||||
* @see GlobalStatus
|
||||
*/
|
||||
GlobalStatus getLocalStatus();
|
||||
|
||||
/**
|
||||
* get global transaction role.
|
||||
*
|
||||
* @return global transaction Role.
|
||||
* @see GlobalTransactionRole
|
||||
*/
|
||||
GlobalTransactionRole getGlobalTransactionRole();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
|
||||
/**
|
||||
* GlobalTransaction API
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class GlobalTransactionContext {
|
||||
|
||||
private GlobalTransactionContext() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a new GlobalTransaction.
|
||||
*
|
||||
* @return the new global transaction
|
||||
*/
|
||||
public static GlobalTransaction createNew() {
|
||||
return new DefaultGlobalTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GlobalTransaction instance bind on current thread.
|
||||
*
|
||||
* @return null if no transaction context there.
|
||||
*/
|
||||
public static GlobalTransaction getCurrent() {
|
||||
String xid = RootContext.getXID();
|
||||
if (xid == null) {
|
||||
return null;
|
||||
}
|
||||
return new DefaultGlobalTransaction(xid, GlobalStatus.Begin, GlobalTransactionRole.Participant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GlobalTransaction instance bind on current thread. Create a new on if no existing there.
|
||||
*
|
||||
* @return new context if no existing there.
|
||||
*/
|
||||
public static GlobalTransaction getCurrentOrCreate() {
|
||||
GlobalTransaction tx = getCurrent();
|
||||
if (tx == null) {
|
||||
return createNew();
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload GlobalTransaction instance according to the given XID
|
||||
*
|
||||
* @param xid the xid
|
||||
* @return reloaded transaction instance.
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
public static GlobalTransaction reload(String xid) throws TransactionException {
|
||||
return new DefaultGlobalTransaction(xid, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher) {
|
||||
@Override
|
||||
public void begin(int timeout, String name) throws TransactionException {
|
||||
throw new IllegalStateException("Never BEGIN on a RELOADED GlobalTransaction. ");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
36
tm/src/main/java/io/seata/tm/api/GlobalTransactionRole.java
Normal file
36
tm/src/main/java/io/seata/tm/api/GlobalTransactionRole.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
/**
|
||||
* Role of current thread involve in a global transaction.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public enum GlobalTransactionRole {
|
||||
|
||||
/**
|
||||
* The Launcher.
|
||||
*/
|
||||
// The one begins the current global transaction.
|
||||
Launcher,
|
||||
|
||||
/**
|
||||
* The Participant.
|
||||
*/
|
||||
// The one just joins into a existing global transaction.
|
||||
Participant
|
||||
}
|
||||
201
tm/src/main/java/io/seata/tm/api/TransactionalExecutor.java
Normal file
201
tm/src/main/java/io/seata/tm/api/TransactionalExecutor.java
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import io.seata.tm.api.transaction.TransactionInfo;
|
||||
|
||||
/**
|
||||
* Callback for executing business logic in a global transaction.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface TransactionalExecutor {
|
||||
|
||||
/**
|
||||
* Execute the business logic here.
|
||||
*
|
||||
* @return What the business logic returns.
|
||||
* @throws Throwable Any throwable during executing.
|
||||
*/
|
||||
Object execute() throws Throwable;
|
||||
|
||||
/**
|
||||
* transaction conf or other attr
|
||||
* @return transaction info
|
||||
*/
|
||||
TransactionInfo getTransactionInfo();
|
||||
|
||||
|
||||
/**
|
||||
* The enum Code.
|
||||
*/
|
||||
enum Code {
|
||||
|
||||
/**
|
||||
* Begin failure code.
|
||||
*/
|
||||
//
|
||||
BeginFailure,
|
||||
|
||||
/**
|
||||
* Commit failure code.
|
||||
*/
|
||||
//
|
||||
CommitFailure,
|
||||
|
||||
/**
|
||||
* Rollback failure code.
|
||||
*/
|
||||
//
|
||||
RollbackFailure,
|
||||
|
||||
/**
|
||||
* Rollback done code.
|
||||
*/
|
||||
//
|
||||
RollbackDone,
|
||||
|
||||
/**
|
||||
* Report failure code.
|
||||
*/
|
||||
//
|
||||
ReportFailure,
|
||||
|
||||
/**
|
||||
* Rollback retrying code.
|
||||
*/
|
||||
//
|
||||
RollbackRetrying
|
||||
}
|
||||
|
||||
/**
|
||||
* The type Execution exception.
|
||||
*/
|
||||
class ExecutionException extends Exception {
|
||||
|
||||
private GlobalTransaction transaction;
|
||||
|
||||
private Code code;
|
||||
|
||||
private Throwable originalException;
|
||||
|
||||
/**
|
||||
* Instantiates a new Execution exception.
|
||||
*
|
||||
* @param transaction the transaction
|
||||
* @param cause the cause
|
||||
* @param code the code
|
||||
*/
|
||||
public ExecutionException(GlobalTransaction transaction, Throwable cause, Code code) {
|
||||
this(transaction, cause, code, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Execution exception.
|
||||
*
|
||||
* @param transaction the transaction
|
||||
* @param code the code
|
||||
* @param originalException the original exception
|
||||
*/
|
||||
public ExecutionException(GlobalTransaction transaction, Code code, Throwable originalException) {
|
||||
this(transaction, null, code, originalException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Execution exception.
|
||||
*
|
||||
* @param transaction the transaction
|
||||
* @param cause the cause
|
||||
* @param code the code
|
||||
* @param originalException the original exception
|
||||
*/
|
||||
public ExecutionException(GlobalTransaction transaction, Throwable cause, Code code,
|
||||
Throwable originalException) {
|
||||
this(transaction, null, cause, code, originalException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Execution exception.
|
||||
*
|
||||
* @param transaction the transaction
|
||||
* @param message the message
|
||||
* @param cause the cause
|
||||
* @param code the code
|
||||
* @param originalException the original exception
|
||||
*/
|
||||
public ExecutionException(GlobalTransaction transaction, String message, Throwable cause, Code code,
|
||||
Throwable originalException) {
|
||||
super(message, cause);
|
||||
this.transaction = transaction;
|
||||
this.code = code;
|
||||
this.originalException = originalException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transaction.
|
||||
*
|
||||
* @return the transaction
|
||||
*/
|
||||
public GlobalTransaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transaction.
|
||||
*
|
||||
* @param transaction the transaction
|
||||
*/
|
||||
public void setTransaction(GlobalTransaction transaction) {
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets code.
|
||||
*
|
||||
* @return the code
|
||||
*/
|
||||
public Code getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets code.
|
||||
*
|
||||
* @param code the code
|
||||
*/
|
||||
public void setCode(Code code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets original exception.
|
||||
*
|
||||
* @return the original exception
|
||||
*/
|
||||
public Throwable getOriginalException() {
|
||||
return originalException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets original exception.
|
||||
*
|
||||
* @param originalException the original exception
|
||||
*/
|
||||
public void setOriginalException(Throwable originalException) {
|
||||
this.originalException = originalException;
|
||||
}
|
||||
}
|
||||
}
|
||||
301
tm/src/main/java/io/seata/tm/api/TransactionalTemplate.java
Normal file
301
tm/src/main/java/io/seata/tm/api/TransactionalTemplate.java
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.core.context.GlobalLockConfigHolder;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalLockConfig;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.tm.api.transaction.Propagation;
|
||||
import io.seata.tm.api.transaction.SuspendedResourcesHolder;
|
||||
import io.seata.tm.api.transaction.TransactionHook;
|
||||
import io.seata.tm.api.transaction.TransactionHookManager;
|
||||
import io.seata.tm.api.transaction.TransactionInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Template of executing business logic with a global transaction.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class TransactionalTemplate {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionalTemplate.class);
|
||||
|
||||
|
||||
/**
|
||||
* Execute object.
|
||||
*
|
||||
* @param business the business
|
||||
* @return the object
|
||||
* @throws TransactionalExecutor.ExecutionException the execution exception
|
||||
*/
|
||||
public Object execute(TransactionalExecutor business) throws Throwable {
|
||||
// 1. Get transactionInfo
|
||||
TransactionInfo txInfo = business.getTransactionInfo();
|
||||
if (txInfo == null) {
|
||||
throw new ShouldNeverHappenException("transactionInfo does not exist");
|
||||
}
|
||||
// 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
|
||||
|
||||
// 1.2 Handle the transaction propagation.
|
||||
Propagation propagation = txInfo.getPropagation();
|
||||
SuspendedResourcesHolder suspendedResourcesHolder = null;
|
||||
try {
|
||||
switch (propagation) {
|
||||
case NOT_SUPPORTED:
|
||||
// If transaction is existing, suspend it.
|
||||
if (existingTransaction(tx)) {
|
||||
suspendedResourcesHolder = tx.suspend();
|
||||
}
|
||||
// Execute without transaction and return.
|
||||
return business.execute();
|
||||
case REQUIRES_NEW:
|
||||
// If transaction is existing, suspend it, and then begin new transaction.
|
||||
if (existingTransaction(tx)) {
|
||||
suspendedResourcesHolder = tx.suspend();
|
||||
tx = GlobalTransactionContext.createNew();
|
||||
}
|
||||
// Continue and execute with new transaction
|
||||
break;
|
||||
case SUPPORTS:
|
||||
// If transaction is not existing, execute without transaction.
|
||||
if (notExistingTransaction(tx)) {
|
||||
return business.execute();
|
||||
}
|
||||
// Continue and execute with new transaction
|
||||
break;
|
||||
case REQUIRED:
|
||||
// If current transaction is existing, execute with current transaction,
|
||||
// else continue and execute with new transaction.
|
||||
break;
|
||||
case NEVER:
|
||||
// If transaction is existing, throw exception.
|
||||
if (existingTransaction(tx)) {
|
||||
throw new TransactionException(
|
||||
String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
|
||||
, tx.getXid()));
|
||||
} else {
|
||||
// Execute without transaction and return.
|
||||
return business.execute();
|
||||
}
|
||||
case MANDATORY:
|
||||
// If transaction is not existing, throw exception.
|
||||
if (notExistingTransaction(tx)) {
|
||||
throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
|
||||
}
|
||||
// Continue and execute with current transaction.
|
||||
break;
|
||||
default:
|
||||
throw new TransactionException("Not Supported Propagation:" + propagation);
|
||||
}
|
||||
|
||||
// 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
|
||||
if (tx == null) {
|
||||
tx = GlobalTransactionContext.createNew();
|
||||
}
|
||||
|
||||
// set current tx config to holder
|
||||
GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
|
||||
|
||||
try {
|
||||
// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
|
||||
// else do nothing. Of course, the hooks will still be triggered.
|
||||
beginTransaction(txInfo, tx);
|
||||
|
||||
Object rs;
|
||||
try {
|
||||
// Do Your Business
|
||||
rs = business.execute();
|
||||
} catch (Throwable ex) {
|
||||
// 3. The needed business exception to rollback.
|
||||
completeTransactionAfterThrowing(txInfo, tx, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// 4. everything is fine, commit.
|
||||
commitTransaction(tx);
|
||||
|
||||
return rs;
|
||||
} finally {
|
||||
//5. clear
|
||||
resumeGlobalLockConfig(previousConfig);
|
||||
triggerAfterCompletion();
|
||||
cleanUp();
|
||||
}
|
||||
} finally {
|
||||
// If the transaction is suspended, resume it.
|
||||
if (suspendedResourcesHolder != null) {
|
||||
tx.resume(suspendedResourcesHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean existingTransaction(GlobalTransaction tx) {
|
||||
return tx != null;
|
||||
}
|
||||
|
||||
private boolean notExistingTransaction(GlobalTransaction tx) {
|
||||
return tx == null;
|
||||
}
|
||||
|
||||
private GlobalLockConfig replaceGlobalLockConfig(TransactionInfo info) {
|
||||
GlobalLockConfig myConfig = new GlobalLockConfig();
|
||||
myConfig.setLockRetryInternal(info.getLockRetryInternal());
|
||||
myConfig.setLockRetryTimes(info.getLockRetryTimes());
|
||||
return GlobalLockConfigHolder.setAndReturnPrevious(myConfig);
|
||||
}
|
||||
|
||||
private void resumeGlobalLockConfig(GlobalLockConfig config) {
|
||||
if (config != null) {
|
||||
GlobalLockConfigHolder.setAndReturnPrevious(config);
|
||||
} else {
|
||||
GlobalLockConfigHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException {
|
||||
//roll back
|
||||
if (txInfo != null && txInfo.rollbackOn(originalException)) {
|
||||
try {
|
||||
rollbackTransaction(tx, originalException);
|
||||
} catch (TransactionException txe) {
|
||||
// Failed to rollback
|
||||
throw new TransactionalExecutor.ExecutionException(tx, txe,
|
||||
TransactionalExecutor.Code.RollbackFailure, originalException);
|
||||
}
|
||||
} else {
|
||||
// not roll back on this exception, so commit
|
||||
commitTransaction(tx);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
|
||||
try {
|
||||
triggerBeforeCommit();
|
||||
tx.commit();
|
||||
triggerAfterCommit();
|
||||
} catch (TransactionException txe) {
|
||||
// 4.1 Failed to commit
|
||||
throw new TransactionalExecutor.ExecutionException(tx, txe,
|
||||
TransactionalExecutor.Code.CommitFailure);
|
||||
}
|
||||
}
|
||||
|
||||
private void rollbackTransaction(GlobalTransaction tx, Throwable originalException) throws TransactionException, TransactionalExecutor.ExecutionException {
|
||||
triggerBeforeRollback();
|
||||
tx.rollback();
|
||||
triggerAfterRollback();
|
||||
// 3.1 Successfully rolled back
|
||||
throw new TransactionalExecutor.ExecutionException(tx, GlobalStatus.RollbackRetrying.equals(tx.getLocalStatus())
|
||||
? TransactionalExecutor.Code.RollbackRetrying : TransactionalExecutor.Code.RollbackDone, originalException);
|
||||
}
|
||||
|
||||
private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
|
||||
try {
|
||||
triggerBeforeBegin();
|
||||
tx.begin(txInfo.getTimeOut(), txInfo.getName());
|
||||
triggerAfterBegin();
|
||||
} catch (TransactionException txe) {
|
||||
throw new TransactionalExecutor.ExecutionException(tx, txe,
|
||||
TransactionalExecutor.Code.BeginFailure);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerBeforeBegin() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.beforeBegin();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute beforeBegin in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerAfterBegin() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.afterBegin();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute afterBegin in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerBeforeRollback() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.beforeRollback();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute beforeRollback in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerAfterRollback() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.afterRollback();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute afterRollback in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerBeforeCommit() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.beforeCommit();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute beforeCommit in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerAfterCommit() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.afterCommit();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute afterCommit in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerAfterCompletion() {
|
||||
for (TransactionHook hook : getCurrentHooks()) {
|
||||
try {
|
||||
hook.afterCompletion();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed execute afterCompletion in hook {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanUp() {
|
||||
TransactionHookManager.clear();
|
||||
}
|
||||
|
||||
private List<TransactionHook> getCurrentHooks() {
|
||||
return TransactionHookManager.getHooks();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class NoRollbackRule extends RollbackRule {
|
||||
|
||||
public static final NoRollbackRule DEFAULT_NO_ROLLBACK_RULE = new NoRollbackRule(Throwable.class);
|
||||
|
||||
|
||||
public NoRollbackRule(Class<?> clazz) {
|
||||
super(clazz);
|
||||
}
|
||||
|
||||
|
||||
public NoRollbackRule(String exceptionName) {
|
||||
super(exceptionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return super.equals(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "No" + super.toString();
|
||||
}
|
||||
}
|
||||
177
tm/src/main/java/io/seata/tm/api/transaction/Propagation.java
Normal file
177
tm/src/main/java/io/seata/tm/api/transaction/Propagation.java
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import io.seata.tm.api.TransactionalExecutor;
|
||||
|
||||
/**
|
||||
* Propagation level of global transactions.
|
||||
*
|
||||
* @author haozhibei
|
||||
* @see io.seata.spring.annotation.GlobalTransactional#propagation() // TM annotation
|
||||
* @see io.seata.spring.annotation.GlobalTransactionalInterceptor#invoke(MethodInvocation) // the interceptor of TM
|
||||
* @see io.seata.tm.api.TransactionalTemplate#execute(TransactionalExecutor) // the transaction template of TM
|
||||
*/
|
||||
public enum Propagation {
|
||||
/**
|
||||
* The REQUIRED.
|
||||
* The default propagation.
|
||||
*
|
||||
* <p>
|
||||
* If transaction is existing, execute with current transaction,
|
||||
* else execute with new transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The logic is similar to the following code:
|
||||
* <code><pre>
|
||||
* if (tx == null) {
|
||||
* try {
|
||||
* tx = beginNewTransaction(); // begin new transaction, is not existing
|
||||
* Object rs = business.execute(); // execute with new transaction
|
||||
* commitTransaction(tx);
|
||||
* return rs;
|
||||
* } catch (Exception ex) {
|
||||
* rollbackTransaction(tx);
|
||||
* throw ex;
|
||||
* }
|
||||
* } else {
|
||||
* return business.execute(); // execute with current transaction
|
||||
* }
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*/
|
||||
REQUIRED,
|
||||
|
||||
/**
|
||||
* The REQUIRES_NEW.
|
||||
*
|
||||
* <p>
|
||||
* If transaction is existing, suspend it, and then execute business with new transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The logic is similar to the following code:
|
||||
* <code><pre>
|
||||
* try {
|
||||
* if (tx != null) {
|
||||
* suspendedResource = suspendTransaction(tx); // suspend current transaction
|
||||
* }
|
||||
* try {
|
||||
* tx = beginNewTransaction(); // begin new transaction
|
||||
* Object rs = business.execute(); // execute with new transaction
|
||||
* commitTransaction(tx);
|
||||
* return rs;
|
||||
* } catch (Exception ex) {
|
||||
* rollbackTransaction(tx);
|
||||
* throw ex;
|
||||
* }
|
||||
* } finally {
|
||||
* if (suspendedResource != null) {
|
||||
* resumeTransaction(suspendedResource); // resume transaction
|
||||
* }
|
||||
* }
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*/
|
||||
REQUIRES_NEW,
|
||||
|
||||
/**
|
||||
* The NOT_SUPPORTED.
|
||||
*
|
||||
* <p>
|
||||
* If transaction is existing, suspend it, and then execute business without transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The logic is similar to the following code:
|
||||
* <code><pre>
|
||||
* try {
|
||||
* if (tx != null) {
|
||||
* suspendedResource = suspendTransaction(tx); // suspend current transaction
|
||||
* }
|
||||
* return business.execute(); // execute without transaction
|
||||
* } finally {
|
||||
* if (suspendedResource != null) {
|
||||
* resumeTransaction(suspendedResource); // resume transaction
|
||||
* }
|
||||
* }
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*/
|
||||
NOT_SUPPORTED,
|
||||
|
||||
/**
|
||||
* The SUPPORTS.
|
||||
*
|
||||
* <p>
|
||||
* If transaction is not existing, execute without global transaction,
|
||||
* else execute business with current transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The logic is similar to the following code:
|
||||
* <code><pre>
|
||||
* if (tx != null) {
|
||||
* return business.execute(); // execute with current transaction
|
||||
* } else {
|
||||
* return business.execute(); // execute without transaction
|
||||
* }
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*/
|
||||
SUPPORTS,
|
||||
|
||||
/**
|
||||
* The NEVER.
|
||||
*
|
||||
* <p>
|
||||
* If transaction is existing, throw exception,
|
||||
* else execute business without transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The logic is similar to the following code:
|
||||
* <code><pre>
|
||||
* if (tx != null) {
|
||||
* throw new TransactionException("existing transaction");
|
||||
* }
|
||||
* return business.execute(); // execute without transaction
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* The MANDATORY.
|
||||
*
|
||||
* <p>
|
||||
* If transaction is not existing, throw exception,
|
||||
* else execute business with current transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The logic is similar to the following code:
|
||||
* <code><pre>
|
||||
* if (tx == null) {
|
||||
* throw new TransactionException("not existing transaction");
|
||||
* }
|
||||
* return business.execute(); // execute with current transaction
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*/
|
||||
MANDATORY
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import io.seata.common.util.StringUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class RollbackRule implements Serializable {
|
||||
|
||||
|
||||
private final String exceptionName;
|
||||
|
||||
public RollbackRule(String exceptionName) {
|
||||
if (StringUtils.isNullOrEmpty(exceptionName)) {
|
||||
throw new IllegalArgumentException("'exceptionName' cannot be null or empty");
|
||||
}
|
||||
this.exceptionName = exceptionName;
|
||||
}
|
||||
|
||||
public RollbackRule(Class<?> clazz) {
|
||||
if (clazz == null) {
|
||||
throw new NullPointerException("'clazz' cannot be null");
|
||||
}
|
||||
if (!Throwable.class.isAssignableFrom(clazz)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable");
|
||||
}
|
||||
this.exceptionName = clazz.getName();
|
||||
}
|
||||
|
||||
public String getExceptionName() {
|
||||
return this.exceptionName;
|
||||
}
|
||||
|
||||
|
||||
public int getDepth(Throwable ex) {
|
||||
return getDepth(ex.getClass(), 0);
|
||||
}
|
||||
|
||||
|
||||
private int getDepth(Class<?> exceptionClass, int depth) {
|
||||
if (exceptionClass.getName().contains(this.exceptionName)) {
|
||||
// Found it!
|
||||
return depth;
|
||||
}
|
||||
// If we've gone as far as we can go and haven't found it...
|
||||
if (exceptionClass == Throwable.class) {
|
||||
return -1;
|
||||
}
|
||||
return getDepth(exceptionClass.getSuperclass(), depth + 1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof RollbackRule)) {
|
||||
return false;
|
||||
}
|
||||
RollbackRule rhs = (RollbackRule) other;
|
||||
return this.exceptionName.equals(rhs.exceptionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.exceptionName.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RollbackRule with pattern [" + this.exceptionName + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Holder for suspended resources to support propagation or nested logic.
|
||||
* Used by {@code suspend} and {@code resume}
|
||||
*
|
||||
* @author wangzhongxiang
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class SuspendedResourcesHolder {
|
||||
|
||||
/**
|
||||
* The xid
|
||||
*/
|
||||
private String xid;
|
||||
|
||||
public SuspendedResourcesHolder(String xid) {
|
||||
if (xid == null) {
|
||||
throw new IllegalArgumentException("xid must be not null");
|
||||
}
|
||||
this.xid = xid;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public interface TransactionHook {
|
||||
|
||||
/**
|
||||
* before tx begin
|
||||
*/
|
||||
void beforeBegin();
|
||||
|
||||
/**
|
||||
* after tx begin
|
||||
*/
|
||||
void afterBegin();
|
||||
|
||||
/**
|
||||
* before tx commit
|
||||
*/
|
||||
void beforeCommit();
|
||||
|
||||
/**
|
||||
* after tx commit
|
||||
*/
|
||||
void afterCommit();
|
||||
|
||||
/**
|
||||
* before tx rollback
|
||||
*/
|
||||
void beforeRollback();
|
||||
|
||||
/**
|
||||
* after tx rollback
|
||||
*/
|
||||
void afterRollback();
|
||||
|
||||
/**
|
||||
* after tx all Completed
|
||||
*/
|
||||
void afterCompletion();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class TransactionHookAdapter implements TransactionHook {
|
||||
|
||||
@Override
|
||||
public void beforeBegin() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterBegin() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCommit() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeRollback() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRollback() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public final class TransactionHookManager {
|
||||
|
||||
private TransactionHookManager() {
|
||||
|
||||
}
|
||||
|
||||
private static final ThreadLocal<List<TransactionHook>> LOCAL_HOOKS = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* get the current hooks
|
||||
*
|
||||
* @return TransactionHook list
|
||||
* @throws IllegalStateException IllegalStateException
|
||||
*/
|
||||
public static List<TransactionHook> getHooks() throws IllegalStateException {
|
||||
List<TransactionHook> hooks = LOCAL_HOOKS.get();
|
||||
|
||||
if (hooks == null || hooks.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(hooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* add new hook
|
||||
*
|
||||
* @param transactionHook transactionHook
|
||||
*/
|
||||
public static void registerHook(TransactionHook transactionHook) {
|
||||
if (transactionHook == null) {
|
||||
throw new NullPointerException("transactionHook must not be null");
|
||||
}
|
||||
List<TransactionHook> transactionHooks = LOCAL_HOOKS.get();
|
||||
if (transactionHooks == null) {
|
||||
LOCAL_HOOKS.set(new ArrayList<>());
|
||||
}
|
||||
LOCAL_HOOKS.get().add(transactionHook);
|
||||
}
|
||||
|
||||
/**
|
||||
* clear hooks
|
||||
*/
|
||||
public static void clear() {
|
||||
LOCAL_HOOKS.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public final class TransactionInfo implements Serializable {
|
||||
|
||||
private int timeOut;
|
||||
|
||||
private String name;
|
||||
|
||||
private Set<RollbackRule> rollbackRules;
|
||||
|
||||
private Propagation propagation;
|
||||
|
||||
private int lockRetryInternal;
|
||||
|
||||
private int lockRetryTimes;
|
||||
|
||||
public int getTimeOut() {
|
||||
return timeOut;
|
||||
}
|
||||
|
||||
public void setTimeOut(int timeOut) {
|
||||
this.timeOut = timeOut;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Set<RollbackRule> getRollbackRules() {
|
||||
return rollbackRules;
|
||||
}
|
||||
|
||||
public void setRollbackRules(Set<RollbackRule> rollbackRules) {
|
||||
this.rollbackRules = rollbackRules;
|
||||
}
|
||||
|
||||
public boolean rollbackOn(Throwable ex) {
|
||||
|
||||
RollbackRule winner = null;
|
||||
int deepest = Integer.MAX_VALUE;
|
||||
|
||||
if (CollectionUtils.isNotEmpty(rollbackRules)) {
|
||||
winner = NoRollbackRule.DEFAULT_NO_ROLLBACK_RULE;
|
||||
for (RollbackRule rule : this.rollbackRules) {
|
||||
int depth = rule.getDepth(ex);
|
||||
if (depth >= 0 && depth < deepest) {
|
||||
deepest = depth;
|
||||
winner = rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !(winner instanceof NoRollbackRule);
|
||||
}
|
||||
|
||||
public Propagation getPropagation() {
|
||||
if (this.propagation != null) {
|
||||
return this.propagation;
|
||||
}
|
||||
//default propagation
|
||||
return Propagation.REQUIRED;
|
||||
}
|
||||
|
||||
public void setPropagation(Propagation propagation) {
|
||||
this.propagation = propagation;
|
||||
}
|
||||
|
||||
public int getLockRetryInternal() {
|
||||
return lockRetryInternal;
|
||||
}
|
||||
|
||||
public void setLockRetryInternal(int lockRetryInternal) {
|
||||
this.lockRetryInternal = lockRetryInternal;
|
||||
}
|
||||
|
||||
public int getLockRetryTimes() {
|
||||
return lockRetryTimes;
|
||||
}
|
||||
|
||||
public void setLockRetryTimes(int lockRetryTimes) {
|
||||
this.lockRetryTimes = lockRetryTimes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.tm.DefaultTransactionManager
|
||||
37
tm/src/test/java/io/seata/tm/TMClientTest.java
Normal file
37
tm/src/test/java/io/seata/tm/TMClientTest.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm;
|
||||
|
||||
import io.seata.core.rpc.netty.TmNettyRemotingClient;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* test for tmClient
|
||||
* @author Ifdevil
|
||||
*/
|
||||
public class TMClientTest {
|
||||
|
||||
private static final String APPLICATION_ID = "my_app_test";
|
||||
private static final String SERVICE_GROUP = "my_test_tx_group";
|
||||
|
||||
@Test
|
||||
public void testInit(){
|
||||
TMClient.init(APPLICATION_ID,SERVICE_GROUP);
|
||||
TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance();
|
||||
Assertions.assertEquals(tmNettyRemotingClient.getTransactionServiceGroup(),SERVICE_GROUP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm;
|
||||
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author wangwei
|
||||
*/
|
||||
class TransactionManagerHolderTest {
|
||||
|
||||
|
||||
@Test
|
||||
void getTest() {
|
||||
Assertions.assertThrows(ShouldNeverHappenException.class, () -> { TransactionManagerHolder.set(null);
|
||||
TransactionManagerHolder.get();});
|
||||
}
|
||||
|
||||
}
|
||||
229
tm/src/test/java/io/seata/tm/api/APITest.java
Normal file
229
tm/src/test/java/io/seata/tm/api/APITest.java
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import io.seata.tm.api.transaction.TransactionInfo;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* The type Api test.
|
||||
*/
|
||||
public class APITest {
|
||||
|
||||
private static final String DEFAULT_XID = "1234567890";
|
||||
private static final String TX_NAME = "test";
|
||||
private static final int TIME_OUT = 30000;
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
TransactionManagerHolder.set(new TransactionManager() {
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
return DEFAULT_XID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
return GlobalStatus.Committed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean root context.
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanRootContext() {
|
||||
RootContext.unbind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test current.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Test
|
||||
public void testCurrent() throws Exception {
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
Assertions.assertEquals(tx.getXid(), DEFAULT_XID);
|
||||
Assertions.assertEquals(tx.getStatus(), GlobalStatus.Begin);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test new tx.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Test
|
||||
public void testNewTx() throws Exception {
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
Assertions.assertEquals(tx.getStatus(), GlobalStatus.UnKnown);
|
||||
Assertions.assertNull(tx.getXid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test begin.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Test
|
||||
public void testBegin() throws Exception {
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx.begin();
|
||||
Assertions.assertEquals(tx.getStatus(), GlobalStatus.Begin);
|
||||
Assertions.assertNotNull(tx.getXid());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test nested commit.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Test
|
||||
public void testNestedCommit() throws Throwable {
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
template.execute(new AbstractTransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
template.execute(new AbstractTransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
template.execute(new AbstractTransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test nested rollback.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Test
|
||||
public void testNestedRollback() throws Throwable {
|
||||
|
||||
final String oexMsg = "xxx";
|
||||
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
try {
|
||||
template.execute(new AbstractTransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
try {
|
||||
template.execute(new AbstractTransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
try {
|
||||
template.execute(new AbstractTransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
throw new RuntimeException(oexMsg);
|
||||
}
|
||||
});
|
||||
} catch (TransactionalExecutor.ExecutionException ex) {
|
||||
throw ex.getOriginalException();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (TransactionalExecutor.ExecutionException ex) {
|
||||
throw ex.getOriginalException();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (TransactionalExecutor.ExecutionException ex) {
|
||||
Throwable oex = ex.getOriginalException();
|
||||
Assertions.assertEquals(oex.getMessage(), oexMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGlobalReport() throws Exception {
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx.globalReport(tx.getStatus());
|
||||
|
||||
Assertions.assertThrows(IllegalStateException.class, () -> tx.globalReport(null));
|
||||
Assertions.assertThrows(IllegalStateException.class, () -> {
|
||||
RootContext.unbind();
|
||||
GlobalTransaction tx2 = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx2.globalReport(tx2.getStatus());
|
||||
});
|
||||
|
||||
Assertions.assertEquals(tx.getStatus(), GlobalStatus.Begin);
|
||||
Assertions.assertNotNull(tx.getXid());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static abstract class AbstractTransactionalExecutor implements TransactionalExecutor {
|
||||
|
||||
@Override
|
||||
public TransactionInfo getTransactionInfo() {
|
||||
TransactionInfo txInfo = new TransactionInfo();
|
||||
txInfo.setTimeOut(TIME_OUT);
|
||||
txInfo.setName(TX_NAME);
|
||||
return txInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
|
||||
import io.netty.util.HashedWheelTimer;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import io.seata.tm.api.transaction.MyRuntimeException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @author wangwei
|
||||
*/
|
||||
class DefaultFailureHandlerImplTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFailureHandlerImplTest.class);
|
||||
|
||||
private static final String DEFAULT_XID = "1234567890";
|
||||
private static GlobalStatus globalStatus = GlobalStatus.Begin;
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
|
||||
TransactionManagerHolder.set(new TransactionManager() {
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
return DEFAULT_XID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
return GlobalStatus.Committed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
return GlobalStatus.Rollbacked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getStatus(String xid) throws TransactionException {
|
||||
return globalStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
return globalStatus;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void onBeginFailure() {
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
FailureHandler failureHandler = new DefaultFailureHandlerImpl();
|
||||
failureHandler.onBeginFailure(tx, new MyRuntimeException("").getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onCommitFailure() throws Exception{
|
||||
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
FailureHandler failureHandler = new DefaultFailureHandlerImpl();
|
||||
failureHandler.onCommitFailure(tx, new MyRuntimeException("").getCause());
|
||||
|
||||
// get timer
|
||||
Class<?> c = Class.forName("io.seata.tm.api.DefaultFailureHandlerImpl");
|
||||
Field field = c.getDeclaredField("timer");
|
||||
field.setAccessible(true);
|
||||
HashedWheelTimer timer = (HashedWheelTimer) field.get(failureHandler);
|
||||
// assert timer pendingCount: first time is 1
|
||||
Long pendingTimeout = timer.pendingTimeouts();
|
||||
Assertions.assertEquals(pendingTimeout,1L);
|
||||
//set globalStatus
|
||||
globalStatus= GlobalStatus.Committed;
|
||||
Thread.sleep(25*1000L);
|
||||
pendingTimeout = timer.pendingTimeouts();
|
||||
LOGGER.info("pendingTimeout {}" ,pendingTimeout);
|
||||
//all timer is done
|
||||
Assertions.assertEquals(pendingTimeout,0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onRollbackFailure() throws Exception {
|
||||
|
||||
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
FailureHandler failureHandler = new DefaultFailureHandlerImpl();
|
||||
failureHandler.onRollbackFailure(tx, new MyRuntimeException("").getCause());
|
||||
|
||||
// get timer
|
||||
Class<?> c = Class.forName("io.seata.tm.api.DefaultFailureHandlerImpl");
|
||||
Field field = c.getDeclaredField("timer");
|
||||
field.setAccessible(true);
|
||||
HashedWheelTimer timer = (HashedWheelTimer) field.get(failureHandler);
|
||||
// assert timer pendingCount: first time is 1
|
||||
Long pendingTimeout = timer.pendingTimeouts();
|
||||
Assertions.assertEquals(pendingTimeout,1L);
|
||||
//set globalStatus
|
||||
globalStatus= GlobalStatus.Rollbacked;
|
||||
Thread.sleep(25*1000L);
|
||||
pendingTimeout = timer.pendingTimeouts();
|
||||
LOGGER.info("pendingTimeout {}" ,pendingTimeout);
|
||||
//all timer is done
|
||||
Assertions.assertEquals(pendingTimeout,0L);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import io.seata.tm.api.transaction.MyRuntimeException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author wangwei
|
||||
*/
|
||||
class DefaultGlobalTransactionTest {
|
||||
private static final String DEFAULT_XID = "1234567890";
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
|
||||
TransactionManagerHolder.set(new TransactionManager() {
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
return DEFAULT_XID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
throw new MyRuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
throw new MyRuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getStatus(String xid) throws TransactionException {
|
||||
throw new MyRuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
return globalStatus;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void commitRetryExceptionTest() throws TransactionException {
|
||||
RootContext.unbind();
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx.begin();
|
||||
Assertions.assertThrows(TransactionException.class, tx::commit);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNoXIDExceptionTest() throws TransactionException {
|
||||
RootContext.unbind();
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
Assertions.assertThrows(IllegalStateException.class, tx::commit);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void rollBackRetryExceptionTest() throws TransactionException {
|
||||
RootContext.unbind();
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx.begin();
|
||||
Assertions.assertThrows(TransactionException.class, tx::rollback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollBackNoXIDExceptionTest() throws TransactionException {
|
||||
RootContext.unbind();
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx.begin();
|
||||
Assertions.assertThrows(TransactionException.class, tx::rollback);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author wangwei
|
||||
*/
|
||||
class GlobalTransactionContextTest {
|
||||
private static final String DEFAULT_XID = "1234567890";
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
|
||||
TransactionManagerHolder.set(new TransactionManager() {
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
return DEFAULT_XID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
return GlobalStatus.Committed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void reloadTest() throws TransactionException {
|
||||
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
|
||||
tx = GlobalTransactionContext.reload(DEFAULT_XID);
|
||||
GlobalTransaction finalTx = tx;
|
||||
Assertions.assertThrows(IllegalStateException.class, finalTx::begin);
|
||||
|
||||
}
|
||||
}
|
||||
157
tm/src/test/java/io/seata/tm/api/TransactionTemplateTest.java
Normal file
157
tm/src/test/java/io/seata/tm/api/TransactionTemplateTest.java
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import io.seata.tm.api.transaction.NoRollbackRule;
|
||||
import io.seata.tm.api.transaction.RollbackRule;
|
||||
import io.seata.tm.api.transaction.TransactionHook;
|
||||
import io.seata.tm.api.transaction.TransactionHookManager;
|
||||
import io.seata.tm.api.transaction.TransactionInfo;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class TransactionTemplateTest {
|
||||
|
||||
private static final String DEFAULT_XID = "123456789";
|
||||
private static final String DEFAULT_NAME = "test";
|
||||
private static final int DEFAULT_TIME_OUT = 30000;
|
||||
|
||||
private TransactionalExecutor transactionalExecutor;
|
||||
|
||||
@BeforeEach
|
||||
public void init() throws Exception {
|
||||
// mock transactionManager
|
||||
TransactionManager transactionManager = mock(TransactionManager.class);
|
||||
when(transactionManager.begin(null, null, DEFAULT_NAME, DEFAULT_TIME_OUT)).thenReturn(DEFAULT_XID);
|
||||
when(transactionManager.commit(DEFAULT_XID)).thenReturn(GlobalStatus.Committed);
|
||||
when(transactionManager.rollback(DEFAULT_XID)).thenReturn(GlobalStatus.Rollbacked);
|
||||
when(transactionManager.getStatus(DEFAULT_XID)).thenReturn(GlobalStatus.Begin);
|
||||
TransactionManagerHolder.set(transactionManager);
|
||||
|
||||
//mock transactionalExecutor
|
||||
transactionalExecutor = Mockito.mock(TransactionalExecutor.class);
|
||||
TransactionInfo txInfo = new TransactionInfo();
|
||||
txInfo.setTimeOut(DEFAULT_TIME_OUT);
|
||||
txInfo.setName(DEFAULT_NAME);
|
||||
when(transactionalExecutor.getTransactionInfo()).thenReturn(txInfo);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void assertHooks() {
|
||||
assertThat(TransactionHookManager.getHooks()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionCommitHook() throws Throwable {
|
||||
TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
|
||||
|
||||
TransactionHookManager.registerHook(transactionHook);
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
template.execute(transactionalExecutor);
|
||||
verifyCommit(transactionHook);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionRollbackHook() throws Throwable {
|
||||
TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
|
||||
when(transactionalExecutor.execute()).thenThrow(new RuntimeException());
|
||||
TransactionHookManager.registerHook(transactionHook);
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
try {
|
||||
template.execute(transactionalExecutor);
|
||||
} catch (Exception e) {
|
||||
//catch rollback exception
|
||||
}
|
||||
verifyRollBack(transactionHook);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionRollbackHook_WithRollBackRule() throws Throwable {
|
||||
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
|
||||
rollbackRules.add(new RollbackRule(NullPointerException.class));
|
||||
TransactionHook transactionHook = testRollBackRules(rollbackRules, new NullPointerException());
|
||||
verifyRollBack(transactionHook);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionRollbackHook_WithNoRollBackRule() throws Throwable {
|
||||
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
|
||||
rollbackRules.add(new NoRollbackRule(NullPointerException.class));
|
||||
TransactionHook transactionHook = testRollBackRules(rollbackRules, new NullPointerException());
|
||||
verifyCommit(transactionHook);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionRollbackHook_WithSameYesNoRollBackRule() throws Throwable {
|
||||
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
|
||||
rollbackRules.add(new RollbackRule(NullPointerException.class));
|
||||
rollbackRules.add(new NoRollbackRule(NullPointerException.class));
|
||||
TransactionHook transactionHook = testRollBackRules(rollbackRules, new NullPointerException());
|
||||
verifyRollBack(transactionHook);
|
||||
}
|
||||
|
||||
private TransactionHook testRollBackRules(Set<RollbackRule> rollbackRules, Throwable throwable) throws Throwable {
|
||||
TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
|
||||
// mock txInfo
|
||||
TransactionInfo txInfo = new TransactionInfo();
|
||||
txInfo.setTimeOut(DEFAULT_TIME_OUT);
|
||||
txInfo.setName(DEFAULT_NAME);
|
||||
txInfo.setRollbackRules(rollbackRules);
|
||||
when(transactionalExecutor.getTransactionInfo()).thenReturn(txInfo);
|
||||
|
||||
when(transactionalExecutor.execute()).thenThrow(throwable);
|
||||
TransactionHookManager.registerHook(transactionHook);
|
||||
TransactionalTemplate template = new TransactionalTemplate();
|
||||
try {
|
||||
template.execute(transactionalExecutor);
|
||||
} catch (Exception e) {
|
||||
//catch rollback exception
|
||||
}
|
||||
return transactionHook;
|
||||
}
|
||||
|
||||
private void verifyCommit(TransactionHook transactionHook) {
|
||||
verify(transactionHook).beforeBegin();
|
||||
verify(transactionHook).afterBegin();
|
||||
verify(transactionHook).beforeCommit();
|
||||
verify(transactionHook).afterCommit();
|
||||
verify(transactionHook).afterCompletion();
|
||||
}
|
||||
|
||||
private void verifyRollBack(TransactionHook transactionHook) {
|
||||
verify(transactionHook).beforeBegin();
|
||||
verify(transactionHook).afterBegin();
|
||||
verify(transactionHook).beforeRollback();
|
||||
verify(transactionHook).afterRollback();
|
||||
verify(transactionHook).afterCompletion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class MyRuntimeException extends RuntimeException {
|
||||
|
||||
public MyRuntimeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author l81893521
|
||||
*/
|
||||
public class NoRollbackRuleTest {
|
||||
|
||||
@Test
|
||||
public void equalsTest(){
|
||||
RollbackRule rollbackRuleByClass = new NoRollbackRule(Exception.class);
|
||||
RollbackRule otherRollbackRuleByClass = new NoRollbackRule(Exception.class);
|
||||
Assertions.assertEquals(rollbackRuleByClass, otherRollbackRuleByClass);
|
||||
RollbackRule rollbackRuleByName = new NoRollbackRule(Exception.class.getName());
|
||||
RollbackRule otherRollbackRuleByName = new NoRollbackRule(Exception.class.getName());
|
||||
Assertions.assertEquals(rollbackRuleByName, otherRollbackRuleByName);
|
||||
NoRollbackRule otherRollbackRuleByName3 = new NoRollbackRule(Exception.class.getName());
|
||||
Assertions.assertEquals(otherRollbackRuleByName3.toString(),"NoRollbackRule with pattern [" + Exception.class.getName() + "]");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class RollbackRuleTest {
|
||||
|
||||
@Test
|
||||
public void foundImmediatelyWithString() {
|
||||
RollbackRule rr = new RollbackRule(Exception.class.getName());
|
||||
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void RollbackRule() {
|
||||
RollbackRule rr = new RollbackRule(Exception.class);
|
||||
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notFound() {
|
||||
RollbackRule rr = new RollbackRule(java.io.IOException.class.getName());
|
||||
assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ancestry() {
|
||||
RollbackRule rr = new RollbackRule(java.lang.Exception.class.getName());
|
||||
// Exception -> Runtime -> MyRuntimeException
|
||||
assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alwaysTrueForThrowable() {
|
||||
RollbackRule rr = new RollbackRule(java.lang.Throwable.class.getName());
|
||||
assertThat(rr.getDepth(new MyRuntimeException("")) > 0).isTrue();
|
||||
assertThat(rr.getDepth(new IOException()) > 0).isTrue();
|
||||
assertThat(rr.getDepth(new ShouldNeverHappenException(null, null)) > 0).isTrue();
|
||||
assertThat(rr.getDepth(new RuntimeException()) > 0).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ctorArgMustBeAThrowableClassWithNonThrowableType() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> new RollbackRule(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ctorArgMustBeAThrowableClassWithNullThrowableType() {
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new RollbackRule((Class<?>) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ctorArgExceptionStringNameVersionWithNull() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> new RollbackRule((String) null));
|
||||
}
|
||||
@Test
|
||||
public void toStringTest(){
|
||||
RollbackRule otherRollbackRuleByName = new RollbackRule(Exception.class.getName());
|
||||
Assertions.assertEquals(otherRollbackRuleByName.toString(), String.format("RollbackRule with pattern [%s]", Exception.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsTest(){
|
||||
RollbackRule otherRollbackRuleByName = new RollbackRule(Exception.class.getName());
|
||||
RollbackRule otherRollbackRuleByName2 = new NoRollbackRule(Exception.class.getName());
|
||||
|
||||
Assertions.assertNotEquals("", otherRollbackRuleByName);
|
||||
Assertions.assertEquals(otherRollbackRuleByName, otherRollbackRuleByName);
|
||||
Assertions.assertEquals(otherRollbackRuleByName, otherRollbackRuleByName2);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class TransactionHookManagerTest {
|
||||
|
||||
@AfterEach
|
||||
public void clear() {
|
||||
TransactionHookManager.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterHook() {
|
||||
TransactionHookAdapter transactionHookAdapter = new TransactionHookAdapter();
|
||||
TransactionHookManager.registerHook(transactionHookAdapter);
|
||||
List<TransactionHook> hooks = TransactionHookManager.getHooks();
|
||||
assertThat(hooks).isNotEmpty();
|
||||
assertThat(hooks.get(0)).isEqualTo(transactionHookAdapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHooks() {
|
||||
assertThat(TransactionHookManager.getHooks()).isEmpty();
|
||||
TransactionHookManager.registerHook(new TransactionHookAdapter());
|
||||
assertThat(TransactionHookManager.getHooks()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() {
|
||||
assertThat(TransactionHookManager.getHooks()).isEmpty();
|
||||
TransactionHookManager.registerHook(new TransactionHookAdapter());
|
||||
assertThat(TransactionHookManager.getHooks()).isNotEmpty();
|
||||
TransactionHookManager.clear();
|
||||
assertThat(TransactionHookManager.getHooks()).isEmpty();
|
||||
}
|
||||
@Test
|
||||
public void testNPE() {
|
||||
Assertions.assertThrows(NullPointerException.class, () -> TransactionHookManager.registerHook(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.tm.api.transaction;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author guoyao
|
||||
*/
|
||||
public class TransactionInfoTest {
|
||||
|
||||
private static final String IO_EXCEPTION_SHORT_NAME = "IOException";
|
||||
|
||||
@Test
|
||||
public void testRollBackOn() {
|
||||
TransactionInfo txInfo = new TransactionInfo();
|
||||
|
||||
//default true
|
||||
assertThat(txInfo.rollbackOn(new IllegalArgumentException())).isTrue();
|
||||
assertThat(txInfo.rollbackOn(new Exception())).isTrue();
|
||||
assertThat(txInfo.rollbackOn(new IOException())).isTrue();
|
||||
assertThat(txInfo.rollbackOn(new NullPointerException())).isTrue();
|
||||
|
||||
Set<RollbackRule> sets = getRollbackRules();
|
||||
txInfo.setRollbackRules(sets);
|
||||
|
||||
assertThat(txInfo.rollbackOn(new IllegalArgumentException())).isTrue();
|
||||
assertThat(txInfo.rollbackOn(new IllegalStateException())).isTrue();
|
||||
assertThat(txInfo.rollbackOn(new IOException())).isFalse();
|
||||
assertThat(txInfo.rollbackOn(new NullPointerException())).isFalse();
|
||||
|
||||
// not found return false
|
||||
assertThat(txInfo.rollbackOn(new MyRuntimeException("test"))).isFalse();
|
||||
assertThat(txInfo.rollbackOn(new RuntimeException())).isFalse();
|
||||
assertThat(txInfo.rollbackOn(new Throwable())).isFalse();
|
||||
}
|
||||
|
||||
private Set<RollbackRule> getRollbackRules() {
|
||||
Set<RollbackRule> sets = new LinkedHashSet<>();
|
||||
sets.add(new RollbackRule(IllegalStateException.class.getName()));
|
||||
sets.add(new RollbackRule(IllegalArgumentException.class));
|
||||
sets.add(new NoRollbackRule(IO_EXCEPTION_SHORT_NAME));
|
||||
sets.add(new NoRollbackRule(NullPointerException.class));
|
||||
return sets;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user