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