Подтвердить что ты не робот

Как не повторять код в блоках catch?

Мне сложно не повторять себя в Java-программе, над которой я сейчас работаю.

Скажем, мне нужно объявить множество методов, которые в основном структурированы следующим образом:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
    EntityManager em = this.createEntityManager();

    EntityTransaction tx = null;
    try {

        /*
         * ... independent logic ...
         */

        tx = em.getTransaction();
    } catch (RuntimeException e) {
        if (tx != null && tx.isActive()) { 
            tx.rollback();
        }
        throw e;
    } finally {
        em.close();
    }

    return something;
}

Тело метода всех методов должно содержать тезисы элементов управления ресурсами.

Сама "независимая логика" будет довольно сложной, поэтому постановка инструкции try/catch отдельным методом не будет работать.

Я хочу избежать повторения этого кода. Каковы наилучшие методы применения в этих ситуациях?

4b9b3361

Ответ 1

Создайте интерфейс:

public interface EntityManagerAction {
   public void execute(EntityManager em);
}

И класс утилиты:

public class EntityUtil {
  public static void executeWithEntityManager(EntityManagerAction action) {
    EntityManager em = someHowCreateEntityManager();

    EntityTransaction tx = null;
    try {
        action.execute(em);
        tx = em.getTransaction();
    } catch (RuntimeException e) {
        if (tx != null && tx.isActive()) { 
            tx.rollback();
        }
        throw e;
    } finally {
        em.close();
    }
  }
}

Теперь вы можете повторно использовать плиту котла в классе EntityUtil, и ваш код будет выглядеть следующим образом:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
   Something something; 
   EntityUtil.executeWithEntityManager(new EntityManagerAction() {
        public void execute(EntityManager em ) {

        /*
         * ... independent logic ...
         */
         //use the passed in 'em' here.
        }
    });

    return something;
}

См. также Что такое "Выполнить круг" " идиома?

Ответ 2

Если все ваши предложения finally используются для закрытия Stream и таких (что-либо, что реализует AutoCloseable), вы можете использовать try-with-resources (как предложено в одном из комментариев), чтобы избавиться от finally.

Однако, если вам нужно более общее решение и иметь один и тот же тип Exception и один и тот же тип обработки в предложении finally, вы можете создать абстрактный класс, например:

abstract class SensitiveBlockHandler {
    public void handle() {
        try {
            doHandling();
        } catch (SomeException | AnotherException e) {
            // TODO: handle exceptions here ...
        } finally {
            // TODO: cleanup here ...
        }
    }

    protected abstract void doHandling();
}

Затем вы можете создавать внутренние классы для обработки различных ситуаций, либо как анонимные классы, либо нет. Код должен выглядеть примерно так:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
    new SensitiveBlockHandler() {
        protected void doHandling() {
            /*
             * ... independent logic ...
             */
        }
    }.handle();

    return something;
}

Ответ 3

Я бы создал абстракцию независимой логики, скажем Job, а doSomething() станет processJob() в классе Service. Вы будете называть ваш processJob() для каждой обработки, и весь код из вашего примера, кроме independent logic, будет написан ровно один раз.

Изменить:. Что такое предложение, предлагаемое в комментарии: Что такое "Выполнить Around" , идиома?

Ответ 4

Недавно мы столкнулись с такой проблемой и решили пойти с шаблоном callback.

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

public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
       return (SomeEntity)callbackMethod(someAttribute, anotherAttribute, new IndependentCodeInterfaceImpl1());
}

public SomeOtherEntity doSomethingElse (String someAttribute, String anotherAttribute) {
    return (SomeOtherEntity)callbackMethod(someAttribute, anotherAttribute, new IndependentCodeInterfaceImpl2());
}

private Object callbackMethod(String someAttribute, String anotherAttribute, IndependentCodeInterface independent) {
    EntityManager em = this.createEntityManager();
    EntityTransaction tx = null;
    Object response = null;
    try {
        response = independent.execute(someAttribute, anotherAttribute, em);
        tx = em.getTransaction();
    } catch (RuntimeException e) {
        if (tx != null && tx.isActive()) { 
            tx.rollback();
        }
        throw e;
    } finally {
        em.close();
    }
    return response;
}

Ответ 5

Если вы используете сервер приложений JavaEE, значительно упростите свой код, используя сеанс без состояния bean:

@Stateless
public class SomethingFactory {

    @PersistenceContext
    private EntityManager em;

    public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
       /*
        * ... independent logic ...
        */
       return something;
    }

}

Контейнер будет следить за всей семантикой управления транзакциями.

Ответ 6

вы можете сделать, чтобы ваши сигнатуры метода возвращали исключение

 public SomeEntity doSomething (String someAttribute, String anotherAttribute) throws RuntimeException {
// your independent logic
}

 public SomeEntity doSomethingElse (String someAttribute, String anotherAttribute) throws RuntimeException {
// your independent logic
}

 public SomeEntity doSomethingDifferent (String someAttribute, String anotherAttribute) throws RuntimeException {
// your independent logic
}

то вы можете обрабатывать его по отдельному методу:

   public String yourHandleMethod(){
String something = "";
EntityManager em = this.createEntityManager();

    EntityTransaction tx = null;
try{
 doSomething();
 doSomethingElse();
 doSomethingDifferent();
 tx = em.getTransaction();
    } catch (RuntimeException e) {
        if (tx != null && tx.isActive()) { 
            tx.rollback();
        }
        throw e;
    } finally {
        em.close();
    }

    return something;

Ответ 7

Вы можете реализовать механизм, похожий на Spring Шаблон транзакции.

Сначала реализуем обратный вызов, который будет реализован для каждой бизнес-операции, предоставляя как диспетчер сущностей, так и транзакцию:

public static interface TransactionCallback<R> {
  R doInTransaction(EntityManager em, EntityTransaction tx);
}

Затем создайте общий метод с шаблоном кода:

public <T> T execute(TransactionCallback<T> callback) {
  EntityManager em = this.createEntityManager();
  EntityTransaction tx = null;

  try {
    tx = em.getTransaction();
    return callback.doInTransaction(em, tx);
  } catch (RuntimeException e) {
    if (tx != null && tx.isActive()) { 
        tx.rollback();
    }
    throw e;
  } finally {
    em.close();
  }
}

Наконец, вы можете создать бизнес-логику, подобную этой:

public SomeEntity doSomething(String someAttribute, String anotherAttribute) {
  return execute(new TransactionCallback<SomeEntity>() {
    @Override
    public SomeEntity  doInTransaction(EntityManager em, EntityTransaction tx) {
      // do something here
    }
  });
}

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

Прошу прощения за любые синтаксические ошибки, я пишу это без IDE. Но у вас есть идея.

EDIT: возможно, он может быть короче, используя lambdas в JDK 8, потому что TransactionCallback может быть функциональным интерфейсом:).

Ответ 8

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

Я использую этот шаблон для, помимо прочего, запуска запросов к базе данных из-за всех правил о том, чтобы закрыть все ResultSet и Connection при правильном управлении PreparedStatement s.

abstract class DoSomethingWithEntity {

    public SomeEntity doSomething(String someAttribute, String anotherAttribute) {
        SomeEntity something;
        EntityManager em = createEntityManager();

        EntityTransaction tx = null;
        try {
            tx = em.getTransaction();
            // Call the abstract stuff that can be different.
            something = doIt(em, tx);
        } catch (RuntimeException e) {
            if (tx != null && tx.isActive()) {
                tx.rollback();
            }
            throw e;
        } finally {
            em.close();
        }

        return something;
    }

    // The bit you want to write yourself. Make it abstract so you have to write it.
    abstract SomeEntity doIt(EntityManager em, EntityTransaction tx) throws Exception;
}

public void test() {
    SomeEntity e = new DoSomethingWithEntity() {

        @Override
        SomeEntity doIt(EntityManager em, EntityTransaction tx) {
            // Do your stuff here.
            return new SomeEntity();
        }

    }.doSomething("someAttribute", "anotherAttribute");
}

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

Ответ 9

FYI - новая функция лямбда-выражений в JDK 1.8 теперь является самым элегантным способом решения этой проблемы. Ваш метод DoSomething будет примерно таким:

DataExecutor.execute(() -> {
    // let say your "independent logic" is the following three statements
    statementA;
    statementB;
    statementC;
});

Ваш метод DataExecutor.execute будет выглядеть примерно так (вам может понадобиться тип возврата):

public static void execute(work)
{
    EntityManager em = this.createEntityManager();

    EntityTransaction tx = null;
    try {

        // this is the "independent logic" you passed in via the "work" lambda
        work.doWork();

        tx = em.getTransaction();
    } catch (RuntimeException e) {
        if (tx != null && tx.isActive()) { 
            tx.rollback();
        }
        throw e;
    } finally {
        em.close();
    }

И, наконец, вам нужно определить функциональный интерфейс (или использовать существующий):

@FunctionalInterface
public interface DataFuncVoid {
    public abstract void doWork();
}