コード例 #1
0
  /**
   * Parses hostPortPair in the form of [host][:port] into an array, with the element of index
   * HOST_NAME_INDEX being the host (or null if not specified), and the element of index
   * PORT_NUMBER_INDEX being the port (or null if not specified).
   *
   * @param hostPortPair host and port in form of of [host][:port]
   * @return array containing host and port as Strings
   * @throws SQLException if a parse error occurs
   */
  protected static String[] parseHostPortPair(String hostPortPair) throws SQLException {
    int portIndex = hostPortPair.indexOf(":"); // $NON-NLS-1$

    String[] splitValues = new String[2];

    String hostname = null;

    if (portIndex != -1) {
      if ((portIndex + 1) < hostPortPair.length()) {
        String portAsString = hostPortPair.substring(portIndex + 1);
        hostname = hostPortPair.substring(0, portIndex);

        splitValues[HOST_NAME_INDEX] = hostname;

        splitValues[PORT_NUMBER_INDEX] = portAsString;
      } else {
        throw SQLError.createSQLException(
            Messages.getString("NonRegisteringDriver.37"), // $NON-NLS-1$
            SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
      }
    } else {
      splitValues[HOST_NAME_INDEX] = hostPortPair;
      splitValues[PORT_NUMBER_INDEX] = null;
    }

    return splitValues;
  }
コード例 #2
0
  /**
   * Try to make a database connection to the given URL. The driver should return "null" if it
   * realizes it is the wrong kind of driver to connect to the given URL. This will be common, as
   * when the JDBC driverManager is asked to connect to a given URL, it passes the URL to each
   * loaded driver in turn.
   *
   * <p>The driver should raise an SQLException if it is the right driver to connect to the given
   * URL, but has trouble connecting to the database.
   *
   * <p>The java.util.Properties argument can be used to pass arbitrary string tag/value pairs as
   * connection arguments.
   *
   * <p>My protocol takes the form:
   *
   * <PRE>
   *
   * jdbc:mysql://host:port/database
   *
   * </PRE>
   *
   * @param url the URL of the database to connect to
   * @param info a list of arbitrary tag/value pairs as connection arguments
   * @return a connection to the URL or null if it isnt us
   * @exception SQLException if a database access error occurs
   * @see java.sql.Driver#connect
   */
  public java.sql.Connection connect(String url, Properties info) throws SQLException {
    if (url != null) {
      if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
        return connectLoadBalanced(url, info);
      } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
        return connectReplicationConnection(url, info);
      }
    }

    Properties props = null;

    if ((props = parseURL(url, info)) == null) {
      return null;
    }

    try {
      Connection newConn =
          new com.mysql.jdbc.Connection(host(props), port(props), props, database(props), url);

      return newConn;
    } catch (SQLException sqlEx) {
      // Don't wrap SQLExceptions, throw
      // them un-changed.
      throw sqlEx;
    } catch (Exception ex) {
      throw SQLError.createSQLException(
          Messages.getString("NonRegisteringDriver.17") // $NON-NLS-1$
              + ex.toString()
              + Messages.getString("NonRegisteringDriver.18"), // $NON-NLS-1$
          SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
    }
  }
コード例 #3
0
  /**
   * Removes a host from the host list.
   *
   * @param host The host to be removed.
   * @throws SQLException
   */
  public synchronized void removeHost(String host) throws SQLException {
    if (this.connectionGroup != null) {
      if (this.connectionGroup.getInitialHosts().size() == 1
          && this.connectionGroup.getInitialHosts().contains(host)) {
        throw SQLError.createSQLException("Cannot remove only configured host.", null);
      }

      this.hostToRemove = host;

      if (host.equals(this.currentConnection.getHost())) {
        closeAllConnections();
      } else {
        this.connectionsToHostsMap.remove(this.liveConnections.remove(host));
        Integer idx = this.hostsToListIndexMap.remove(host);
        long[] newResponseTimes = new long[this.responseTimes.length - 1];
        int newIdx = 0;
        for (Iterator<String> i = this.hostList.iterator(); i.hasNext(); newIdx++) {
          String copyHost = i.next();
          if (idx != null && idx < this.responseTimes.length) {
            newResponseTimes[newIdx] = this.responseTimes[idx];
            this.hostsToListIndexMap.put(copyHost, newIdx);
          }
        }
        this.responseTimes = newResponseTimes;
      }
    }
  }
コード例 #4
0
  /**
   * Creates a proxy for java.sql.Connection that routes requests between the given list of
   * host:port and uses the given properties when creating connections.
   *
   * @param hosts
   * @param props
   * @throws SQLException
   */
  LoadBalancingConnectionProxy(List hosts, Properties props) throws SQLException {
    this.hostList = hosts;

    int numHosts = this.hostList.size();

    this.liveConnections = new HashMap(numHosts);
    this.connectionsToHostsMap = new HashMap(numHosts);
    this.responseTimes = new long[numHosts];
    this.hostsToListIndexMap = new HashMap(numHosts);

    for (int i = 0; i < numHosts; i++) {
      this.hostsToListIndexMap.put(this.hostList.get(i), new Integer(i));
    }

    this.localProps = (Properties) props.clone();
    this.localProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY);
    this.localProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY);
    this.localProps.setProperty("useLocalSessionState", "true");

    String strategy = this.localProps.getProperty("loadBalanceStrategy", "random");

    if ("random".equals(strategy)) {
      this.balancer = new RandomBalanceStrategy();
    } else if ("bestResponseTime".equals(strategy)) {
      this.balancer = new BestResponseTimeBalanceStrategy();
    } else {
      throw SQLError.createSQLException(
          Messages.getString("InvalidLoadBalanceStrategy", new Object[] {strategy}),
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
    }

    pickNewConnection();
  }
コード例 #5
0
  public Properties parseURL(String url, Properties defaults) throws java.sql.SQLException {
    Properties urlProps = (defaults != null) ? new Properties(defaults) : new Properties();

    if (url == null) {
      return null;
    }

    if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX)
        && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)
        && !StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)
        && !StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) { // $NON-NLS-1$

      return null;
    }

    int beginningOfSlashes = url.indexOf("//");

    if (StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)) {
      urlProps.setProperty(
          "socketFactory", "com.mysql.management.driverlaunched.ServerLauncherSocketFactory");
    }

    /*
     * Parse parameters after the ? in the URL and remove them from the
     * original URL.
     */
    int index = url.indexOf("?"); // $NON-NLS-1$

    if (index != -1) {
      String paramString = url.substring(index + 1, url.length());
      url = url.substring(0, index);

      StringTokenizer queryParams = new StringTokenizer(paramString, "&"); // $NON-NLS-1$

      while (queryParams.hasMoreTokens()) {
        String parameterValuePair = queryParams.nextToken();

        int indexOfEquals = StringUtils.indexOfIgnoreCase(0, parameterValuePair, "=");

        String parameter = null;
        String value = null;

        if (indexOfEquals != -1) {
          parameter = parameterValuePair.substring(0, indexOfEquals);

          if (indexOfEquals + 1 < parameterValuePair.length()) {
            value = parameterValuePair.substring(indexOfEquals + 1);
          }
        }

        if ((value != null && value.length() > 0)
            && (parameter != null && parameter.length() > 0)) {
          try {
            urlProps.put(parameter, URLDecoder.decode(value, "UTF-8"));
          } catch (UnsupportedEncodingException badEncoding) {
            // punt
            urlProps.put(parameter, URLDecoder.decode(value));
          } catch (NoSuchMethodError nsme) {
            // punt again
            urlProps.put(parameter, URLDecoder.decode(value));
          }
        }
      }
    }

    url = url.substring(beginningOfSlashes + 2);

    String hostStuff = null;

    int slashIndex = url.indexOf("/"); // $NON-NLS-1$

    if (slashIndex != -1) {
      hostStuff = url.substring(0, slashIndex);

      if ((slashIndex + 1) < url.length()) {
        urlProps.put(
            DBNAME_PROPERTY_KEY, //$NON-NLS-1$
            url.substring((slashIndex + 1), url.length()));
      }
    } else {
      hostStuff = url;
    }

    if ((hostStuff != null) && (hostStuff.length() > 0)) {
      urlProps.put(HOST_PROPERTY_KEY, hostStuff); // $NON-NLS-1$
    }

    String propertiesTransformClassName = urlProps.getProperty(PROPERTIES_TRANSFORM_KEY);

    if (propertiesTransformClassName != null) {
      try {
        ConnectionPropertiesTransform propTransformer =
            (ConnectionPropertiesTransform)
                Class.forName(propertiesTransformClassName).newInstance();

        urlProps = propTransformer.transformProperties(urlProps);
      } catch (InstantiationException e) {
        throw SQLError.createSQLException(
            "Unable to create properties transform instance '"
                + propertiesTransformClassName
                + "' due to underlying exception: "
                + e.toString(),
            SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
      } catch (IllegalAccessException e) {
        throw SQLError.createSQLException(
            "Unable to create properties transform instance '"
                + propertiesTransformClassName
                + "' due to underlying exception: "
                + e.toString(),
            SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
      } catch (ClassNotFoundException e) {
        throw SQLError.createSQLException(
            "Unable to create properties transform instance '"
                + propertiesTransformClassName
                + "' due to underlying exception: "
                + e.toString(),
            SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
      }
    }

    if (Util.isColdFusion()
        && urlProps.getProperty("autoConfigureForColdFusion", "true").equalsIgnoreCase("true")) {
      String configs = urlProps.getProperty(USE_CONFIG_PROPERTY_KEY);

      StringBuffer newConfigs = new StringBuffer();

      if (configs != null) {
        newConfigs.append(configs);
        newConfigs.append(",");
      }

      newConfigs.append("coldFusion");

      urlProps.setProperty(USE_CONFIG_PROPERTY_KEY, newConfigs.toString());
    }

    // If we use a config, it actually should get overridden by anything in
    // the URL or passed-in properties

    String configNames = null;

    if (defaults != null) {
      configNames = defaults.getProperty(USE_CONFIG_PROPERTY_KEY);
    }

    if (configNames == null) {
      configNames = urlProps.getProperty(USE_CONFIG_PROPERTY_KEY);
    }

    if (configNames != null) {
      List splitNames = StringUtils.split(configNames, ",", true);

      Properties configProps = new Properties();

      Iterator namesIter = splitNames.iterator();

      while (namesIter.hasNext()) {
        String configName = (String) namesIter.next();

        try {
          InputStream configAsStream =
              getClass().getResourceAsStream("configs/" + configName + ".properties");

          if (configAsStream == null) {
            throw SQLError.createSQLException(
                "Can't find configuration template named '" + configName + "'",
                SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
          }
          configProps.load(configAsStream);
        } catch (IOException ioEx) {
          throw SQLError.createSQLException(
              "Unable to load configuration template '"
                  + configName
                  + "' due to underlying IOException: "
                  + ioEx,
              SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
        }
      }

      Iterator propsIter = urlProps.keySet().iterator();

      while (propsIter.hasNext()) {
        String key = propsIter.next().toString();
        String property = urlProps.getProperty(key);
        configProps.setProperty(key, property);
      }

      urlProps = configProps;
    }

    // Properties passed in should override ones in URL

    if (defaults != null) {
      Iterator propsIter = defaults.keySet().iterator();

      while (propsIter.hasNext()) {
        String key = propsIter.next().toString();
        String property = defaults.getProperty(key);
        urlProps.setProperty(key, property);
      }
    }

    return urlProps;
  }
コード例 #6
0
  private java.sql.Connection connectReplicationConnection(String url, Properties info)
      throws SQLException {
    Properties parsedProps = parseURL(url, info);

    if (parsedProps == null) {
      return null;
    }

    Properties masterProps = (Properties) parsedProps.clone();
    Properties slavesProps = (Properties) parsedProps.clone();

    // Marker used for further testing later on, also when
    // debugging
    slavesProps.setProperty("com.mysql.jdbc.ReplicationConnection.isSlave", "true");

    String hostValues = parsedProps.getProperty(HOST_PROPERTY_KEY);

    if (hostValues != null) {
      StringTokenizer st = new StringTokenizer(hostValues, ",");

      StringBuffer masterHost = new StringBuffer();
      StringBuffer slaveHosts = new StringBuffer();

      if (st.hasMoreTokens()) {
        String[] hostPortPair = parseHostPortPair(st.nextToken());

        if (hostPortPair[HOST_NAME_INDEX] != null) {
          masterHost.append(hostPortPair[HOST_NAME_INDEX]);
        }

        if (hostPortPair[PORT_NUMBER_INDEX] != null) {
          masterHost.append(":");
          masterHost.append(hostPortPair[PORT_NUMBER_INDEX]);
        }
      }

      boolean firstSlaveHost = true;

      while (st.hasMoreTokens()) {
        String[] hostPortPair = parseHostPortPair(st.nextToken());

        if (!firstSlaveHost) {
          slaveHosts.append(",");
        } else {
          firstSlaveHost = false;
        }

        if (hostPortPair[HOST_NAME_INDEX] != null) {
          slaveHosts.append(hostPortPair[HOST_NAME_INDEX]);
        }

        if (hostPortPair[PORT_NUMBER_INDEX] != null) {
          slaveHosts.append(":");
          slaveHosts.append(hostPortPair[PORT_NUMBER_INDEX]);
        }
      }

      if (slaveHosts.length() == 0) {
        throw SQLError.createSQLException(
            "Must specify at least one slave host to connect to for master/slave replication load-balancing functionality",
            SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
      }

      masterProps.setProperty(HOST_PROPERTY_KEY, masterHost.toString());
      slavesProps.setProperty(HOST_PROPERTY_KEY, slaveHosts.toString());
    }

    return new ReplicationConnection(masterProps, slavesProps);
  }
コード例 #7
0
  /**
   * Proxies method invocation on the java.sql.Connection interface, trapping "close", "isClosed"
   * and "commit/rollback" to switch connections for load balancing. This is the continuation of
   * MultiHostConnectionProxy#invoke(Object, Method, Object[]).
   */
  @Override
  public synchronized Object invokeMore(Object proxy, Method method, Object[] args)
      throws Throwable {
    String methodName = method.getName();

    if (this.isClosed
        && !allowedOnClosedConnection(method)
        && method.getExceptionTypes().length > 0) {
      if (this.autoReconnect && !this.closedExplicitly) {
        // try to reconnect first!
        this.currentConnection = null;
        pickNewConnection();
        this.isClosed = false;
        this.closedReason = null;
      } else {
        String reason = "No operations allowed after connection closed.";
        if (this.closedReason != null) {
          reason += ("  " + this.closedReason);
        }
        throw SQLError.createSQLException(
            reason,
            SQLError.SQL_STATE_CONNECTION_NOT_OPEN,
            null /* no access to a interceptor here... */);
      }
    }

    if (!this.inTransaction) {
      this.inTransaction = true;
      this.transactionStartTime = System.nanoTime();
      this.transactionCount++;
    }

    Object result = null;

    try {
      result = method.invoke(this.thisAsConnection, args);

      if (result != null) {
        if (result instanceof com.mysql.jdbc.Statement) {
          ((com.mysql.jdbc.Statement) result).setPingTarget(this);
        }
        result = proxyIfReturnTypeIsJdbcInterface(method.getReturnType(), result);
      }

    } catch (InvocationTargetException e) {
      dealWithInvocationException(e);

    } finally {
      if ("commit".equals(methodName) || "rollback".equals(methodName)) {
        this.inTransaction = false;

        // Update stats
        String host = this.connectionsToHostsMap.get(this.currentConnection);
        // avoid NPE if the connection has already been removed from connectionsToHostsMap in
        // invalidateCurrenctConnection()
        if (host != null) {
          synchronized (this.responseTimes) {
            Integer hostIndex = (this.hostsToListIndexMap.get(host));

            if (hostIndex != null && hostIndex < this.responseTimes.length) {
              this.responseTimes[hostIndex] = System.nanoTime() - this.transactionStartTime;
            }
          }
        }
        pickNewConnection();
      }
    }

    return result;
  }
コード例 #8
0
  /**
   * Creates a proxy for java.sql.Connection that routes requests between the given list of
   * host:port and uses the given properties when creating connections.
   *
   * @param hosts The list of the hosts to load balance.
   * @param props Connection properties from where to get initial settings and to be used in new
   *     connections.
   * @throws SQLException
   */
  private LoadBalancedConnectionProxy(List<String> hosts, Properties props) throws SQLException {
    super();

    String group = props.getProperty("loadBalanceConnectionGroup", null);
    boolean enableJMX = false;
    String enableJMXAsString = props.getProperty("loadBalanceEnableJMX", "false");
    try {
      enableJMX = Boolean.parseBoolean(enableJMXAsString);
    } catch (Exception e) {
      throw SQLError.createSQLException(
          Messages.getString(
              "LoadBalancedConnectionProxy.badValueForLoadBalanceEnableJMX",
              new Object[] {enableJMXAsString}),
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
          null);
    }

    if (group != null) {
      this.connectionGroup = ConnectionGroupManager.getConnectionGroupInstance(group);
      if (enableJMX) {
        ConnectionGroupManager.registerJmx();
      }
      this.connectionGroupProxyID = this.connectionGroup.registerConnectionProxy(this, hosts);
      hosts = new ArrayList<String>(this.connectionGroup.getInitialHosts());
    }

    // hosts specifications may have been reset with settings from a previous connection group
    int numHosts = initializeHostsSpecs(hosts, props);

    this.liveConnections = new HashMap<String, ConnectionImpl>(numHosts);
    this.hostsToListIndexMap = new HashMap<String, Integer>(numHosts);
    for (int i = 0; i < numHosts; i++) {
      this.hostsToListIndexMap.put(this.hostList.get(i), i);
    }
    this.connectionsToHostsMap = new HashMap<ConnectionImpl, String>(numHosts);
    this.responseTimes = new long[numHosts];

    String retriesAllDownAsString = this.localProps.getProperty("retriesAllDown", "120");
    try {
      this.retriesAllDown = Integer.parseInt(retriesAllDownAsString);
    } catch (NumberFormatException nfe) {
      throw SQLError.createSQLException(
          Messages.getString(
              "LoadBalancedConnectionProxy.badValueForRetriesAllDown",
              new Object[] {retriesAllDownAsString}),
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
          null);
    }

    String blacklistTimeoutAsString =
        this.localProps.getProperty(BLACKLIST_TIMEOUT_PROPERTY_KEY, "0");
    try {
      this.globalBlacklistTimeout = Integer.parseInt(blacklistTimeoutAsString);
    } catch (NumberFormatException nfe) {
      throw SQLError.createSQLException(
          Messages.getString(
              "LoadBalancedConnectionProxy.badValueForLoadBalanceBlacklistTimeout",
              new Object[] {retriesAllDownAsString}),
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
          null);
    }

    String strategy = this.localProps.getProperty("loadBalanceStrategy", "random");
    if ("random".equals(strategy)) {
      this.balancer =
          (BalanceStrategy)
              Util.loadExtensions(
                      null,
                      props,
                      "com.mysql.jdbc.RandomBalanceStrategy",
                      "InvalidLoadBalanceStrategy",
                      null)
                  .get(0);
    } else if ("bestResponseTime".equals(strategy)) {
      this.balancer =
          (BalanceStrategy)
              Util.loadExtensions(
                      null,
                      props,
                      "com.mysql.jdbc.BestResponseTimeBalanceStrategy",
                      "InvalidLoadBalanceStrategy",
                      null)
                  .get(0);
    } else {
      this.balancer =
          (BalanceStrategy)
              Util.loadExtensions(null, props, strategy, "InvalidLoadBalanceStrategy", null).get(0);
    }

    String autoCommitSwapThresholdAsString =
        props.getProperty("loadBalanceAutoCommitStatementThreshold", "0");
    try {
      this.autoCommitSwapThreshold = Integer.parseInt(autoCommitSwapThresholdAsString);
    } catch (NumberFormatException nfe) {
      throw SQLError.createSQLException(
          Messages.getString(
              "LoadBalancedConnectionProxy.badValueForLoadBalanceAutoCommitStatementThreshold",
              new Object[] {autoCommitSwapThresholdAsString}),
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
          null);
    }

    String autoCommitSwapRegex = props.getProperty("loadBalanceAutoCommitStatementRegex", "");
    if (!("".equals(autoCommitSwapRegex))) {
      try {
        "".matches(autoCommitSwapRegex);
      } catch (Exception e) {
        throw SQLError.createSQLException(
            Messages.getString(
                "LoadBalancedConnectionProxy.badValueForLoadBalanceAutoCommitStatementRegex",
                new Object[] {autoCommitSwapRegex}),
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
            null);
      }
    }

    if (this.autoCommitSwapThreshold > 0) {
      String statementInterceptors = this.localProps.getProperty("statementInterceptors");
      if (statementInterceptors == null) {
        this.localProps.setProperty(
            "statementInterceptors", "com.mysql.jdbc.LoadBalancedAutoCommitInterceptor");
      } else if (statementInterceptors.length() > 0) {
        this.localProps.setProperty(
            "statementInterceptors",
            statementInterceptors + ",com.mysql.jdbc.LoadBalancedAutoCommitInterceptor");
      }
      props.setProperty(
          "statementInterceptors", this.localProps.getProperty("statementInterceptors"));
    }

    this.balancer.init(null, props);

    String lbExceptionChecker =
        this.localProps.getProperty(
            "loadBalanceExceptionChecker", "com.mysql.jdbc.StandardLoadBalanceExceptionChecker");
    this.exceptionChecker =
        (LoadBalanceExceptionChecker)
            Util.loadExtensions(
                    null, props, lbExceptionChecker, "InvalidLoadBalanceExceptionChecker", null)
                .get(0);

    pickNewConnection();
  }
コード例 #9
0
  /**
   * Proxies method invocation on the java.sql.Connection interface, trapping "close", "isClosed"
   * and "commit/rollback" (to switch connections for load balancing).
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    String methodName = method.getName();

    if ("close".equals(methodName)) {
      synchronized (this.liveConnections) {
        // close all underlying connections
        Iterator allConnections = this.liveConnections.values().iterator();

        while (allConnections.hasNext()) {
          ((Connection) allConnections.next()).close();
        }

        this.liveConnections.clear();
        this.connectionsToHostsMap.clear();
      }

      return null;
    }

    if ("isClosed".equals(methodName)) {
      return Boolean.valueOf(this.isClosed);
    }

    if (this.isClosed) {
      throw SQLError.createSQLException(
          "No operations allowed after connection closed.", SQLError.SQL_STATE_CONNECTION_NOT_OPEN);
    }

    if (!inTransaction) {
      this.inTransaction = true;
      this.transactionStartTime = getLocalTimeBestResolution();
    }

    Object result = null;

    try {
      result = method.invoke(this.currentConn, args);

      if (result != null) {
        if (result instanceof com.mysql.jdbc.Statement) {
          ((com.mysql.jdbc.Statement) result).setPingTarget(this);
        }

        result = proxyIfInterfaceIsJdbc(result, result.getClass());
      }
    } catch (InvocationTargetException e) {
      dealWithInvocationException(e);
    } finally {
      if ("commit".equals(methodName) || "rollback".equals(methodName)) {
        this.inTransaction = false;

        // Update stats
        int hostIndex =
            ((Integer)
                    this.hostsToListIndexMap.get(this.connectionsToHostsMap.get(this.currentConn)))
                .intValue();

        synchronized (this.responseTimes) {
          this.responseTimes[hostIndex] = getLocalTimeBestResolution() - this.transactionStartTime;
        }

        pickNewConnection();
      }
    }

    return result;
  }