package com.taobao.tddl.group.jdbc; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.ResultSet; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import com.taobao.tddl.common.jdbc.TExceptionUtils; import com.taobao.tddl.group.config.GroupIndex; import com.taobao.tddl.group.dbselector.DBSelector; import com.taobao.tddl.group.dbselector.DBSelector.AbstractDataSourceTryer; import com.taobao.tddl.group.dbselector.DBSelector.DataSourceTryer; import com.taobao.tddl.group.utils.GroupHintParser; import com.taobao.tddl.monitor.unit.UnitDeployProtect; import com.taobao.tddl.common.utils.logger.Logger; import com.taobao.tddl.common.utils.logger.LoggerFactory; /** * 相关的JDBC规范: 1. * Connection关闭,在其上打开的statement自动关闭。这就要求Connection持有其上打开的所有statement的引用 2. * 重试的场景1:在第一个statement上执行查询,路由到db1成功。再创建一个statement查询在db1上失败: stmt1 = * TGroupConnection.createStatement rs1 = stmt1.executeQuery --create connection * on db1 and execute success stmt2 = conn..createStatement rs2 = * stmt2..executeQuery --db1 failed then... 这时如果重试到db2库,db1的connection要不要关? * a:如果关,其上的实际stmt和rs就都会关掉。这样db2成功后 * 用户会看不到exception,对用户来说,stms1和rs1都是正常的。但实际上已经是坏掉的了。 b: * 如果不关,也就是TGroupConnection持有多个baseConnection,。。。 由以上场景的考虑,提炼出一个原则: 重试的原则: * 一个TGroupConnection中,只在第一次与真正与数据库交互时,也就是不得不返回db结果给用户时,才在DBGroup上进行重试。 * 一旦在某个库上重试成功,后续在这个TGroupConnection上执行的所有操作,都只到这个库上,不再重试,出错直接抛出异常。 * 第一次建立真正连接的重试过程中,baseConnection有可能会发生变化被替换。一旦重试成功,baseConnection则保持不再改变。 * 这样可以简化很多事情,但同时不会对功能造成本质影响。同时避免了对状态处理不当,可能会给用户造成的诡异现象。 * * @author linxuan * @author yangzhu */ public class TGroupConnection implements Connection { private static final Logger log = LoggerFactory.getLogger(TGroupConnection.class); private TGroupDataSource tGroupDataSource; // 虽然DataSource.getConnection(String username, String password)不常用, // 但为了尽量遵循jdbc规范,还是保留好。 private String username; private String password; public TGroupConnection(TGroupDataSource tGroupDataSource){ this.tGroupDataSource = tGroupDataSource; } public TGroupConnection(TGroupDataSource tGroupDataSource, String username, String password){ this(tGroupDataSource); this.username = username; this.password = password; } /* * ======================================================================== * 下层connection的持有,getter/setter包权限 * ====================================================================== */ private Connection rBaseConnection; private Connection wBaseConnection; // private String rBaseDsKey; // rBaseConnection对应的数据源key // private String wBaseDsKey; // wBaseConnection对应的数据源key // private int rBaseDataSourceIndex = -2; // rBaseConnection对应的数据源Index // private int wBaseDataSourceIndex = -2; // wBaseConnection对应的数据源Index private DataSourceWrapper rBaseDsWrapper; private DataSourceWrapper wBaseDsWrapper; private Set<TGroupStatement> openedStatements = new HashSet<TGroupStatement>(2); // TODO: 以后让这个值真正的起作用 private int transactionIsolation = -1; public static final GroupIndex DEFAULT_GROUPINDEX = new GroupIndex(DBSelector.NOT_EXIST_USER_SPECIFIED_INDEX, false); /** * 获取事务中的上一个操作的链接 */ Connection getBaseConnection(String sql, boolean isRead) throws SQLException { GroupIndex dataSourceIndex = DEFAULT_GROUPINDEX; if (sql == null) { // 如果当前的数据源索引与上一次的数据源索引不一样,说明上一次缓存的Connection已经无用了,需要关闭后重建。 dataSourceIndex = ThreadLocalDataSourceIndex.getIndex(); } else { dataSourceIndex = GroupHintParser.convertHint2Index(sql); if (dataSourceIndex == null) { dataSourceIndex = ThreadLocalDataSourceIndex.getIndex(); } } // 代表出现自定义index请求 if (dataSourceIndex.index != DBSelector.NOT_EXIST_USER_SPECIFIED_INDEX) { if (log.isDebugEnabled()) { log.debug("dataSourceIndex=" + dataSourceIndex); } // 在事务状态下,设置不同的数据源索引会导致异常。 if (!isAutoCommit) { if (wBaseDsWrapper != null && !wBaseDsWrapper.isMatchDataSourceIndex(dataSourceIndex.index)) { throw new SQLException("Transaction in another dataSourceIndex: " + dataSourceIndex); } } if (isRead) { if (rBaseDsWrapper != null && !rBaseDsWrapper.isMatchDataSourceIndex(dataSourceIndex.index)) { closeReadConnection(); } } else { if (wBaseDsWrapper != null && !wBaseDsWrapper.isMatchDataSourceIndex(dataSourceIndex.index)) { closeWriteConnection(); } } } // 为了保证事务正确关闭,在事务状态下只会取回写连接 if (isRead && isAutoCommit) { // 只要有写连接,并且对应的库可读,则复用。否则返回读连接 return wBaseConnection != null && wBaseDsWrapper.hasReadWeight() ? wBaseConnection : rBaseConnection; // 先写后读,重用写连接读后,rBaseConnection仍然是null } else { if (wBaseConnection != null) { this.tGroupDataSource.setWriteTarget(wBaseDsWrapper); return wBaseConnection; } else if (rBaseConnection != null && rBaseDsWrapper.hasWriteWeight()) { // 在写连接null的情况下,如果读连接已经建立,且对应的库可写,则复用 wBaseConnection = rBaseConnection; // wBaseConnection赋值,以确保事务能够正确提交回滚 // 在写连接上设置事务 if (wBaseConnection.getAutoCommit() != isAutoCommit) { wBaseConnection.setAutoCommit(isAutoCommit); } // wBaseDsKey = rBaseDsKey; wBaseDsWrapper = rBaseDsWrapper; this.tGroupDataSource.setWriteTarget(wBaseDsWrapper); return wBaseConnection; } else { return null; } } } /** * 从实际的DataSource获得一个下层(有可能不是真实的)Connection * 包权限:此方法只在TGroupStatement、TGroupPreparedStatement中使用 */ Connection createNewConnection(DataSourceWrapper dsw, boolean isRead) throws SQLException { // 这个方法只发生在第一次建立读/写连接的时候,以后都是复用了 Connection conn; if (username != null) { conn = dsw.getConnection(username, password); } else { conn = dsw.getConnection(); } // 为了保证事务正确关闭,在事务状态下只设置写连接 setBaseConnection(conn, dsw, isRead && isAutoCommit); // 只在写连接上调用 setAutoCommit, 与 TGroupConnection#setAutoCommit 的代码保持一致 if (!isRead || !isAutoCommit) { conn.setAutoCommit(isAutoCommit); // 新建连接的AutoCommit要与当前isAutoCommit的状态同步 } return conn; } private void setBaseConnection(Connection baseConnection, DataSourceWrapper dsw, boolean isRead) { if (baseConnection == null) { log.warn("setBaseConnection to null !!"); } if (isRead) { closeReadConnection(); } else { closeWriteConnection(); } if (isRead) { rBaseConnection = baseConnection; // this.rBaseDsKey = dsw.getDataSourceKey(); // this.rBaseDataSourceIndex = dsw.getDataSourceIndex(); this.rBaseDsWrapper = dsw; } else { wBaseConnection = baseConnection; // this.wBaseDsKey = dsw.getDataSourceKey(); // this.wBaseDataSourceIndex = dsw.getDataSourceIndex(); this.wBaseDsWrapper = dsw; this.tGroupDataSource.setWriteTarget(dsw); } } private void closeReadConnection() { // r|wBaseConnection可能指向同一个对象,如果另一个引用在用,就不去关闭 if (rBaseConnection != null && rBaseConnection != wBaseConnection) { try { rBaseConnection.close(); // 旧的baseConnection要关闭 } catch (SQLException e) { log.error("close rBaseConnection failed.", e); } rBaseDsWrapper = null; rBaseConnection = null; } } private void closeWriteConnection() { // r|wBaseConnection可能指向同一个对象,如果另一个引用在用,就不去关闭 if (wBaseConnection != null && rBaseConnection != wBaseConnection) { try { wBaseConnection.close(); // 旧的baseConnection要关闭 } catch (SQLException e) { log.error("close wBaseConnection failed.", e); } wBaseDsWrapper = null; wBaseConnection = null; } } void removeOpenedStatements(Statement statement) { if (!openedStatements.remove(statement)) { log.warn("current statmenet :" + statement + " doesn't exist!"); } } /* * ======================================================================== * 关闭逻辑 * ====================================================================== */ private boolean closed; private void checkClosed() throws SQLException { if (closed) { throw new SQLException("No operations allowed after connection closed."); } } public boolean isClosed() throws SQLException { return closed; } @SuppressWarnings("unchecked") public void close() throws SQLException { if (closed) { return; } closed = true; List<SQLException> exceptions = new LinkedList<SQLException>(); try { // 关闭statement for (TGroupStatement stmt : openedStatements) { try { stmt.close(false); } catch (SQLException e) { exceptions.add(e); } } try { if (rBaseConnection != null && !rBaseConnection.isClosed()) { rBaseConnection.close(); } } catch (SQLException e) { exceptions.add(e); } try { if (wBaseConnection != null && !wBaseConnection.isClosed()) { wBaseConnection.close(); } } catch (SQLException e) { exceptions.add(e); } } finally { openedStatements.clear(); // openedStatements = null; //逻辑完整性 rBaseConnection = null; wBaseConnection = null; ThreadLocalDataSourceIndex.clearIndex(); UnitDeployProtect.clearUnitValidThreadLocal(); } TExceptionUtils.throwSQLException(exceptions, "close tconnection", Collections.EMPTY_LIST); } /* * ======================================================================== * 创建Statement逻辑 * ====================================================================== */ public TGroupStatement createStatement() throws SQLException { checkClosed(); TGroupStatement stmt = new TGroupStatement(tGroupDataSource, this); openedStatements.add(stmt); return stmt; } public TGroupStatement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { TGroupStatement stmt = createStatement(); stmt.setResultSetType(resultSetType); stmt.setResultSetConcurrency(resultSetConcurrency); return stmt; } public TGroupStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { TGroupStatement stmt = createStatement(resultSetType, resultSetConcurrency); stmt.setResultSetHoldability(resultSetHoldability); return stmt; } /* * ======================================================================== * 创建PreparedStatement逻辑 * ====================================================================== */ public TGroupPreparedStatement prepareStatement(String sql) throws SQLException { checkClosed(); TGroupPreparedStatement stmt = new TGroupPreparedStatement(tGroupDataSource, this, sql); openedStatements.add(stmt); return stmt; } public TGroupPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { TGroupPreparedStatement stmt = prepareStatement(sql); stmt.setResultSetType(resultSetType); stmt.setResultSetConcurrency(resultSetConcurrency); return stmt; } public TGroupPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { TGroupPreparedStatement stmt = prepareStatement(sql, resultSetType, resultSetConcurrency); stmt.setResultSetHoldability(resultSetHoldability); return stmt; } public TGroupPreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { TGroupPreparedStatement stmt = prepareStatement(sql); stmt.setAutoGeneratedKeys(autoGeneratedKeys); return stmt; } public TGroupPreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { TGroupPreparedStatement stmt = prepareStatement(sql); stmt.setColumnIndexes(columnIndexes); return stmt; } public TGroupPreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { TGroupPreparedStatement stmt = prepareStatement(sql); stmt.setColumnNames(columnNames); return stmt; } /* * ======================================================================== * 创建CallableStatement逻辑。存储过程CallableStatement支持 * ====================================================================== */ private DataSourceTryer<CallableStatement> getCallableStatementTryer = new AbstractDataSourceTryer<CallableStatement>() { public CallableStatement tryOnDataSource(DataSourceWrapper dsw, Object... args) throws SQLException { String sql = (String) args[0]; int resultSetType = (Integer) args[1]; int resultSetConcurrency = (Integer) args[2]; int resultSetHoldability = (Integer) args[3]; Connection conn = TGroupConnection.this.createNewConnection(dsw, false); return getCallableStatement(conn, sql, resultSetType, resultSetConcurrency, resultSetHoldability); } }; private CallableStatement getCallableStatement(Connection conn, String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (resultSetType == Integer.MIN_VALUE) { return conn.prepareCall(sql); } else if (resultSetHoldability == Integer.MIN_VALUE) { return conn.prepareCall(sql, resultSetType, resultSetConcurrency); } else { return conn.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } } public TGroupCallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkClosed(); CallableStatement target; Connection conn = this.getBaseConnection(sql, false); // 存储过程默认走写库 if (conn != null) { sql = GroupHintParser.removeTddlGroupHint(sql); target = getCallableStatement(conn, sql, resultSetType, resultSetConcurrency, resultSetHoldability); } else { // hint优先 GroupIndex dataSourceIndex = GroupHintParser.convertHint2Index(sql); sql = GroupHintParser.removeTddlGroupHint(sql); if (dataSourceIndex == null) { dataSourceIndex = ThreadLocalDataSourceIndex.getIndex(); } target = tGroupDataSource.getDBSelector(false).tryExecute(null, getCallableStatementTryer, this.tGroupDataSource.getRetryingTimes(), sql, resultSetType, resultSetConcurrency, resultSetHoldability, dataSourceIndex); } TGroupCallableStatement stmt = new TGroupCallableStatement(tGroupDataSource, this, target, sql); if (resultSetType != Integer.MIN_VALUE) { stmt.setResultSetType(resultSetType); stmt.setResultSetConcurrency(resultSetConcurrency); } if (resultSetHoldability != Integer.MIN_VALUE) { stmt.setResultSetHoldability(resultSetHoldability); } openedStatements.add(stmt); return stmt; } public TGroupCallableStatement prepareCall(String sql) throws SQLException { return prepareCall(sql, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); } public TGroupCallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareCall(sql, resultSetType, resultSetConcurrency, Integer.MIN_VALUE); } /* * ======================================================================== * JDBC事务相关的autoCommit设置、commit/rollback、TransactionIsolation等 * ====================================================================== */ private boolean isAutoCommit = true; // jdbc规范,新连接为true public void setAutoCommit(boolean autoCommit0) throws SQLException { checkClosed(); if (this.isAutoCommit == autoCommit0) { // 先排除两种最常见的状态,true==true 和false == false: 什么也不做 return; } this.isAutoCommit = autoCommit0; /* * /////////////////////////////////////只读情况忽略事务 if * (this.rBaseConnection != null) { * this.rBaseConnection.setAutoCommit(autoCommit0); } */ if (this.wBaseConnection != null) { this.wBaseConnection.setAutoCommit(autoCommit0); } } public boolean getAutoCommit() throws SQLException { checkClosed(); return isAutoCommit; } public void commit() throws SQLException { checkClosed(); if (isAutoCommit) { return; } /* * /////////////////////////////////////只读情况忽略事务 if (rBaseConnection != * null) { try { rBaseConnection.commit(); } catch (SQLException e) { * log.error("Commit failed on " + this.rBaseDsKey + ":" + * e.getMessage()); throw e; } } */ if (wBaseConnection != null) { try { wBaseConnection.commit(); } catch (SQLException e) { log.error("Commit failed on " + this.wBaseDsWrapper.getDataSourceKey() + ":" + e.getMessage()); throw e; } } } public void rollback() throws SQLException { checkClosed(); if (isAutoCommit) { return; } /* * /////////////////////////////////////只读情况忽略事务 if (rBaseConnection != * null) { try { rBaseConnection.rollback(); } catch (SQLException e) { * log.error("Rollback failed on " + this.rBaseDsKey + ":" + * e.getMessage()); throw e; } } */ if (wBaseConnection != null) { try { wBaseConnection.rollback(); } catch (SQLException e) { log.error("Rollback failed on " + this.wBaseDsWrapper.getDataSourceKey() + ":" + e.getMessage()); throw e; } } } public int getTransactionIsolation() throws SQLException { checkClosed(); return transactionIsolation; } public void setTransactionIsolation(int transactionIsolation) throws SQLException { checkClosed(); this.transactionIsolation = transactionIsolation; } /* * ======================================================================== * SQLWarning 和 DatabaseMetaData * ====================================================================== */ public SQLWarning getWarnings() throws SQLException { checkClosed(); if (rBaseConnection != null) { return rBaseConnection.getWarnings(); } else if (wBaseConnection != null) { return wBaseConnection.getWarnings(); } else { return null; } } public void clearWarnings() throws SQLException { checkClosed(); if (rBaseConnection != null) { rBaseConnection.clearWarnings(); } if (wBaseConnection != null) { wBaseConnection.clearWarnings(); } } public DatabaseMetaData getMetaData() throws SQLException { checkClosed(); if (rBaseConnection != null) { return rBaseConnection.getMetaData(); } else if (wBaseConnection != null) { return wBaseConnection.getMetaData(); } else { return new TGroupDatabaseMetaData(this, tGroupDataSource); } } public void cancel() throws SQLException { for (TGroupStatement stat : openedStatements) { stat.cancel(); } } /* * ======================================================================== * 后面是未实现的方法 * ====================================================================== */ public void rollback(Savepoint savepoint) throws SQLException { throw new UnsupportedOperationException("rollback"); } public Savepoint setSavepoint() throws SQLException { throw new UnsupportedOperationException("setSavepoint"); } public Savepoint setSavepoint(String name) throws SQLException { throw new UnsupportedOperationException("setSavepoint"); } public void releaseSavepoint(Savepoint savepoint) throws SQLException { throw new UnsupportedOperationException("releaseSavepoint"); } public String getCatalog() throws SQLException { throw new UnsupportedOperationException("getCatalog"); } public void setCatalog(String catalog) throws SQLException { throw new UnsupportedOperationException("setCatalog"); } public int getHoldability() throws SQLException { return ResultSet.CLOSE_CURSORS_AT_COMMIT; } public void setHoldability(int holdability) throws SQLException { /* * 如果你看到这里,那么恭喜,哈哈 mysql默认在5.x的jdbc driver里面也没有实现holdability 。 * 所以默认都是.CLOSE_CURSORS_AT_COMMIT 为了简化起见,我们也就只实现close这种 */ // mysql 5.x的jdbc driver只支持ResultSet.HOLD_CURSORS_OVER_COMMIT throw new UnsupportedOperationException("setHoldability"); } public Map<String, Class<?>> getTypeMap() throws SQLException { throw new UnsupportedOperationException("getTypeMap"); } public void setTypeMap(Map<String, Class<?>> map) throws SQLException { throw new UnsupportedOperationException("setTypeMap"); } public String nativeSQL(String sql) throws SQLException { throw new UnsupportedOperationException("nativeSQL"); } /** * 保持可读可写 * * @author junyu */ public boolean isReadOnly() throws SQLException { return false; } /** * 不做任何事情 * * @author junyu */ public void setReadOnly(boolean readOnly) throws SQLException { } public boolean isWrapperFor(Class<?> iface) throws SQLException { return this.getClass().isAssignableFrom(iface); } @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { try { return (T) this; } catch (Exception e) { throw new SQLException(e); } } public Clob createClob() throws SQLException { throw new SQLException("not support exception"); } public Blob createBlob() throws SQLException { throw new SQLException("not support exception"); } public NClob createNClob() throws SQLException { throw new SQLException("not support exception"); } public SQLXML createSQLXML() throws SQLException { throw new SQLException("not support exception"); } public boolean isValid(int timeout) throws SQLException { throw new SQLException("not support exception"); } public void setClientInfo(String name, String value) throws SQLClientInfoException { throw new RuntimeException("not support exception"); } public void setClientInfo(Properties properties) throws SQLClientInfoException { throw new RuntimeException("not support exception"); } public String getClientInfo(String name) throws SQLException { throw new SQLException("not support exception"); } public Properties getClientInfo() throws SQLException { throw new SQLException("not support exception"); } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { throw new SQLException("not support exception"); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { throw new SQLException("not support exception"); } }