From a1606a3f7b5ee22710f976796d3afb5742c96d2f Mon Sep 17 00:00:00 2001 From: terrymanu Date: Tue, 29 Aug 2017 22:04:32 +0800 Subject: [PATCH] fixed #196 --- RELEASE-NOTES.md | 6 + .../adapter/AbstractConnectionAdapter.java | 2 +- .../connection/MasterSlaveConnection.java | 168 ++++++++++++++++++ .../core/connection/ShardingConnection.java | 2 +- .../datasource/MasterSlaveDataSource.java | 3 +- .../core/statement/MasterSlaveStatement.java | 168 ++++++++++++++++++ .../datasource/MasterSlaveDataSourceTest.java | 6 +- 7 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/MasterSlaveConnection.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/statement/MasterSlaveStatement.java diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5a78591eefd4a..5f7181f0b5389 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,9 @@ +## 1.6.0 + +### 功能提升 + +1. [ISSUE #196](https://github.com/dangdangdotcom/sharding-jdbc/issues/196) 读写分离与分库分表配置独立 + ## 1.5.3 ### 缺陷修正 diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java index 047e4f866a1cc..8f4e024219bfc 100644 --- a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java @@ -41,7 +41,7 @@ public abstract class AbstractConnectionAdapter extends AbstractUnsupportedOpera private int transactionIsolation = TRANSACTION_READ_UNCOMMITTED; - protected abstract Collection getConnections(); + protected abstract Collection getConnections() throws SQLException; @Override public final boolean getAutoCommit() throws SQLException { diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/MasterSlaveConnection.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/MasterSlaveConnection.java new file mode 100644 index 0000000000000..4387aadb3a3fb --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/MasterSlaveConnection.java @@ -0,0 +1,168 @@ +/* + * Copyright 1999-2015 dangdang.com. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *

+ */ + +package com.dangdang.ddframe.rdb.sharding.jdbc.core.connection; + +import com.dangdang.ddframe.rdb.sharding.constant.SQLType; +import com.dangdang.ddframe.rdb.sharding.hint.HintManagerHolder; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractConnectionAdapter; +import com.dangdang.ddframe.rdb.sharding.jdbc.core.datasource.MasterSlaveDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.core.statement.MasterSlaveStatement; +import com.dangdang.ddframe.rdb.sharding.parsing.SQLJudgeEngine; +import com.dangdang.ddframe.rdb.sharding.parsing.parser.sql.SQLStatement; +import lombok.RequiredArgsConstructor; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.LinkedList; + +/** + * Connection that support master-slave. + * + * @author zhangliang + */ +@RequiredArgsConstructor +public final class MasterSlaveConnection extends AbstractConnectionAdapter { + + private final MasterSlaveDataSource masterSlaveDataSource; + + /** + * Get database connections via SQL. + * + *

DDL, DQL and DML will return different database connections.

+ * + * @param sql SQL + * @return database connections via SQL + * @throws SQLException SQL exception + */ + public Collection getConnection(final String sql) throws SQLException { + Collection dataSources = new LinkedList<>(); + SQLStatement sqlStatement = new SQLJudgeEngine(sql).judge(); + if (SQLType.DDL == sqlStatement.getType()) { + dataSources.add(masterSlaveDataSource.getMasterDataSource()); + dataSources.addAll(masterSlaveDataSource.getSlaveDataSources()); + } else { + dataSources.add(masterSlaveDataSource.getDataSource(sqlStatement.getType())); + } + Collection result = new LinkedList<>(); + for (DataSource each : dataSources) { + result.add(each.getConnection()); + replayMethodsInvocation(result); + } + return result; + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return masterSlaveDataSource.getDataSource(SQLType.DML).getConnection().getMetaData(); + } + + @Override + public PreparedStatement prepareStatement(final String sql) throws SQLException { + Collection connections = getConnection(sql); + if (1 != connections.size()) { + throw new UnsupportedOperationException("Cannot support DDL for prepare statement and master-slave only."); + } + return connections.iterator().next().prepareStatement(sql); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { + Collection connections = getConnection(sql); + if (1 != connections.size()) { + throw new UnsupportedOperationException("Cannot support DDL for prepare statement and master-slave only."); + } + return connections.iterator().next().prepareStatement(sql, resultSetType, resultSetConcurrency); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + Collection connections = getConnection(sql); + if (1 != connections.size()) { + throw new UnsupportedOperationException("Cannot support DDL for prepare statement and master-slave only."); + } + return connections.iterator().next().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { + Collection connections = getConnection(sql); + if (1 != connections.size()) { + throw new UnsupportedOperationException("Cannot support DDL for prepare statement and master-slave only."); + } + return connections.iterator().next().prepareStatement(sql, autoGeneratedKeys); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { + Collection connections = getConnection(sql); + if (1 != connections.size()) { + throw new UnsupportedOperationException("Cannot support DDL for prepare statement and master-slave only."); + } + return connections.iterator().next().prepareStatement(sql, columnIndexes); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { + Collection connections = getConnection(sql); + if (1 != connections.size()) { + throw new UnsupportedOperationException("Cannot support DDL for prepare statement and master-slave only."); + } + return connections.iterator().next().prepareStatement(sql, columnNames); + } + + @Override + public Statement createStatement() throws SQLException { + return new MasterSlaveStatement(this); + } + + @Override + public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException { + return new MasterSlaveStatement(this, resultSetType, resultSetConcurrency); + } + + @Override + public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + return new MasterSlaveStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public Collection getConnections() throws SQLException { + Collection dataSources = new LinkedList<>(); + dataSources.add(masterSlaveDataSource.getMasterDataSource()); + dataSources.addAll(masterSlaveDataSource.getSlaveDataSources()); + Collection result = new LinkedList<>(); + for (DataSource each : dataSources) { + Connection connection = each.getConnection(); + replayMethodsInvocation(connection); + result.add(connection); + } + return result; + } + + @Override + public void close() throws SQLException { + HintManagerHolder.clear(); + MasterSlaveDataSource.resetDMLFlag(); + super.close(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/ShardingConnection.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/ShardingConnection.java index e330dced13b23..0ce189b5e785b 100644 --- a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/ShardingConnection.java +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/connection/ShardingConnection.java @@ -177,7 +177,7 @@ public Statement createStatement(final int resultSetType, final int resultSetCon } @Override - public Collection getConnections() { + public Collection getConnections() throws SQLException { return connectionMap.values(); } diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSource.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSource.java index 980aba050335c..073d1e062f3be 100644 --- a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSource.java +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSource.java @@ -22,6 +22,7 @@ import com.dangdang.ddframe.rdb.sharding.constant.SQLType; import com.dangdang.ddframe.rdb.sharding.hint.HintManagerHolder; import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractDataSourceAdapter; +import com.dangdang.ddframe.rdb.sharding.jdbc.core.connection.MasterSlaveConnection; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import lombok.Getter; @@ -118,7 +119,7 @@ public String getDatabaseProductName() throws SQLException { @Override public Connection getConnection() throws SQLException { - throw new UnsupportedOperationException("Master slave data source cannot support get connection directly."); + return new MasterSlaveConnection(this); } /** diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/statement/MasterSlaveStatement.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/statement/MasterSlaveStatement.java new file mode 100644 index 0000000000000..b1a9310fd093c --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/statement/MasterSlaveStatement.java @@ -0,0 +1,168 @@ +/* + * Copyright 1999-2015 dangdang.com. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *

+ */ + +package com.dangdang.ddframe.rdb.sharding.jdbc.core.statement; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractStatementAdapter; +import com.dangdang.ddframe.rdb.sharding.jdbc.core.connection.MasterSlaveConnection; +import com.google.common.base.Preconditions; +import lombok.AccessLevel; +import lombok.Getter; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; + +/** + * Statement that support master-slave. + * + * @author zhangliang + */ +public class MasterSlaveStatement extends AbstractStatementAdapter { + + @Getter(AccessLevel.PROTECTED) + private final MasterSlaveConnection masterSlaveConnection; + + @Getter + private final int resultSetType; + + @Getter + private final int resultSetConcurrency; + + @Getter + private final int resultSetHoldability; + + private Statement statement; + + public MasterSlaveStatement(final MasterSlaveConnection masterSlaveConnection) { + this(masterSlaveConnection, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + } + + public MasterSlaveStatement(final MasterSlaveConnection masterSlaveConnection, final int resultSetType, final int resultSetConcurrency) { + this(masterSlaveConnection, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); + } + + public MasterSlaveStatement(final MasterSlaveConnection masterSlaveConnection, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) { + super(Statement.class); + this.masterSlaveConnection = masterSlaveConnection; + this.resultSetType = resultSetType; + this.resultSetConcurrency = resultSetConcurrency; + this.resultSetHoldability = resultSetHoldability; + } + + @Override + public Connection getConnection() throws SQLException { + return masterSlaveConnection; + } + + @Override + public ResultSet executeQuery(final String sql) throws SQLException { + Collection connections = masterSlaveConnection.getConnection(sql); + Preconditions.checkState(1 == connections.size()); + statement = connections.iterator().next().createStatement(); + return statement.executeQuery(sql); + } + + @Override + public int executeUpdate(final String sql) throws SQLException { + Collection connections = masterSlaveConnection.getConnection(sql); + Preconditions.checkState(1 == connections.size()); + statement = connections.iterator().next().createStatement(); + return statement.executeUpdate(sql); + } + + @Override + public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { + Collection connections = masterSlaveConnection.getConnection(sql); + Preconditions.checkState(1 == connections.size()); + statement = connections.iterator().next().createStatement(); + return statement.executeUpdate(sql, autoGeneratedKeys); + } + + @Override + public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException { + Collection connections = masterSlaveConnection.getConnection(sql); + Preconditions.checkState(1 == connections.size()); + statement = connections.iterator().next().createStatement(); + return statement.executeUpdate(sql, columnIndexes); + } + + @Override + public int executeUpdate(final String sql, final String[] columnNames) throws SQLException { + Collection connections = masterSlaveConnection.getConnection(sql); + Preconditions.checkState(1 == connections.size()); + statement = connections.iterator().next().createStatement(); + return statement.executeUpdate(sql, columnNames); + } + + @Override + public boolean execute(final String sql) throws SQLException { + boolean result = false; + for (Connection each : masterSlaveConnection.getConnection(sql)) { + statement = each.createStatement(); + result = statement.execute(sql); + } + return result; + } + + @Override + public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException { + boolean result = false; + for (Connection each : masterSlaveConnection.getConnection(sql)) { + statement = each.createStatement(); + result = statement.execute(sql, autoGeneratedKeys); + } + return result; + } + + @Override + public boolean execute(final String sql, final int[] columnIndexes) throws SQLException { + boolean result = false; + for (Connection each : masterSlaveConnection.getConnection(sql)) { + statement = each.createStatement(); + result = statement.execute(sql, columnIndexes); + } + return result; + } + + @Override + public boolean execute(final String sql, final String[] columnNames) throws SQLException { + boolean result = false; + for (Connection each : masterSlaveConnection.getConnection(sql)) { + statement = each.createStatement(); + result = statement.execute(sql, columnNames); + } + return result; + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return statement.getGeneratedKeys(); + } + + @Override + public ResultSet getResultSet() throws SQLException { + return statement.getResultSet(); + } + + @Override + protected Collection getRoutedStatements() { + return null; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSourceTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSourceTest.java index 76dba0d4af4bb..12ac7863b6089 100644 --- a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSourceTest.java +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/core/datasource/MasterSlaveDataSourceTest.java @@ -22,6 +22,7 @@ import com.dangdang.ddframe.rdb.sharding.constant.SQLType; import com.dangdang.ddframe.rdb.sharding.fixture.TestDataSource; import com.dangdang.ddframe.rdb.sharding.hint.HintManagerHolder; +import com.dangdang.ddframe.rdb.sharding.jdbc.core.connection.MasterSlaveConnection; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -32,6 +33,7 @@ import java.sql.SQLException; import java.util.Collections; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; @@ -141,9 +143,9 @@ private Connection mockConnection(final String dataBaseProductName) throws SQLEx return result; } - @Test(expected = UnsupportedOperationException.class) + @Test public void assertGetConnection() throws SQLException { - masterSlaveDataSource.getConnection(); + assertThat(masterSlaveDataSource.getConnection(), instanceOf(MasterSlaveConnection.class)); } @Test