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.datasource; 017 018import com.mybatisflex.core.dialect.DbType; 019import com.mybatisflex.core.dialect.DbTypeUtil; 020import com.mybatisflex.core.transaction.TransactionContext; 021import com.mybatisflex.core.transaction.TransactionalManager; 022import com.mybatisflex.core.util.ArrayUtil; 023import com.mybatisflex.core.util.StringUtil; 024import org.apache.ibatis.logging.Log; 025import org.apache.ibatis.logging.LogFactory; 026 027import javax.sql.DataSource; 028import java.lang.reflect.InvocationHandler; 029import java.lang.reflect.Method; 030import java.lang.reflect.Proxy; 031import java.sql.Connection; 032import java.sql.SQLException; 033import java.util.HashMap; 034import java.util.Map; 035import java.util.Objects; 036 037/** 038 * @author michael 039 */ 040public class FlexDataSource extends AbstractDataSource { 041 042 private static final Log log = LogFactory.getLog(FlexDataSource.class); 043 044 private final Map<String, DataSource> dataSourceMap = new HashMap<>(); 045 private final Map<String, DbType> dbTypeHashMap = new HashMap<>(); 046 047 private final DbType defaultDbType; 048 private final String defaultDataSourceKey; 049 private final DataSource defaultDataSource; 050 051 public FlexDataSource(String dataSourceKey, DataSource dataSource) { 052 053 DataSourceManager.decryptDataSource(dataSource); 054 055 this.defaultDataSourceKey = dataSourceKey; 056 this.defaultDataSource = dataSource; 057 this.defaultDbType = DbTypeUtil.getDbType(dataSource); 058 059 dataSourceMap.put(dataSourceKey, dataSource); 060 dbTypeHashMap.put(dataSourceKey, defaultDbType); 061 } 062 063 public void addDataSource(String dataSourceKey, DataSource dataSource) { 064 DataSourceManager.decryptDataSource(dataSource); 065 dataSourceMap.put(dataSourceKey, dataSource); 066 dbTypeHashMap.put(dataSourceKey, DbTypeUtil.getDbType(dataSource)); 067 } 068 069 public void removeDatasource(String dataSourceKey) { 070 dataSourceMap.remove(dataSourceKey); 071 dbTypeHashMap.remove(dataSourceKey); 072 } 073 074 public Map<String, DataSource> getDataSourceMap() { 075 return dataSourceMap; 076 } 077 078 public Map<String, DbType> getDbTypeHashMap() { 079 return dbTypeHashMap; 080 } 081 082 public String getDefaultDataSourceKey() { 083 return defaultDataSourceKey; 084 } 085 086 public DataSource getDefaultDataSource() { 087 return defaultDataSource; 088 } 089 090 public DbType getDefaultDbType() { 091 return defaultDbType; 092 } 093 094 public DbType getDbType(String dataSourceKey) { 095 return dbTypeHashMap.get(dataSourceKey); 096 } 097 098 099 @Override 100 public Connection getConnection() throws SQLException { 101 String xid = TransactionContext.getXID(); 102 if (StringUtil.isNotBlank(xid)) { 103 String dataSourceKey = DataSourceKey.get(); 104 if (StringUtil.isBlank(dataSourceKey)) { 105 dataSourceKey = defaultDataSourceKey; 106 } 107 108 Connection connection = TransactionalManager.getConnection(xid, dataSourceKey); 109 if (connection == null) { 110 connection = proxy(getDataSource().getConnection(), xid); 111 TransactionalManager.hold(xid, dataSourceKey, connection); 112 } 113 return connection; 114 } else { 115 return getDataSource().getConnection(); 116 } 117 } 118 119 120 @Override 121 public Connection getConnection(String username, String password) throws SQLException { 122 String xid = TransactionContext.getXID(); 123 if (StringUtil.isNotBlank(xid)) { 124 String dataSourceKey = DataSourceKey.get(); 125 if (StringUtil.isBlank(dataSourceKey)) { 126 dataSourceKey = defaultDataSourceKey; 127 } 128 Connection connection = TransactionalManager.getConnection(xid, dataSourceKey); 129 if (connection == null) { 130 connection = proxy(getDataSource().getConnection(username, password), xid); 131 TransactionalManager.hold(xid, dataSourceKey, connection); 132 } 133 return connection; 134 } else { 135 return getDataSource().getConnection(username, password); 136 } 137 } 138 139 static void closeAutoCommit(Connection connection) { 140 try { 141 connection.setAutoCommit(false); 142 } catch (SQLException e) { 143 if (log.isDebugEnabled()) { 144 log.debug("Error set autoCommit to false. Cause: " + e); 145 } 146 } 147 } 148 149 static void resetAutoCommit(Connection connection) { 150 try { 151 if (!connection.getAutoCommit()) { 152 connection.setAutoCommit(true); 153 } 154 } catch (SQLException e) { 155 if (log.isDebugEnabled()) { 156 log.debug("Error resetting autoCommit to true before closing the connection. " + 157 "Cause: " + e); 158 } 159 } 160 } 161 162 163 public Connection proxy(Connection connection, String xid) { 164 return (Connection) Proxy.newProxyInstance(FlexDataSource.class.getClassLoader() 165 , new Class[]{Connection.class} 166 , new ConnectionHandler(connection, xid)); 167 } 168 169 /** 170 * 方便用于 {@link DbTypeUtil#getDbType(DataSource)} 171 */ 172 public String getUrl() { 173 return DbTypeUtil.getJdbcUrl(defaultDataSource); 174 } 175 176 177 @Override 178 @SuppressWarnings("unchecked") 179 public <T> T unwrap(Class<T> iface) throws SQLException { 180 if (iface.isInstance(this)) { 181 return (T) this; 182 } 183 return getDataSource().unwrap(iface); 184 } 185 186 @Override 187 public boolean isWrapperFor(Class<?> iface) throws SQLException { 188 return (iface.isInstance(this) || getDataSource().isWrapperFor(iface)); 189 } 190 191 192 private DataSource getDataSource() { 193 DataSource dataSource = defaultDataSource; 194 if (dataSourceMap.size() > 1) { 195 String dataSourceKey = DataSourceKey.get(); 196 if (StringUtil.isNotBlank(dataSourceKey)) { 197 dataSource = dataSourceMap.get(dataSourceKey); 198 if (dataSource == null) { 199 throw new IllegalStateException("Cannot get target DataSource for dataSourceKey [" + dataSourceKey + "]"); 200 } 201 } 202 } 203 return dataSource; 204 } 205 206 private static class ConnectionHandler implements InvocationHandler { 207 208 private static final String[] proxyMethods = new String[]{"commit", "rollback", "close", "setAutoCommit"}; 209 private final Connection original; 210 private final String xid; 211 212 public ConnectionHandler(Connection original, String xid) { 213 214 closeAutoCommit(original); 215 216 this.original = original; 217 this.xid = xid; 218 } 219 220 @Override 221 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 222 if (ArrayUtil.contains(proxyMethods, method.getName()) 223 && isTransactional()) { 224 //do nothing 225 return null; 226 } 227 228 //setAutoCommit: true 229 if ("close".equalsIgnoreCase(method.getName())) { 230 resetAutoCommit(original); 231 } 232 233 return method.invoke(original, args); 234 } 235 236 private boolean isTransactional() { 237 return Objects.equals(xid, TransactionContext.getXID()); 238 } 239 240 } 241 242 243}