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