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