溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

發(fā)布時間:2021-12-20 10:15:24 來源:億速云 閱讀:178 作者:柒染 欄目:大數(shù)據(jù)

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

JDBC規(guī)范與ShardingSphere

ShardingSphere是一種典型的客戶端分片解決方案,而客戶端分片的實現(xiàn)方式之一就是重寫JDBC規(guī)范。

JDBC 規(guī)范簡介

JDBC(Java Database Connectivity)的設計初衷是提供一套用于各種數(shù)據(jù)庫的統(tǒng)一標準,而不同的數(shù)據(jù)庫廠家共同遵守這套標準,并提供各自的實現(xiàn)方案供應用程序調(diào)用。JDBC規(guī)范具有完整的架構體系,如下圖:

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

對于開發(fā)人員而言,JDBC API是我們訪問數(shù)據(jù)庫的主要途徑,也是ShardingSphere重寫JDBC規(guī)范并添加分片功能的入口。 JDBC開發(fā)一個訪問數(shù)據(jù)庫的處理流程,常見的代碼風格如下:

// 創(chuàng)建池化的數(shù)據(jù)源
PooledDataSource dataSource = new PooledDataSource ();
// 設置MySQL Driver
dataSource.setDriver ("com.mysql.jdbc.Driver");
// 設置數(shù)據(jù)庫URL、用戶名和密碼
dataSource.setUrl ("jdbc:mysql://localhost:3306/test");
dataSource.setUsername ("root");
dataSource.setPassword ("root");
// 獲取連接
Connection connection = dataSource.getConnection();
// 執(zhí)行查詢
PreparedStatement statement = connection.prepareStatement ("select * from user");
// 獲取查詢結(jié)果應該處理
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
	…
}
// 關閉資源
statement.close();
resultSet.close();
connection.close();

DataSource

DataSource 在JDBC規(guī)范中代表的是一種數(shù)據(jù)源,核心作用是獲取數(shù)據(jù)庫連接對象Connection。在JDBC規(guī)范中,實際可以通過DriverManager獲取Connection。我們知道獲取Connection的過程需要建立與數(shù)據(jù)庫之間的連接,而這個過程會產(chǎn)生較大的系統(tǒng)開銷。

為了提高性能,通常會建立一個中間層,該中間層將DriverManager生成的Connection存放到連接池中,然后從池中獲取Connection,可以認為,DataSource就是這樣一個中間層。在日常開發(fā)過程中,我們通常都會基于DataSource來獲取Connection。而在ShardingSphere中,暴露給業(yè)務開發(fā)人員的同樣是一個經(jīng)過增強的DataSource對象。DataSource接口的定義是這樣的:

public interface DataSource  extends CommonDataSource, Wrapper {
  Connection getConnection() throws SQLException;
 
  Connection getConnection(String username, String password)
    throws SQLException;
}

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

其中,DataSource是官方定義的獲取Connection的基礎接口,ConnectionPoolDataSource是從連接池ConnectionPool中獲取的Connection接口。而XADataSource則用來實現(xiàn)在分布式事務環(huán)境下獲取Connection。

DataSource 接口同時還繼承了一個 Wrapper 接口。從接口的命名上看,可以判斷該接口應該起到一種包裝器的作用,事實上,由于很多數(shù)據(jù)庫供應商提供了超越標準 JDBC API 的擴展功能,所以,Wrapper 接口可以把一個由第三方供應商提供的、非 JDBC 標準的接口包裝成標準接口。以 DataSource 接口為例,如果我們想要實現(xiàn)自己的數(shù)據(jù)源 MyDataSource,就可以提供一個實現(xiàn)了 Wrapper 接口的 MyDataSourceWrapper 類來完成包裝和適配:

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

在JDBC規(guī)范中,除了DataSource之外,Connection、Statement、ResultSet等核心對象也都繼承了這個接口。顯然,ShardingSphere提供的就是非JDBC 標準的接口,所以也應該會用到這個Wrapper接口,并提供了類似的實現(xiàn)方案。

Connection

DataSource的目的是獲取Connection對象,我們可以把Connection理解為一種會話(Session)機制。 Connection 代表一個數(shù)據(jù)庫連接,負責完成與數(shù)據(jù)庫之間的通信。所有 SQL 的執(zhí)行都是在某個特定 Connection 環(huán)境中進行的,同時它還提供了一組重載方法,分別用于創(chuàng)建 Statement 和 PreparedStatement。另一方面,Connection 也涉及事務相關的操作,為了實現(xiàn)分片操作,ShardingSphere 同樣也實現(xiàn)了定制化的 Connection 類 ShardingConnection。

statement

JDBC 規(guī)范中的 Statement 存在兩種類型,一種是普通的 Statement,一種是支持預編譯的 PreparedStatement。所謂預編譯,是指數(shù)據(jù)庫的編譯器會對 SQL 語句提前編譯,然后將預編譯的結(jié)果緩存到數(shù)據(jù)庫中,這樣下次執(zhí)行時就可以替換參數(shù)并直接使用編譯過的語句,從而提高 SQL 的執(zhí)行效率。當然,這種預編譯也需要成本,所以在日常開發(fā)中,對數(shù)據(jù)庫只執(zhí)行一次性讀寫操作時,用 Statement 對象進行處理比較合適;而當涉及 SQL 語句的多次執(zhí)行時,可以使用 PreparedStatement。

如果需要查詢數(shù)據(jù)庫中的數(shù)據(jù),只需要調(diào)用 Statement 或 PreparedStatement 對象的 executeQuery 方法即可,該方法以 SQL 語句作為參數(shù),執(zhí)行完后返回一個 JDBC 的 ResultSet 對象。當然,Statement 或 PreparedStatement 中提供了一大批執(zhí)行 SQL 更新和查詢的重載方法。在 ShardingSphere 中,同樣也提供了 ShardingStatement 和 ShardingPreparedStatement 這兩個支持分片操作的 Statement 對象。

ResultSet

一旦通過 Statement 或 PreparedStatement 執(zhí)行了 SQL 語句并獲得了 ResultSet 對象后,那么就可以通過調(diào)用 Resulset 對象中的 next() 方法遍歷整個結(jié)果集。對于分庫分表操作而言,因為涉及從多個數(shù)據(jù)庫或數(shù)據(jù)表中獲取目標數(shù)據(jù),勢必需要對獲取的結(jié)果進行歸并。因此,ShardingSphere 中也提供了分片環(huán)境下的 ShardingResultSet 對象。

總結(jié)

基于JDBC規(guī)范進行數(shù)據(jù)庫訪問的開發(fā)流程圖。

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

基于適配器模式的JDBC重寫實現(xiàn)方案

在 ShardingSphere 中,實現(xiàn)與 JDBC 規(guī)范兼容性的基本策略就是采用了設計模式中的適配器模式(Adapter Pattern)。適配器模式通常被用作連接兩個不兼容接口之間的橋梁,涉及為某一個接口加入獨立的或不兼容的功能。

作為一套適配 JDBC 規(guī)范的實現(xiàn)方案,ShardingSphere 需要對上面介紹的 JDBC API 中的 DataSource、Connection、Statement 及 ResultSet 等核心對象都完成重寫。雖然這些對象承載著不同功能,但重寫機制應該是共通的,否則就需要對不同對象都實現(xiàn)定制化開發(fā),顯然,這不符合我們的設計原則。為此,ShardingSphere 抽象并開發(fā)了一套基于適配器模式的實現(xiàn)方案,整體結(jié)構是這樣的,如下圖所示:

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

首先,我們看到這里有一個JdbcObject 接口,這個接口泛指JDBC API 中的 DataSource、Connection、Statement等核心接口。 這些接口都繼承自包裝器Wrapper接口。ShardingSphere為這個Wrapper接口提供了一個實現(xiàn)類WrapperAdapter。

ShardingSphere基于適配器模式的實現(xiàn)方案圖的底部,有一個ShardingJdbcObject類的定義。這個類也是一種泛指,代表ShardingSphere中用于分片的ShardingDataSource、ShardingConnection、ShardingStatement等對象。

最后發(fā)現(xiàn) ShardingJdbcObject 繼承自一個 AbstractJdbcObjectAdapter,而 AbstractJdbcObjectAdapter 又繼承自 AbstractUnsupportedOperationJdbcObject,這兩個類都是抽象類,而且也都泛指一組類。兩者的區(qū)別在于,AbstractJdbcObjectAdapter 只提供了針對 JdbcObject 接口的一部分實現(xiàn)方法,這些方法是我們完成分片操作所需要的。而對于那些我們不需要的方法實現(xiàn),則全部交由 AbstractUnsupportedOperationJdbcObject 進行實現(xiàn),這兩個類的所有方法的合集,就是原有 JdbcObject 接口的所有方法定義。

ShardingSphere重寫JDBC規(guī)范示例:ShardingConnection

ShardingSphere 的分片引擎中提供了一系列 ShardingJdbcObject 來支持分片操作,包括 ShardingDataSource、ShardingConnection、ShardingStatement、ShardingPreparedStament 等。這里以最具代表性的 ShardingConnection 為例,來講解它的實現(xiàn)過程。請注意,今天我們關注的還是重寫機制,不會對 ShardingConnection 中的具體功能以及與其他類之間的交互過程做過多展開講解。

ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

ShardingConnection 類的一條類層結(jié)構支線就是適配器模式的具體應用,這部分內(nèi)容的類層結(jié)構與前面介紹的重寫機制的類層結(jié)構是完全一致的,如下圖所示: ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解

AbstractConnectionAdapter

我們首先來看看 AbstractConnectionAdapter 抽象類,ShardingConnection 直接繼承了它。在 AbstractConnectionAdapter 中發(fā)現(xiàn)了一個 cachedConnections 屬性,它是一個 Map 對象,該對象其實緩存了這個經(jīng)過封裝的 ShardingConnection 背后真實的 Connection 對象。如果我們對一個 AbstractConnectionAdapter 重復使用,那么這些 cachedConnections 也會一直被緩存,直到調(diào)用 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;
		// 根據(jù)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);
			// 創(chuàng)建不夠的 connections
            List<Connection> newConnections = createConnections(dataSourceName, connectionMode, dataSource, connectionSize - connections.size());
            result.addAll(newConnections);
            synchronized (cachedConnections) {
				// 將新創(chuàng)建的connections也放入緩存中進行管理
                cachedConnections.putAll(dataSourceName, newConnections);
            }
        } else {
			// 如果緩存中沒有對應dataSource的Connections,同樣進行創(chuàng)建并放入到緩存中
            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 執(zhí)行引擎中的重要概念,今天我們先不展開??梢钥吹?createConnections 方法批量調(diào)用了一個 createConnection 抽象方法,該方法需要 AbstractConnectionAdapter 的子類進行實現(xiàn):

protected abstract Connection createConnection(String dataSourceName, DataSource dataSource) throws SQLException;

同時,我們看到對于創(chuàng)建Connection對象,都需要執(zhí)行這樣一個語句:

    replayMethodsInvocation(connection);

這行代碼比較難以理解,讓我們來看看定義它的地方,即WrapperAdapter類。

WrapperAdapter

從命名上看,WrapperAdapter是一個包裝器的適配類,實現(xiàn)了JDBC中的Wrapper接口。我們在該類中找到了這樣一對方法定義:

	//記錄方法調(diào)用
    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));
    }
	//重放方法調(diào)用
    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 類中用到了反射技術根據(jù)傳入的 method 和 arguments 對象執(zhí)行對應方法。 recordMethodInvocation用于記錄需要執(zhí)行的方法和參數(shù),而replayMethodsInvocation則根據(jù)這些方法和參數(shù)通過反射技術進行執(zhí)行。

對于執(zhí)行 replayMethodsInvocation,我們必須先找到 recordMethodInvocation 的調(diào)用入口。通過代碼的調(diào)用關系,可以看到在 AbstractConnectionAdapter 中對其進行了調(diào)用,具體來說就是 setAutoCommit、setReadOnly 和 setTransactionIsolation 這三處方法。這里以 setReadOnly 方法為例給出它的實現(xiàn):

    @Override
    public final void setReadOnly(final boolean readOnly) throws SQLException {
        this.readOnly = readOnly;
 
        //調(diào)用recordMethodInvocation方法記錄方法調(diào)用的元數(shù)據(jù)
        recordMethodInvocation(Connection.class, "setReadOnly", new Class[]{boolean.class}, new Object[]{readOnly});

        //執(zhí)行回調(diào)
        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 所不能支持的,屬于職責分離的一種具體實現(xiàn)方法。

看完上述內(nèi)容,你們掌握ShardingSphere中JDBC規(guī)范與ShardingSphere如何理解的方法了嗎?如果還想學到更多技能或想了解更多相關內(nèi)容,歡迎關注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI