Sharding JDBC源码分析-JdbcMethodInvocation类的作用

来源:互联网 发布:淘宝门头在线制作 编辑:程序博客网 时间:2024/06/10 05:06

       摘要

       当当的Sharding JDBC是在JDBC规范上进行封装来实现数据库分表分库分表功能的。其整体结构非常清晰,主线就是将JDBC规范中的DataSource、Connection、Statement、PreparedStament分别封装为ShardingDataSource、ShardingConnection、ShardingStatement、ShardingPreparedStament类。然后将对象的方法进行重写,而ShardingDataSource、ShardingConnection、ShardingStatement、ShardingPreparedStament类可以看作一个代理对象,它们内部完成了根据逻辑sql语句,转化成真实sql语句,路由找到对应数据源,并根据数据源产生真实的Connection对象,然后执行真实sql语句,并将结果归并返回给应用层的功能。今天来分析一下Sharding JDBC中JdbcMethodInvocation类的作用。

      一般情况在调用Connection对象的setAutoCommit方法

默认情况下执行完的操作会被自动提交,当我们要改变时可以在执行sql前调用Connection类的setAutoCommit方法
try{      con = getConnection();      con.setAutoCommit(false);      /*      * do what you want here.      */      con.commit();   }catch(Throwable e){      if(con!=null){          try {              con.rollback();          } catch (SQLException e1) {              e1.printStackTrace();          }      }     throw new RuntimeException(e);   }finally{      if(con!=null){          try {              con.close();          } catch (SQLException e) {              e.printStackTrace();          }      }  }
而ShardingJDBC的ShardingConnection类封装了JDBC中的Connection接口,ShardingConnection只是多个Connection对象的一个代理,直接调用他的重写的setAutoCommit方法无法达到实际的效果,为此ShardingJDBC中引入了JdbcMethodInvocation对ShardingConnection、ShardingStatement、ShardingPreparedStatement类中类似setAutoCommit方法的方法调用进行记录。

JdbcMethodInvocation作用分析

       通过ShardingDataSource类得到的ShardingConnection不是真的JDBC Connection对象,其内部有一个Map<String, Connection> connectionMap key为数据源名,value为真实的Connection。当调用Connection类的一些方法时,实际是调用了ShardingConnection 重写后的方法,而此时connectionMap的还是空,达不到调用真实Connection对象方法的功能,为此在ShardingConnection 重写后的方法方法中将调用的方法与方法的参数记录到JdbcMethodInvocation对象中。只有当ShardingPreparedStatement或者ShardingStatement调用对应数据操作函数时(executeQuery,execute,executeUpdate)才会调用ShardingConnection的getContetion方法产生真实的Connection对象,同时把这些对象放入connectionMap。为此在调用ShardingConnection 的setAutoCommint方法、setReadOnly方法、setTransactionIsolation方法时,要记录一下所有调用过的方法的名称与参数。当通过ShardingPreparedStatement或者ShardingStatement产生一个实际的Connection时,再通过反射的方式让真实的Connection对象调用JdbcMethodInvocation记录的方法与方法的参数。
       ShardingJDBC框架通过配置文件指定ShardingDataSource与外部应用程序交互,达到访问真实数据源的目的。其有三个成员变量
  •  private final ShardingProperties shardingProperties;
  •  private final ExecutorEngine executorEngine;
  •  private final ShardingContext shardingContext;
 shardingProperties为一些属性,executorEngine为一个多线程的执行引擎其内部采用Guava的ListeningExecutorService实现。shardingContext为上下文信息,记录了分库分表规则配置、执行引擎、sql路由引擎这些信息。以便ShardingConnection 使用。
       利用ShardingJDBC框架后通过DataSource得到的Connection实际上是包装后的ShardingConnection。ShardingDataSource对DataSource的所有方法进行了重写,其中getConnection如下:
  @Override    public ShardingConnection getConnection() throws SQLException {        MetricsContext.init(shardingProperties);        return new ShardingConnection(shardingContext);  }
       得到得到的Connection实际上是包装后的ShardingConnection,而Sharding上下文对象在创建ShardingConnection时被传入。按照上面的一般情况的示例,当我们调用con.setAutoCommit方法时,实际是调用了ShardingDataSource的getConnection方法产生的ShardingConnection对象的setAutoCommit方法。
       ShardingConnection自的成员变量如下:
  •       private final ShardingContext shardingContext;
  •       private final Map<String, Connection> connectionMap = new HashMap<>();
shardingContext为Sharding上下文对象在创建ShardingConnection时被传入,而connectionMap 为保存真实Connection的Map,其key为配置文件中真实dataSource Bean的id。connectionMap 的初始值为空。在调用ShardingConnection的getConnection方法得到真实的Connection对象后,才会将其实放connectionMap 中。代码如下:
public Connection getConnection(final String dataSourceName, final SQLStatementType sqlStatementType) throws SQLException {        Connection result = getConnectionInternal(dataSourceName, sqlStatementType);        replayMethodsInvocation(result);//对记录的要执行的调用方法,在这里通过反射进行执行        return result;}

private Connection getConnectionInternal(final String dataSourceName, final SQLStatementType sqlStatementType) throws SQLException {        if (connectionMap.containsKey(dataSourceName)) {            return connectionMap.get(dataSourceName);        }        Context metricsContext = MetricsContext.start(Joiner.on("-").join("ShardingConnection-getConnection", dataSourceName));        DataSource dataSource = shardingContext.getShardingRule().getDataSourceRule().getDataSource(dataSourceName);        if (dataSource instanceof MasterSlaveDataSource) {            dataSource = ((MasterSlaveDataSource) dataSource).getDataSource(sqlStatementType);        }        Connection result = dataSource.getConnection();        MetricsContext.stop(metricsContext);        connectionMap.put(dataSourceName, result);//将真实的Connction对象放入connectionMap中        return result; }
按照上面的一般情况的示例,当我们调用con.setAutoCommit方法时,实际是调用ShardingConnection重写后的setAutoCommit方法。该方法在其父类AbstractConnectionAdapter中完成重写,代码如下:
@Override    public final void setAutoCommit(final boolean autoCommit) throws SQLException {        this.autoCommit = autoCommit;        //getConnections方法实际ShardingConnection对象中的coonectionMap对象的value集合,       //为空时记录调用过的方法与对应参数,当在产生真正的Connection对象时,       //再利用反射的方式让真正的Connection对象调用该方法        if (getConnections().isEmpty()) {            recordMethodInvocation(Connection.class, "setAutoCommit", new Class[] {boolean.class}, new Object[] {autoCommit});            return;        }        for (Connection each : getConnections()) {            each.setAutoCommit(autoCommit);        }    }


AbstractConnectionAdapter的setAutoCommit方法中的getConnections得到了实现是ShardingConnection对象中的coonectionMap对象的value集合。ShardingConnection对AbstractConnectionAdapter的getConnections方法的重写:
public Collection<Connection> getConnections() {        return connectionMap.values(); }
而调用setAutoCommit时coonectionMap还是空。不是达到真实Connection对象的setAutoCommit的目的,为此先对其进行记录,当在产生真实的Connection对象时,再利用反射的方法让其调用recordMethodInvocation方法记录的方法。其中recordMethodInvocation方法继续自WrapperAdapter类。记录了调用的方法与传入方法的参数,实际是将JdbcMethodInvocation对象放入WrapperAdapter的成员变量
private final Collection<JdbcMethodInvocation> jdbcMethodInvocations = new ArrayList<>();
中。WrapperAdapter中的recordMethodInvocation方法如下:
protected final void recordMethodInvocation(final Class<?> targetClass, final String methodName, final Class<?>[] argumentTypes, final Object[] arguments) {        try {            jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments));        } catch (final NoSuchMethodException ex) {            throw new ShardingJdbcException(ex);        }}

jdbcMethodInvocations记录了所有调用的方法与参数。
@RequiredArgsConstructorpublic final class JdbcMethodInvocation {    private final Method method;//调用的方法    private final Object[] arguments;//调用方法时传入的参数        /**     *  调用方法.     *      * @param target 目标对象     */    public void invoke(final Object target) {        try {            method.invoke(target, arguments);        } catch (final IllegalAccessException | InvocationTargetException ex) {            throw new ShardingJdbcException("Invoke jdbc method exception", ex);        }    }}
现在来看一下,当真实的Connection对象产生时,通过反射的方法调用jdbcMetiondInvoctions记录的要调用的方法与参数。真实的Connection的产生就是通过我们上面提到的ShardingConnection的getConnection方法来达到的。
 
public Connection getConnection(final String dataSourceName, final SQLStatementType sqlStatementType) throws SQLException {        Connection result = getConnectionInternal(dataSourceName, sqlStatementType);        replayMethodsInvocation(result);//对记录的要执行的调用方法,在这里通过反射进行执行        return result;}

private Connection getConnectionInternal(final String dataSourceName, final SQLStatementType sqlStatementType) throws SQLException {        if (connectionMap.containsKey(dataSourceName)) {            return connectionMap.get(dataSourceName);        }        Context metricsContext = MetricsContext.start(Joiner.on("-").join("ShardingConnection-getConnection", dataSourceName));        DataSource dataSource = shardingContext.getShardingRule().getDataSourceRule().getDataSource(dataSourceName);        if (dataSource instanceof MasterSlaveDataSource) {            dataSource = ((MasterSlaveDataSource) dataSource).getDataSource(sqlStatementType);        }        Connection result = dataSource.getConnection();        MetricsContext.stop(metricsContext);        connectionMap.put(dataSourceName, result);//将真实的Connction对象放入connectionMap中        return result; }

在 return result之前执行replayMethodsInvocations方法,该方法实际通过反射的方式对之前记录的方法进行了调用,这个例子中是对setAutoCommit方法进行了调用。replayMethodsInvocations继承自WrapperAdapter类,代码如下:
protected final void replayMethodsInvocation(final Object target) {        for (JdbcMethodInvocation each : jdbcMethodInvocations) {            each.invoke(target);        } }

除了setAutoCommit方法外以下的方式也是通过这JdbcMethodInvocation实现的。
AbstractConnectionAdapter类:
  • mmitsetReadOnly
  • setTransactionIsolation
AbstractStatementAdapter类:
  • setPoolable
  • setFetchSize
  • setEscapeProcessing
  • setCursorName
  • setMaxFieldSize
  • setMaxRows
  • setQueryTimeout
因调用这些方法时真实的Connection对象、Statement对象、PreparedStatement对象还没有产生,所以要先记录下,当在真实的对象产生时,再通过反射的方式调用在JdbcMethodInvocation中记录的方法。
后面还会分享更多ShardingJDBC源码的分析,不正确的地方欢迎指正,也欢迎一同分享。

2 0