001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.spring;
017
018import org.springframework.transaction.TransactionDefinition;
019import org.springframework.transaction.TransactionTimedOutException;
020
021import java.util.Date;
022
023/**
024 * 事务定义管理器 用于更完整的实现Spring事务
025 * 仅支持传统事务不支持R2DBC事务
026 *
027 * @author Aliothmoon
028 * @author Michael
029 * @since 2024/10/25
030 */
031public final class TimeoutHolder {
032    private static final ThreadLocal<Long> TRANSACTION_DEADLINE = new ThreadLocal<>();
033
034    public static void hold(TransactionDefinition definition) {
035        if (definition == null) {
036            return;
037        }
038        int timeout = definition.getTimeout();
039        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
040            Long deadline = System.currentTimeMillis() + timeout * 1000L;
041            TRANSACTION_DEADLINE.set(deadline);
042        }
043    }
044
045
046    /**
047     * 清除事务上下文
048     */
049    public static void clear() {
050        TRANSACTION_DEADLINE.remove();
051    }
052
053    /**
054     * 获取当前事务可用TTL
055     *
056     * @return int
057     */
058    public static Integer getTimeToLiveInSeconds() {
059        Long deadline = TRANSACTION_DEADLINE.get();
060        if (deadline == null) {
061            return null;
062        }
063        double diff = ((double) getTimeToLiveInMillis(deadline)) / 1000;
064        int secs = (int) Math.ceil(diff);
065        checkTransactionTimeout(secs <= 0, deadline);
066        return secs;
067    }
068
069
070    private static void checkTransactionTimeout(boolean deadlineReached, Long deadline) throws TransactionTimedOutException {
071        if (deadlineReached) {
072            throw new TransactionTimedOutException("Transaction timed out: deadline was " + new Date(deadline));
073        }
074    }
075
076
077    private static long getTimeToLiveInMillis(Long deadline) throws TransactionTimedOutException {
078        if (deadline == null) {
079            throw new IllegalStateException("No timeout specified for this resource holder");
080        }
081        long timeToLive = deadline - System.currentTimeMillis();
082        checkTransactionTimeout(timeToLive <= 0, deadline);
083        return timeToLive;
084    }
085
086
087}