Introduction to the

In the previous few articles, we have run the core functions of ShardingSphere based on the source code. At the beginning of this article, we begin to explore the source code to see how ShardingSphere works

An overview of

Before we begin, let’s consider the mystery of this quest:

  • 1. How does ShardingSphere load the multiple database sources we configured?
  • 2. How does ShardingSphere write statements to different data sources?

Explore with questions. You may not get answers, but at least you have a direction

Based on our JDBC exploration article, click on the relevant breakpoint and start our exploration journey

  • ShardingSphere JDBC sub-database sub-table read-write separation data encryption

The source code to explore

1. Find the ShardingSphere JDBC entry

Put breakpoints on init and process in the code below and see if you can find any clues through the program call stack

public final class ExampleExecuteTemplate {
    
    public static void run(final ExampleService exampleService) throws SQLException {
        try {
            exampleService.initEnvironment();
            exampleService.processSuccess();
        } finally{ exampleService.cleanEnvironment(); }}}Copy the code

ExampleService. InitEnvironment () come in later, the call stack is empty, what also have no, it looks like we’re the first to explore if the load multiple configuration data for the purpose of fell through

However, I continued to Debug, but it was all Ibatis related, and there was no code related to ShardingSphere. I comforted myself that it was normal

Continue to explore: exampleService processSuccess () to the following statements related to insert, query and other related operations, the insertData () on the breakpoint

public class OrderServiceImpl implements ExampleService {
    @Override
    @Transactional
    public void processSuccess(a) throws SQLException {
        System.out.println("-------------- Process Success Begin ---------------");
        List<Long> orderIds = insertData();
        printData();
        deleteData(orderIds);
        printData();
        System.out.println("-------------- Process Success Finish --------------"); }}Copy the code

Based on previous attempts that didn’t work, I had to tread carefully and follow function calls further down the line

Mapperproxy. class, execute the following method to proceed:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args); }}catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
	// Enter from here
        return mapperMethod.execute(this.sqlSession, args);
    }
Copy the code

Let’s go to the following function

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
	    // Enter from here and trace the insert method
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
	......
    }
Copy the code

SqlSession = SqlSession; SqlSession = SqlSession; SqlSession = SqlSession;

Database name is logic_DB, think of Teacher Qin said ShardingSphere UI can also use JDBC, but its name is logic_DB

There’s some speculation at this point, but we need to keep tracking and confirm

Sqlsessiontemplate. class, which reobtains a sqlSession, but is still logic_db

private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
		// Continue here
                Object result = method.invoke(sqlSession, args);
                if(! SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }

                unwrapped = result;
            } catch (Throwable var11) {
               ......
            } finally{... }returnunwrapped; }}Copy the code

And met a lot of update, we’re going down, finally we come to: PreparedStatementHandler. Class

public class PreparedStatementHandler extends BaseStatementHandler {
    public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
	// Execution is critical here
        ps.execute();
        int rows = ps.getUpdateCount();
        Object parameterObject = this.boundSql.getParameterObject();
        KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
        keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
        returnrows; }}Copy the code

In the above: ps. The execute (), we to go on, surprise came, bang, came to ShardingSphere own writing class: ShardingSpherePreparedStatement. Java

Here are some thoughts on ShardingSphere JDBC:

ShardingSphere JDBC feels in fact there is a middleware in essence, embedded, is also between the program and the database, in the local exploration, it is a logic_DB, the most downstream of Mybatis, all database access truncation processing

I have such a preliminary experience, and there may be more discoveries with the further research. Here we find the entrance of ShardingSphere

2. Initially explore the ShardingSphere JDBC processing path

We came to the entrance, find: on ShardingSpherePreparedStatement. Java

    @Override
    public boolean execute(a) throws SQLException {
        try {
            clearPrevious();
	    // Here are the actual database sources and statements
            executionContext = createExecutionContext();
            if (metaDataContexts.getMetaData(connection.getSchemaName()).getRuleMetaData().getRules().stream().anyMatch(each -> each instanceof RawExecutionRule)) {
                // TODO process getStatement
                Collection<ExecuteResult> executeResults = rawExecutor.execute(createRawExecutionGroupContext(), executionContext.getLogicSQL(), new RawSQLExecutorCallback());
                return executeResults.iterator().next() instanceof QueryResult;
            }
            if (executionContext.getRouteContext().isFederated()) {
                List<QueryResult> queryResults = executeFederatedQuery();
                return! queryResults.isEmpty(); }// Continue tracing
            ExecutionGroupContext<JDBCExecutionUnit> executionGroupContext = createExecutionGroupContext();
            cacheStatements(executionGroupContext.getInputGroups());
            return driverJDBCExecutor.execute(executionGroupContext,
                    executionContext.getLogicSQL(), executionContext.getRouteContext().getRouteUnits(), createExecuteCallback());
        } finally{ clearBatch(); }}Copy the code

ExecutionContext = createExecutionContext() has the original statement, or we timed the actual data source and SQL statement, as shown below. Let’s continue:

To: AbstractExecutionPrepareEngine. Java, actually have been able to get in front of the display the execution of the statement, it is less clear what is the purpose of this step

    public final ExecutionGroupContext<T> prepare(final RouteContext routeContext, final Collection<ExecutionUnit> executionUnits) throws SQLException {
        Collection<ExecutionGroup<T>> result = new LinkedList<>();
        for (Entry<String, List<SQLUnit>> entry : aggregateSQLUnitGroups(executionUnits).entrySet()) {
            String dataSourceName = entry.getKey();
            List<SQLUnit> sqlUnits = entry.getValue();
            List<List<SQLUnit>> sqlUnitGroups = group(sqlUnits);
            ConnectionMode connectionMode = maxConnectionsSizePerQuery < sqlUnits.size() ? ConnectionMode.CONNECTION_STRICTLY : ConnectionMode.MEMORY_STRICTLY;
            result.addAll(group(dataSourceName, sqlUnitGroups, connectionMode));
        }
        return decorate(routeContext, result);
    }
Copy the code

Back to ShardingSpherePreparedStatement. Java, tracking driverJDBCExecutor. Execute, came to: driverJDBCExecutor. Java

   public boolean execute(final ExecutionGroupContext<JDBCExecutionUnit> executionGroupContext, final LogicSQL logicSQL,
                           final Collection<RouteUnit> routeUnits, final JDBCExecutorCallback<Boolean> callback) throws SQLException {
        try {
            ExecuteProcessEngine.initialize(logicSQL, executionGroupContext, metaDataContexts.getProps());
            List<Boolean> results = jdbcLockEngine.execute(executionGroupContext, logicSQL.getSqlStatementContext(), routeUnits, callback);
            boolean result = null! = results && ! results.isEmpty() &&null! = results.get(0) && results.get(0);
            ExecuteProcessEngine.finish(executionGroupContext.getExecutionID());
            return result;
        } finally{ ExecuteProcessEngine.clean(); }}Copy the code

The interesting thing is whether jdbcLockEngine or logic_db keeps track of the execute function, all the way down to executorengae.java

    public <I, O> List<O> execute(final ExecutionGroupContext<I> executionGroupContext,
                                  final ExecutorCallback<I, O> firstCallback, final ExecutorCallback<I, O> callback, final boolean serial) throws SQLException {
        if (executionGroupContext.getInputGroups().isEmpty()) {
            return Collections.emptyList();
        }
        return serial ? serialExecute(executionGroupContext.getInputGroups().iterator(), firstCallback, callback)
                : parallelExecute(executionGroupContext.getInputGroups().iterator(), firstCallback, callback);
    }

    private <I, O> List<O> serialExecute(final Iterator<ExecutionGroup<I>> executionGroups, final ExecutorCallback<I, O> firstCallback, final ExecutorCallback<I, O> callback) throws SQLException {
        ExecutionGroup<I> firstInputs = executionGroups.next();
        List<O> result = new LinkedList<>(syncExecute(firstInputs, null == firstCallback ? callback : firstCallback));
        while (executionGroups.hasNext()) {
	    // Continue tracing
            result.addAll(syncExecute(executionGroups.next(), callback));
        }
        return result;
    }
Copy the code

Tracking all the way down to: JDBCExecutorCallback. Java

    public final Collection<T> execute(final Collection<JDBCExecutionUnit> executionUnits, final boolean isTrunkThread, final Map<String, Object> dataMap) throws SQLException {
        // TODO It is better to judge whether need sane result before execute, can avoid exception thrown
        Collection<T> result = new LinkedList<>();
        for (JDBCExecutionUnit each : executionUnits) {
            T executeResult = execute(each, isTrunkThread, dataMap);
            if (null != executeResult) {
                result.add(executeResult);
            }
        }
        return result;
    }

    private T execute(final JDBCExecutionUnit jdbcExecutionUnit, final boolean isTrunkThread, final Map<String, Object> dataMap) throws SQLException {
        SQLExecutorExceptionHandler.setExceptionThrown(isExceptionThrown);
	// Here we get our real database source
        DataSourceMetaData dataSourceMetaData = getDataSourceMetaData(jdbcExecutionUnit.getStorageResource().getConnection().getMetaData());
        SQLExecutionHook sqlExecutionHook = new SPISQLExecutionHook();
        try {
            SQLUnit sqlUnit = jdbcExecutionUnit.getExecutionUnit().getSqlUnit();
            sqlExecutionHook.start(jdbcExecutionUnit.getExecutionUnit().getDataSourceName(), sqlUnit.getSql(), sqlUnit.getParameters(), dataSourceMetaData, isTrunkThread, dataMap);
	    // Continue tracing the execution statement
            T result = executeSQL(sqlUnit.getSql(), jdbcExecutionUnit.getStorageResource(), jdbcExecutionUnit.getConnectionMode());
            sqlExecutionHook.finishSuccess();
            finishReport(dataMap, jdbcExecutionUnit);
            return result;
        } catch (finalSQLException ex) { ...... }}Copy the code

In the code above, find the code related to directly fetching the real database source

We continue to monitor on the relevant code: ShardingSpherePreparedStatement. Java, come

   private JDBCExecutorCallback<Boolean> createExecuteCallback(a) {
        boolean isExceptionThrown = SQLExecutorExceptionHandler.isExceptionThrown();
        return new JDBCExecutorCallback<Boolean>(metaDataContexts.getMetaData(connection.getSchemaName()).getResource().getDatabaseType(), sqlStatement, isExceptionThrown) {
            
            @Override
            protected Boolean executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode) throws SQLException {
                return ((PreparedStatement) statement).execute();
            }
            
            @Override
            protected Optional<Boolean> getSaneResult(final SQLStatement sqlStatement) {
                returnOptional.empty(); }}; }Copy the code

If you use native JDBC to write your own database, you should compare the data

conclusion

Although the relevant database source parsing and how to hit the corresponding part of the database has not been explored, but we have located its general location, can continue to study later

ShardingSphere JDBC in this exploration, feel is an embedded Proxy, its database name fixed logic_DB, truncated Mybatis and the real database link, the current middleman, roughly as follows: