package jef.database.innerpool; 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.PreparedStatement; 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.Executor; import javax.sql.DataSource; import jef.common.SimpleMap; import jef.database.DbUtils; import jef.database.exception.InconsistentCommitException; import jef.tools.StringUtils; /** * 最简单的路由Connection实现 * * @author jiyi * */ final class RoutingConnection implements ReentrantConnection { /** * 缺省数据源名称 */ private String defaultKey = null; /** * 自动提交 */ private boolean autoCommit = true; /** * 只读标记 */ private boolean readOnly; /** * 事务隔离级别 */ private int isolation = -1; /** * 当前的Savapoint序号 */ private int savePointId = 0; /** * 连接池 */ private IRoutingConnectionPool parent; /** * 目前已经用到的连接 */ private final Map<String, Connection> connections = new HashMap<String, Connection>(4); /** * 目前使用的连接数据源名称 */ private String key; /** * 即便提交过程中出现错误,也持续将剩余的连接都提交完。 如果设置为false,那么任意连接的提交错误将终止提交过程。剩余的连接等待回滚。 * */ private boolean continueCommitEvenException; /** * 构造 * * @param parent */ public RoutingConnection(IRoutingConnectionPool parent, boolean isCommitEvenExeption) { this.parent = parent; this.continueCommitEvenException = isCommitEvenExeption; } /** * 归还到父池中 */ public void closePhysical() { savePointId = 0; if (parent != null) { IRoutingConnectionPool parent = this.parent; for (Entry<String, Connection> entry : connections.entrySet()) { String key = entry.getKey(); parent.putback(key, entry.getValue()); } connections.clear();// 全部归还 } } public final void ensureOpen() throws SQLException { if (parent == null) { throw new SQLException("Current Routing Connection is closed already!"); } } // 一般标记为事务的开始 public void setAutoCommit(boolean flag) throws SQLException { if (autoCommit == flag) { return; } this.autoCommit = flag; for (Connection conn : connections.values()) { if (conn.getAutoCommit() != flag) { conn.setAutoCommit(flag); } } } // public void setReadOnly(boolean flag) throws SQLException { if (readOnly == flag) { return; } readOnly = flag; for (Connection conn : connections.values()) { if (conn.isReadOnly() != flag) { conn.setReadOnly(flag); } } } public boolean isContinueCommitEvenException() { return continueCommitEvenException; } public void setContinueCommitEvenException(boolean continueCommitEvenException) { this.continueCommitEvenException = continueCommitEvenException; } public void setKey(String key) { if (key != null && key.length() == 0) { key = null; } this.key = key; } /** * 当有多个连接需要提交时,对每个连接进行提交。 无论过程中是否有异常,都记录提交过程中发生的异常信息,并继续提交后续的连接。 */ public void commit() throws SQLException { //如果没有需要提交的连接,直接返回 if (connections.isEmpty() || autoCommit) return; ensureOpen(); //记录提交程度的数据源名称 List<String> succeed = new ArrayList<String>(); // SimpleMap<String, SQLException> errors = new SimpleMap<String, SQLException>(); for (Map.Entry<String, Connection> entry : connections.entrySet()) { try { entry.getValue().commit(); succeed.add(entry.getKey()); } catch (SQLException e) { errors.add(entry.getKey(), e); if (!continueCommitEvenException){ break; } } catch (RuntimeException e) { errors.add(entry.getKey(), new SQLException(e)); if (!continueCommitEvenException){ break; } } } if (errors.isEmpty()) { return; } if (succeed.isEmpty()) {// 第一个连接提交就出错,事务依然保持一致 throw DbUtils.wrapExceptions(errors.values()); } else { // 第success.size()+1个连接提交出错 throw new InconsistentCommitException(succeed,errors,connections.size()); } } /** * 当有多个连接需要回滚时,对每个连接进行回滚。 无论过程中是否有异常,都记录回滚过程中发生的异常信息,并继续回滚后续的连接。 */ public void rollback() throws SQLException { if (connections.isEmpty() || autoCommit) return; ensureOpen(); List<String> successed = new ArrayList<String>(); SimpleMap<String, SQLException> errors = new SimpleMap<String, SQLException>(); for (Map.Entry<String, Connection> entry : connections.entrySet()) { try { entry.getValue().rollback(); successed.add(entry.getKey()); } catch (SQLException e) { errors.add(entry.getKey(), e); // 即时前面的连接出错,后面的依然要回滚 } catch (RuntimeException e) { errors.add(entry.getKey(), new SQLException(e)); } } if (errors.isEmpty()) { return; } if (successed.isEmpty()) {// 第一个连接提交就出错,事务依然保持一致 throw DbUtils.wrapExceptions(errors.values()); } else { // 第success.size()+1个连接提交出错 Entry<String, SQLException> error = errors.getEntries().get(0); String message = StringUtils.concat("Error while rollback data to datasource [", error.getKey(), "], and this is the ", String.valueOf(successed.size() + 1), "th rollback of ", String.valueOf(connections.size()), ", there must be some data consistency problem, please check it."); SQLException ex = DbUtils.wrapExceptions(errors.values()); SQLException e = new SQLException(message, ex); e.setNextException(ex); throw e; } } private Connection getConnection() throws SQLException { if (key == null) { if (defaultKey == null) { Entry<String, DataSource> ds = parent.getRoutingDataSource().getDefaultDatasource(); defaultKey = ds.getKey(); } return getConnectionOfKey(defaultKey); } else { return getConnectionOfKey(key); } } /** * 实现 * * @param key * @return * @throws SQLException */ private Connection getConnectionOfKey(String key) throws SQLException { Connection conn = connections.get(key); if (conn == null) { do { conn = parent.getCachedConnection(key); if (conn.isClosed()) { DbUtils.closeConnection(conn); conn = null; } } while (conn == null); if (conn.isReadOnly() != readOnly) { conn.setReadOnly(readOnly); } if (isolation >= 0) { conn.setTransactionIsolation(isolation); } if (conn.getAutoCommit() != autoCommit) { conn.setAutoCommit(autoCommit); } connections.put(key, conn); } return conn; } public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return getConnection().createStatement(resultSetType, resultSetConcurrency); } public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return getConnection().prepareStatement(sql, autoGeneratedKeys); } public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return getConnection().prepareStatement(sql, resultSetType, resultSetConcurrency); } public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return getConnection().prepareStatement(sql, columnNames); } public Statement createStatement() throws SQLException { return getConnection().createStatement(); } public PreparedStatement prepareStatement(String sql) throws SQLException { return getConnection().prepareStatement(sql); } public CallableStatement prepareCall(String sql) throws SQLException { return getConnection().prepareCall(sql); } public DatabaseMetaData getMetaData() throws SQLException { return getConnection().getMetaData(); } private volatile Object used; private volatile int count; public void setUsedByObject(Object user) { this.used = user; count++; } public Object popUsedByObject() { if (--count > 0) { // System.out.println("不是真正的归还"+used+"还有"+count+"次使用."); return null; } else { Object o = used; used = null; return o; } } public void addUsedByObject() { count++; } // // public boolean isUsed() { // return count > 0; // } public void notifyDisconnect() { } public int getTransactionIsolation() throws SQLException { return isolation; } public void setTransactionIsolation(int level) throws SQLException { if (level < 0 || level == isolation) { return; } this.isolation = level; for (Connection conn : connections.values()) { if (conn.getTransactionIsolation() != level) { conn.setTransactionIsolation(level); } } } /** * 归还到父池中 */ public void close() { parent.offer(this); } public <T> T unwrap(Class<T> iface) throws SQLException { return getConnection().unwrap(iface); } public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } public String nativeSQL(String sql) throws SQLException { return getConnection().nativeSQL(sql); } public boolean getAutoCommit() throws SQLException { return autoCommit; } public boolean isClosed() throws SQLException { return parent == null; } public boolean isReadOnly() throws SQLException { return readOnly; } public void setCatalog(String catalog) throws SQLException { throw new UnsupportedOperationException(); } public String getCatalog() throws SQLException { return null; } public SQLWarning getWarnings() throws SQLException { return null; } public void clearWarnings() throws SQLException { } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return getConnection().prepareCall(sql, resultSetType, resultSetConcurrency); } public Map<String, Class<?>> getTypeMap() throws SQLException { return getConnection().getTypeMap(); } public void setTypeMap(Map<String, Class<?>> map) throws SQLException { throw new UnsupportedOperationException(); } public void setHoldability(int holdability) throws SQLException { throw new UnsupportedOperationException(); } public int getHoldability() throws SQLException { return getConnection().getHoldability(); } public Savepoint setSavepoint() throws SQLException { ensureOpen(); List<SQLException> errors = new ArrayList<SQLException>(); Savepoints sp = new Savepoints(++savePointId); for (Connection conn : connections.values()) { try { Savepoint s = conn.setSavepoint(); sp.add(conn, s); } catch (SQLException e) { errors.add(e); } } if (!errors.isEmpty()) { throw DbUtils.wrapExceptions(errors); } return sp; } public Savepoint setSavepoint(String name) throws SQLException { if (name == null) { return setSavepoint(); } ensureOpen(); List<SQLException> errors = new ArrayList<SQLException>(); Savepoints sp = new Savepoints(name); for (Connection conn : connections.values()) { try { Savepoint s = conn.setSavepoint(); sp.add(conn, s); } catch (SQLException e) { errors.add(e); } } if (!errors.isEmpty()) { throw DbUtils.wrapExceptions(errors); } return sp; } public void rollback(Savepoint savepoint) throws SQLException { if (savepoint instanceof Savepoints) { ((Savepoints) savepoint).doRollback(); } else { throw new SQLException(savepoint + " is not a valid savepoint!"); } } public void releaseSavepoint(Savepoint savepoint) throws SQLException { if (savepoint instanceof Savepoints) { ((Savepoints) savepoint).doRelease(); } else { throw new SQLException(savepoint + " is not a valid savepoint!"); } } public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return getConnection().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); } public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return getConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return getConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return getConnection().prepareStatement(sql, columnIndexes); } public Clob createClob() throws SQLException { return getConnection().createClob(); } public Blob createBlob() throws SQLException { return getConnection().createBlob(); } public NClob createNClob() throws SQLException { return getConnection().createNClob(); } public SQLXML createSQLXML() throws SQLException { return getConnection().createSQLXML(); } public boolean isValid(int timeout) throws SQLException { return true; } public void setClientInfo(String name, String value) throws SQLClientInfoException { for (Connection conn : this.connections.values()) { conn.setClientInfo(name, value); } } public void setClientInfo(Properties properties) throws SQLClientInfoException { for (Connection conn : this.connections.values()) { conn.setClientInfo(properties); } } public String getClientInfo(String name) throws SQLException { return null; } public Properties getClientInfo() throws SQLException { return null; } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return getConnection().createArrayOf(typeName, elements); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return getConnection().createStruct(typeName, attributes); } public void setSchema(String schema) throws SQLException { } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { } public int getNetworkTimeout() throws SQLException { return 0; } @Override public void setInvalid() { } @Override public boolean checkValid(String testSql) throws SQLException { return true; } @Override public boolean checkValid(int timeout) throws SQLException { return true; } @Override public boolean isUsed() { return count > 0; } @Override public String getSchema() throws SQLException { return null; } @Override public void abort(Executor executor) throws SQLException { } }