您好,登錄后才能下訂單哦!
ShardingSphere中JDBC規范與ShardingSphere如何理解,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
ShardingSphere是一種典型的客戶端分片解決方案,而客戶端分片的實現方式之一就是重寫JDBC規范。
JDBC(Java Database Connectivity)的設計初衷是提供一套用于各種數據庫的統一標準,而不同的數據庫廠家共同遵守這套標準,并提供各自的實現方案供應用程序調用。JDBC規范具有完整的架構體系,如下圖:
對于開發人員而言,JDBC API是我們訪問數據庫的主要途徑,也是ShardingSphere重寫JDBC規范并添加分片功能的入口。 JDBC開發一個訪問數據庫的處理流程,常見的代碼風格如下:
// 創建池化的數據源 PooledDataSource dataSource = new PooledDataSource (); // 設置MySQL Driver dataSource.setDriver ("com.mysql.jdbc.Driver"); // 設置數據庫URL、用戶名和密碼 dataSource.setUrl ("jdbc:mysql://localhost:3306/test"); dataSource.setUsername ("root"); dataSource.setPassword ("root"); // 獲取連接 Connection connection = dataSource.getConnection(); // 執行查詢 PreparedStatement statement = connection.prepareStatement ("select * from user"); // 獲取查詢結果應該處理 ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { … } // 關閉資源 statement.close(); resultSet.close(); connection.close();
DataSource
DataSource 在JDBC規范中代表的是一種數據源,核心作用是獲取數據庫連接對象Connection。在JDBC規范中,實際可以通過DriverManager獲取Connection。我們知道獲取Connection的過程需要建立與數據庫之間的連接,而這個過程會產生較大的系統開銷。
為了提高性能,通常會建立一個中間層,該中間層將DriverManager生成的Connection存放到連接池中,然后從池中獲取Connection,可以認為,DataSource就是這樣一個中間層。在日常開發過程中,我們通常都會基于DataSource來獲取Connection。而在ShardingSphere中,暴露給業務開發人員的同樣是一個經過增強的DataSource對象。DataSource接口的定義是這樣的:
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }
其中,DataSource是官方定義的獲取Connection的基礎接口,ConnectionPoolDataSource是從連接池ConnectionPool中獲取的Connection接口。而XADataSource則用來實現在分布式事務環境下獲取Connection。
DataSource 接口同時還繼承了一個 Wrapper 接口。從接口的命名上看,可以判斷該接口應該起到一種包裝器的作用,事實上,由于很多數據庫供應商提供了超越標準 JDBC API 的擴展功能,所以,Wrapper 接口可以把一個由第三方供應商提供的、非 JDBC 標準的接口包裝成標準接口。以 DataSource 接口為例,如果我們想要實現自己的數據源 MyDataSource,就可以提供一個實現了 Wrapper 接口的 MyDataSourceWrapper 類來完成包裝和適配:
在JDBC規范中,除了DataSource之外,Connection、Statement、ResultSet等核心對象也都繼承了這個接口。顯然,ShardingSphere提供的就是非JDBC 標準的接口,所以也應該會用到這個Wrapper接口,并提供了類似的實現方案。
Connection
DataSource的目的是獲取Connection對象,我們可以把Connection理解為一種會話(Session)機制。 Connection 代表一個數據庫連接,負責完成與數據庫之間的通信。所有 SQL 的執行都是在某個特定 Connection 環境中進行的,同時它還提供了一組重載方法,分別用于創建 Statement 和 PreparedStatement。另一方面,Connection 也涉及事務相關的操作,為了實現分片操作,ShardingSphere 同樣也實現了定制化的 Connection 類 ShardingConnection。
statement
JDBC 規范中的 Statement 存在兩種類型,一種是普通的 Statement,一種是支持預編譯的 PreparedStatement。所謂預編譯,是指數據庫的編譯器會對 SQL 語句提前編譯,然后將預編譯的結果緩存到數據庫中,這樣下次執行時就可以替換參數并直接使用編譯過的語句,從而提高 SQL 的執行效率。當然,這種預編譯也需要成本,所以在日常開發中,對數據庫只執行一次性讀寫操作時,用 Statement 對象進行處理比較合適;而當涉及 SQL 語句的多次執行時,可以使用 PreparedStatement。
如果需要查詢數據庫中的數據,只需要調用 Statement 或 PreparedStatement 對象的 executeQuery 方法即可,該方法以 SQL 語句作為參數,執行完后返回一個 JDBC 的 ResultSet 對象。當然,Statement 或 PreparedStatement 中提供了一大批執行 SQL 更新和查詢的重載方法。在 ShardingSphere 中,同樣也提供了 ShardingStatement 和 ShardingPreparedStatement 這兩個支持分片操作的 Statement 對象。
ResultSet
一旦通過 Statement 或 PreparedStatement 執行了 SQL 語句并獲得了 ResultSet 對象后,那么就可以通過調用 Resulset 對象中的 next() 方法遍歷整個結果集。對于分庫分表操作而言,因為涉及從多個數據庫或數據表中獲取目標數據,勢必需要對獲取的結果進行歸并。因此,ShardingSphere 中也提供了分片環境下的 ShardingResultSet 對象。
總結
基于JDBC規范進行數據庫訪問的開發流程圖。
在 ShardingSphere 中,實現與 JDBC 規范兼容性的基本策略就是采用了設計模式中的適配器模式(Adapter Pattern)。適配器模式通常被用作連接兩個不兼容接口之間的橋梁,涉及為某一個接口加入獨立的或不兼容的功能。
作為一套適配 JDBC 規范的實現方案,ShardingSphere 需要對上面介紹的 JDBC API 中的 DataSource、Connection、Statement 及 ResultSet 等核心對象都完成重寫。雖然這些對象承載著不同功能,但重寫機制應該是共通的,否則就需要對不同對象都實現定制化開發,顯然,這不符合我們的設計原則。為此,ShardingSphere 抽象并開發了一套基于適配器模式的實現方案,整體結構是這樣的,如下圖所示:
首先,我們看到這里有一個JdbcObject 接口,這個接口泛指JDBC API 中的 DataSource、Connection、Statement等核心接口。 這些接口都繼承自包裝器Wrapper接口。ShardingSphere為這個Wrapper接口提供了一個實現類WrapperAdapter。
ShardingSphere基于適配器模式的實現方案圖的底部,有一個ShardingJdbcObject類的定義。這個類也是一種泛指,代表ShardingSphere中用于分片的ShardingDataSource、ShardingConnection、ShardingStatement等對象。
最后發現 ShardingJdbcObject 繼承自一個 AbstractJdbcObjectAdapter,而 AbstractJdbcObjectAdapter 又繼承自 AbstractUnsupportedOperationJdbcObject,這兩個類都是抽象類,而且也都泛指一組類。兩者的區別在于,AbstractJdbcObjectAdapter 只提供了針對 JdbcObject 接口的一部分實現方法,這些方法是我們完成分片操作所需要的。而對于那些我們不需要的方法實現,則全部交由 AbstractUnsupportedOperationJdbcObject 進行實現,這兩個類的所有方法的合集,就是原有 JdbcObject 接口的所有方法定義。
ShardingSphere 的分片引擎中提供了一系列 ShardingJdbcObject 來支持分片操作,包括 ShardingDataSource、ShardingConnection、ShardingStatement、ShardingPreparedStament 等。這里以最具代表性的 ShardingConnection 為例,來講解它的實現過程。請注意,今天我們關注的還是重寫機制,不會對 ShardingConnection 中的具體功能以及與其他類之間的交互過程做過多展開講解。
ShardingConnection 類的一條類層結構支線就是適配器模式的具體應用,這部分內容的類層結構與前面介紹的重寫機制的類層結構是完全一致的,如下圖所示:
AbstractConnectionAdapter
我們首先來看看 AbstractConnectionAdapter 抽象類,ShardingConnection 直接繼承了它。在 AbstractConnectionAdapter 中發現了一個 cachedConnections 屬性,它是一個 Map 對象,該對象其實緩存了這個經過封裝的 ShardingConnection 背后真實的 Connection 對象。如果我們對一個 AbstractConnectionAdapter 重復使用,那么這些 cachedConnections 也會一直被緩存,直到調用 close 方法。可以從 AbstractConnectionAdapter 的 getConnections 方法中理解具體的操作過程:
public final List<Connection> getConnections(final ConnectionMode connectionMode, final String dataSourceName, final int connectionSize) throws SQLException { // 獲取DataSource DataSource dataSource = getDataSourceMap().get(dataSourceName); Preconditions.checkState(null != dataSource, "Missing the data source name: '%s'", dataSourceName); Collection<Connection> connections; // 根據dataSourceName 從cachedConnections中獲取connections synchronized (cachedConnections) { connections = cachedConnections.get(dataSourceName); } List<Connection> result; // 如果connections多于想要的connectionsSize,則只獲取所需部分 if (connections.size() >= connectionSize) { result = new ArrayList<>(connections).subList(0, connectionSize); } else if (!connections.isEmpty()) { // 如果connections不夠 result = new ArrayList<>(connectionSize); result.addAll(connections); // 創建不夠的 connections List<Connection> newConnections = createConnections(dataSourceName, connectionMode, dataSource, connectionSize - connections.size()); result.addAll(newConnections); synchronized (cachedConnections) { // 將新創建的connections也放入緩存中進行管理 cachedConnections.putAll(dataSourceName, newConnections); } } else { // 如果緩存中沒有對應dataSource的Connections,同樣進行創建并放入到緩存中 result = new ArrayList<>(createConnections(dataSourceName, connectionMode, dataSource, connectionSize)); synchronized (cachedConnections) { cachedConnections.putAll(dataSourceName, result); } } return result; }
我們主要關注 createConnections方法:
private List<Connection> createConnections(final String dataSourceName, final ConnectionMode connectionMode, final DataSource dataSource, final int connectionSize) throws SQLException { if (1 == connectionSize) { Connection connection = createConnection(dataSourceName, dataSource); replayMethodsInvocation(connection); return Collections.singletonList(connection); } if (ConnectionMode.CONNECTION_STRICTLY == connectionMode) { return createConnections(dataSourceName, dataSource, connectionSize); } synchronized (dataSource) { return createConnections(dataSourceName, dataSource, connectionSize); } }
這段代碼涉及了 ConnectionMode(連接模式),這是 ShardingSphere 執行引擎中的重要概念,今天我們先不展開。可以看到 createConnections 方法批量調用了一個 createConnection 抽象方法,該方法需要 AbstractConnectionAdapter 的子類進行實現:
protected abstract Connection createConnection(String dataSourceName, DataSource dataSource) throws SQLException;
同時,我們看到對于創建Connection對象,都需要執行這樣一個語句:
replayMethodsInvocation(connection);
這行代碼比較難以理解,讓我們來看看定義它的地方,即WrapperAdapter類。
WrapperAdapter
從命名上看,WrapperAdapter是一個包裝器的適配類,實現了JDBC中的Wrapper接口。我們在該類中找到了這樣一對方法定義:
//記錄方法調用 public final void recordMethodInvocation(final Class<?> targetClass, final String methodName, final Class<?>[] argumentTypes, final Object[] arguments) { jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments)); } //重放方法調用 public final void replayMethodsInvocation(final Object target) { for (JdbcMethodInvocation each : jdbcMethodInvocations) { each.invoke(target); } }
這兩個方法都用到了JdbcMethodInvocation類:
public class JdbcMethodInvocation { @Getter private final Method method; @Getter private final Object[] arguments; /** * Invoke JDBC method. * * @param target target object */ @SneakyThrows public void invoke(final Object target) { method.invoke(target, arguments); } }
JdbcMethodInvocation 類中用到了反射技術根據傳入的 method 和 arguments 對象執行對應方法。 recordMethodInvocation用于記錄需要執行的方法和參數,而replayMethodsInvocation則根據這些方法和參數通過反射技術進行執行。
對于執行 replayMethodsInvocation,我們必須先找到 recordMethodInvocation 的調用入口。通過代碼的調用關系,可以看到在 AbstractConnectionAdapter 中對其進行了調用,具體來說就是 setAutoCommit、setReadOnly 和 setTransactionIsolation 這三處方法。這里以 setReadOnly 方法為例給出它的實現:
@Override public final void setReadOnly(final boolean readOnly) throws SQLException { this.readOnly = readOnly; //調用recordMethodInvocation方法記錄方法調用的元數據 recordMethodInvocation(Connection.class, "setReadOnly", new Class[]{boolean.class}, new Object[]{readOnly}); //執行回調 forceExecuteTemplate.execute(cachedConnections.values(), new ForceExecuteCallback<Connection>() { @Override public void execute(final Connection connection) throws SQLException { connection.setReadOnly(readOnly); } }); }
AbstractUnsupportedOperationConnection
從類層次關系上,可以看到AbstractConnectionAdapter直接繼承的是AbstractUnsupportedOperationConnection。在AbstractUnsupportedOperationConnection中都是一組直接拋出異常的方法。這里截取部分代碼:
public abstract class AbstractUnsupportedOperationConnection extends WrapperAdapter implements Connection { @Override public final CallableStatement prepareCall(final String sql) throws SQLException { throw new SQLFeatureNotSupportedException("prepareCall"); } @Override public final CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { throw new SQLFeatureNotSupportedException("prepareCall"); } … }
AbstractUnsupportedOperationConnection 這種處理方式的目的就是明確哪些操作是 AbstractConnectionAdapter 及其子類 ShardingConnection 所不能支持的,屬于職責分離的一種具體實現方法。
看完上述內容,你們掌握ShardingSphere中JDBC規范與ShardingSphere如何理解的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。