Code review comment: `currentTimeMillis` may be affected by clock adjustments in the machine, while `nanoTime` measures ellapsed time in the machine, so it will not be affected by that.

Code under review:
private void executeBatch() throws SQLException, IOException, InterruptedException {
        if (records.isEmpty()) {
          return;
        }
        Long startTimeNs = System.nanoTime();
        
        if (connection == null) {
          connection = dataSource.getConnection();
          connection.setAutoCommit(false);
          preparedStatement = connection.prepareStatement(spec.getStatement().get());
        }
        Sleeper sleeper = Sleeper.DEFAULT;
        BackOff backoff = retryBackOff.backoff();
        while (true) {
          try (PreparedStatement preparedStatement =
              connection.prepareStatement(spec.getStatement().get())) {
            try {
              
              for (T record : records) {
                processRecord(record, preparedStatement);
              }
              
              preparedStatement.executeBatch();
              
              connection.commit();
              RECORDS_PER_BATCH.update(records.size());
              MS_PER_BATCH.update((System.nanoTime() - startTimeNs) / 1000);
              break;
            } catch (SQLException exception) {
              if (!spec.getRetryStrategy().apply(exception)) {
                throw exception;
              }
              LOG.warn("Deadlock detected, retrying", exception);
              
              preparedStatement.clearBatch();
              connection.rollback();
              if (!BackOffUtils.next(sleeper, backoff)) {
                
                throw exception;
              }
            }
          }
        }
        records.clear();
      }

Line commented on: Long startTimeNs = System.nanoTime();

Revised code:
private void executeBatch() throws SQLException, IOException, InterruptedException {
        if (records.isEmpty()) {
          return;
        }
        Long startTimeNs = System.nanoTime();
        
        if (connection == null) {
          connection = dataSource.getConnection();
          connection.setAutoCommit(false);
          preparedStatement = connection.prepareStatement(spec.getStatement().get());
        }
        Sleeper sleeper = Sleeper.DEFAULT;
        BackOff backoff = retryBackOff.backoff();
        while (true) {
          try (PreparedStatement preparedStatement =
              connection.prepareStatement(spec.getStatement().get())) {
            try {
              
              for (T record : records) {
                processRecord(record, preparedStatement);
              }
              
              preparedStatement.executeBatch();
              
              connection.commit();
              RECORDS_PER_BATCH.update(records.size());
              MS_PER_BATCH.update(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeNs));
              break;
            } catch (SQLException exception) {
              if (!spec.getRetryStrategy().apply(exception)) {
                throw exception;
              }
              LOG.warn("Deadlock detected, retrying", exception);
              
              preparedStatement.clearBatch();
              connection.rollback();
              if (!BackOffUtils.next(sleeper, backoff)) {
                
                throw exception;
              }
            }
          }
        }
        records.clear();
      }