Java FunctionalInterface с переменным количеством аргументов

Я хочу написать очень легкий библиотечный метод, который будет принимать любое количество лямбда-функций Java и выполнять их с отключенной автоматической фиксацией. В случае возникновения ошибки произойдет откат. Таким образом, сами лямбда-функции могут иметь код, который, помимо прочего, занимается обновлениями базы данных.

Я просто использовал замыкание функции и передал его коду, который выполняет логику базы данных, чтобы сделать ее транзакционной. Ищу способ обобщить это, поэтому все, что мне нужно сделать, это просто передать набор лямбда-функций и ожидать, что любые обновления базы данных в коде будут транзакционными.

Есть ли какие-нибудь советы о том, как подойти к этому, или правильно ли я думаю о проблеме?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

То, что вы делаете, уже существует — см., например, JDBI.

Общая идея хорошая. Некоторые ключевые вещи, о которых вам следует позаботиться в ваших лямбда-выражениях:

  • Убедитесь, что БД не просто отключена автоматически, она также должна находиться на уровне изоляции сериализуемых транзакций.
  • Вам нужно будет посмотреть, как каждый механизм БД сообщает об исключении повторной попытки. Вам придется вызвать .getSQLState() для исключения SQLException (которое вы должны перехватить в своем методе, принимающем лямбда-выражение) и разобраться в нем. Если это так, просто... повторно запускайте лямбду, пока она не перестанет ее выдавать.
  • Посмотрите алгоритм Нэгла. Каждый раз при повторном запуске вам придется ждать все большее количество случайного времени (это очень важно, вы должны ввести источник случайности), пока повторная попытка не произойдет.

Это все немного сложно. Вот почему... вам следует просто использовать JDBI.

Базовая настройка

Давайте сначала напишем очень простой взгляд на вашу идею, чтобы мы знали, о чем говорим:

// Given, from your library:
@FunctionalInterface
public interface DbRunner {
  public void proc(Connection con) throws SQLException;
}

public class DbAccess {
  private final Connection con = ...;
  // con is in SERIALIZABLE, non-auto commit mode.

  public void q(DbRunner runner) throws SQLException {
    int attempts = 0;
    while (true) {
      try {
        runner.proc(con);
        con.commit();
        break;
      } catch (SQLException e) {
        if (!isRetryException(e)) throw e;
        if (attempts > 50) throw new SQLException("Too many retries", e);
        long wait = rnd.nextInt(20 * attempts);
        Thread.sleep(wait);
        attempts++;
      }
    }
  }
}

// Note: This is an extremely oversimplified bare bones impl of your idea!

Несколько ламб

Для этого нет причин. Потому что вы можете легко создать одну лямбду, которая будет кратна. Для этого мы можем предоставить DbRunner служебный метод — вы можете добавлять default методы, сохраняя при этом функциональный интерфейс:

@FunctionalInterface
public interface DbRunner {
  public void proc(Connection con) throws SQLException;

  public default DbRunner andThen(DbRunner next) throws SQLxception {
    return con -> {
      proc(con);
      next.proc(con);
    };
  }
}

Тогда вы можете просто сделать новый dbrunner из двух бегунов:

DbRunner taskA = ...;
DbRunner taskB = ...;
DbRunner doBoth = taskA.andThen(taskB);

Переменные аргументы

Это... не работает и не имеет смысла. Какие бы параметры ни нужны вашему dbrunner, они должны будут поступать откуда-то еще — ваша структура базы данных не знает, как их предоставить. Обратите внимание, что любые переменные, доступные «снаружи», могут использоваться «внутри» (кроме неэффективно окончательных локальных переменных). Например:

void markUserLogin(String username) throws SQLException {
  dbAccess.q(db -> {
    db.executeStatement("UPDATE users SET lastLogin = NOW() WHERE username = ? ", username);
  });
}

Обратите внимание, что параметр username доступен «внутри». Нет необходимости заставлять функцию делать это.

Но я действительно хочу принять лямбда-выражения с переменными аргументами.

Вы не можете. Не так, как работает Java.

Примечание о JDBI или JOOQ

Инструкция UPDATE в последнем фрагменте на самом деле не соответствует тому, как работает JDBC; вам нужно гораздо больше кода. Но это может быть так просто: если вы уже пишете программу повторной попытки, вы, вероятно, в конечном итоге добавите и ее (в этом случае ваша лямбда вообще не получит объект java.sql.Connection, она получит некоторый 'Db ' представляющий объект из вашей библиотеки, который содержит соединение, но имеет множество гораздо более приятных методов для безопасного выполнения операций с БД. За исключением... того, что тоже уже существует - JDBI или JOOQ почти все это делают: Runners for, среди прочего, цели повторной попытки и гораздо более удобный API.

Другие вопросы по теме