@Override
  protected <R> void postInvoke(
      Invoker<Z, D, S, R, SQLException> invoker, S proxy, Method method, Object... parameters) {
    if (method.equals(addBatchMethod)) {
      this.getProxyFactory().addBatchSQL((String) parameters[0]);
    } else if (method.equals(clearBatchMethod) || method.equals(executeBatchMethod)) {
      this.getProxyFactory().clearBatch();
      this.logger.log(Level.TRACE, "Clearing recorded batch methods");
      this.getProxyFactory().clearBatchInvokers();
    } else if (method.equals(closeMethod)) {
      Resources.close(this.getProxyFactory().getInputSinkRegistry());
      this.getProxyFactory().remove();
    }

    if (this.isBatchMethod(method)) {
      this.logger.log(Level.TRACE, "Recording batch method: {0}", invoker);
      this.getProxyFactory().addBatchInvoker(invoker);
    } else if (driverWriteMethodSet.contains(method)) {
      this.getProxyFactory().record(invoker);
    }
  }
 @Override
 public void close(D database, Connection connection) {
   Resources.close(connection);
 }
  @Override
  public <Z, D extends Database<Z>> void synchronize(
      SynchronizationContext<Z, D> context, TableProperties table) throws SQLException {
    final String tableName = table.getName().getDMLName();
    final Collection<String> columns = table.getColumns();

    final String commaDelimitedColumns = Strings.join(columns, Strings.PADDED_COMMA);

    final String selectSQL = String.format("SELECT %s FROM %s", commaDelimitedColumns, tableName);
    final String deleteSQL = context.getDialect().getTruncateTableSQL(table);
    final String insertSQL =
        String.format(
            "INSERT INTO %s (%s) VALUES (%s)",
            tableName,
            commaDelimitedColumns,
            Strings.join(
                Collections.nCopies(columns.size(), Strings.QUESTION), Strings.PADDED_COMMA));

    Connection sourceConnection = context.getConnection(context.getSourceDatabase());
    final Statement selectStatement = sourceConnection.createStatement();
    try {
      selectStatement.setFetchSize(this.fetchSize);

      Callable<ResultSet> callable =
          new Callable<ResultSet>() {
            @Override
            public ResultSet call() throws SQLException {
              logger.log(Level.DEBUG, selectSQL);
              return selectStatement.executeQuery(selectSQL);
            }
          };

      Future<ResultSet> future = context.getExecutor().submit(callable);

      Connection targetConnection = context.getConnection(context.getTargetDatabase());
      Statement deleteStatement = targetConnection.createStatement();

      try {
        logger.log(Level.DEBUG, deleteSQL);
        int deletedRows = deleteStatement.executeUpdate(deleteSQL);

        logger.log(Level.INFO, Messages.DELETE_COUNT.getMessage(), deletedRows, tableName);
      } finally {
        Resources.close(deleteStatement);
      }

      logger.log(Level.DEBUG, insertSQL);
      PreparedStatement insertStatement = targetConnection.prepareStatement(insertSQL);

      try {
        int statementCount = 0;

        ResultSet resultSet = future.get();

        while (resultSet.next()) {
          int index = 0;

          for (String column : table.getColumns()) {
            index += 1;

            int type = context.getDialect().getColumnType(table.getColumnProperties(column));

            Object object = context.getSynchronizationSupport().getObject(resultSet, index, type);

            if (resultSet.wasNull()) {
              insertStatement.setNull(index, type);
            } else {
              insertStatement.setObject(index, object, type);
            }
          }

          insertStatement.addBatch();
          statementCount += 1;

          if ((statementCount % this.maxBatchSize) == 0) {
            insertStatement.executeBatch();
            insertStatement.clearBatch();
          }

          insertStatement.clearParameters();
        }

        if ((statementCount % this.maxBatchSize) > 0) {
          insertStatement.executeBatch();
        }

        logger.log(Level.INFO, Messages.INSERT_COUNT.getMessage(), statementCount, table);
      } catch (ExecutionException e) {
        throw ExceptionType.getExceptionFactory(SQLException.class).createException(e.getCause());
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new SQLException(e);
      } finally {
        Resources.close(insertStatement);
      }
    } finally {
      Resources.close(selectStatement);
    }
  }