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.core.transaction; 017 018import org.apache.ibatis.logging.Log; 019import org.apache.ibatis.logging.LogFactory; 020 021import java.sql.Connection; 022import java.sql.SQLException; 023import java.util.Map; 024import java.util.UUID; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.function.Supplier; 027 028/** 029 * 事务管理器 030 */ 031public class TransactionalManager { 032 033 private TransactionalManager() { 034 } 035 036 private static final Log log = LogFactory.getLog(TransactionalManager.class); 037 038 //<xid : <dataSourceKey : connection>> 039 private static final ThreadLocal<Map<String, Map<String, Connection>>> CONNECTION_HOLDER 040 = ThreadLocal.withInitial(ConcurrentHashMap::new); 041 042 043 public static void hold(String xid, String ds, Connection connection) { 044 Map<String, Map<String, Connection>> holdMap = CONNECTION_HOLDER.get(); 045 Map<String, Connection> connMap = holdMap.get(xid); 046 if (connMap == null) { 047 connMap = new ConcurrentHashMap<>(); 048 holdMap.put(xid, connMap); 049 } 050 051 if (connMap.containsKey(ds)) { 052 return; 053 } 054 055 connMap.put(ds, connection); 056 } 057 058 059 public static <T> T exec(Supplier<T> supplier, Propagation propagation, boolean withResult) { 060 //上一级事务的id,支持事务嵌套 061 String currentXID = TransactionContext.getXID(); 062 try { 063 switch (propagation) { 064 //若存在当前事务,则加入当前事务,若不存在当前事务,则创建新的事务 065 case REQUIRED: 066 if (currentXID != null) { 067 return supplier.get(); 068 } else { 069 return execNewTransactional(supplier, withResult); 070 } 071 072 073 //若存在当前事务,则加入当前事务,若不存在当前事务,则已非事务的方式运行 074 case SUPPORTS: 075 return supplier.get(); 076 077 078 //若存在当前事务,则加入当前事务,若不存在当前事务,则已非事务的方式运行 079 case MANDATORY: 080 if (currentXID != null) { 081 return supplier.get(); 082 } else { 083 throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'"); 084 } 085 086 087 //始终以新事务的方式运行,若存在当前事务,则暂停(挂起)当前事务。 088 case REQUIRES_NEW: 089 return execNewTransactional(supplier, withResult); 090 091 092 //以非事务的方式运行,若存在当前事务,则暂停(挂起)当前事务。 093 case NOT_SUPPORTED: 094 if (currentXID != null) { 095 TransactionContext.release(); 096 } 097 return supplier.get(); 098 099 100 //以非事务的方式运行,若存在当前事务,则抛出异常。 101 case NEVER: 102 if (currentXID != null) { 103 throw new TransactionException("Existing transaction found for transaction marked with propagation 'never'"); 104 } 105 return supplier.get(); 106 107 108 //暂时不支持这种事务传递方式 109 //default 为 nested 方式 110 default: 111 throw new TransactionException("Transaction manager does not allow nested transactions"); 112 113 } 114 } finally { 115 //恢复上一级事务 116 if (currentXID != null) { 117 TransactionContext.holdXID(currentXID); 118 } 119 } 120 } 121 122 private static <T> T execNewTransactional(Supplier<T> supplier, boolean withResult) { 123 String xid = startTransactional(); 124 T result = null; 125 boolean isRollback = false; 126 try { 127 result = supplier.get(); 128 } catch (Throwable e) { 129 isRollback = true; 130 rollback(xid); 131 throw new TransactionException(e.getMessage(), e); 132 } finally { 133 if (!isRollback) { 134 if (!withResult) { 135 if (result instanceof Boolean && (Boolean) result) { 136 commit(xid); 137 } 138 //null or false 139 else { 140 rollback(xid); 141 } 142 } else { 143 commit(xid); 144 } 145 } 146 } 147 return result; 148 } 149 150 151 public static Connection getConnection(String xid, String ds) { 152 Map<String, Connection> connections = CONNECTION_HOLDER.get().get(xid); 153 return connections == null || connections.isEmpty() ? null : connections.get(ds); 154 } 155 156 157 public static String startTransactional() { 158 String xid = UUID.randomUUID().toString(); 159 TransactionContext.holdXID(xid); 160 return xid; 161 } 162 163 public static void commit(String xid) { 164 release(xid, true); 165 } 166 167 public static void rollback(String xid) { 168 release(xid, false); 169 } 170 171 private static void release(String xid, boolean commit) { 172 //先release,才能正常的进行 commit 或者 rollback. 173 TransactionContext.release(); 174 175 Exception exception = null; 176 Map<String, Map<String, Connection>> holdMap = CONNECTION_HOLDER.get(); 177 try { 178 if (holdMap.isEmpty()) { 179 return; 180 } 181 Map<String, Connection> connections = holdMap.get(xid); 182 if (connections != null) { 183 for (Connection conn : connections.values()) { 184 try { 185 if (commit) { 186 conn.commit(); 187 } else { 188 conn.rollback(); 189 } 190 } catch (SQLException e) { 191 exception = e; 192 } finally { 193 try { 194 conn.close(); 195 } catch (SQLException e) { 196 //ignore 197 } 198 } 199 } 200 } 201 } finally { 202 holdMap.remove(xid); 203 204 if (holdMap.isEmpty()) { 205 CONNECTION_HOLDER.remove(); 206 } 207 if (exception != null) { 208 log.error("TransactionalManager.release() is error. Cause: " + exception.getMessage(), exception); 209 } 210 } 211 } 212 213}