Статьи

Использование Java EE ManagedExecutorService для асинхронного выполнения транзакций

Прошел год с момента публикации спецификации Java EE 7. Теперь, когда вышел Wildfly 8 Final, пришло время познакомиться с новыми возможностями.

Одна вещь, которая отсутствовала с начала дней Java EE — это возможность работать с полноценными потоками Java EE. Java EE 6 уже принесла нам аннотацию @Asynchronous, с помощью которой мы могли выполнять отдельные методы в фоновом режиме, но реальный пул потоков все еще был недоступен. Но все это теперь история, так как Java EE 7 представила ManagedExecutorService:

1
2
@Resource
ManagedExecutorService managedExecutorService;

Как и хорошо известный ExecutorService из Standard Edition, ManagedExecutorService можно использовать для отправки задач, которые выполняются в пуле потоков. Можно выбрать, должны ли представленные задачи реализовывать интерфейс Runnable или Callable.

В отличие от обычного экземпляра SE ExecutorService, ManagedExecutorService предоставляет потоки, которые могут получать доступ, например, к пользовательским транзакциям из JNDI для выполнения транзакций JPA во время их выполнения. Эта особенность сильно отличается от потоков, запускаемых как в среде SE.

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

Теперь, после того, как мы узнали что-то о теории, давайте возьмемся за некоторый код. Сначала мы пишем @Stateless EJB, который получает ManagedExecutorService:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Stateless
public class MyBean {
    @Resource
    ManagedExecutorService managedExecutorService;
    @PersistenceContext
    EntityManager entityManager;
    @Inject
    Instance<MyTask> myTaskInstance;
 
    public void executeAsync() throws ExecutionException, InterruptedException {
        for(int i=0; i<10; i++) {
            MyTask myTask = myTaskInstance.get();
            this.managedExecutorService.submit(myTask);
        }
    }
 
    public List<MyEntity> list() {
        return entityManager.createQuery("select m from MyEntity m", MyEntity.class).getResultList();
    }
}

Задачи, которые мы передадим в ManagedExecutorService, извлекаются из механизма экземпляров CDI. Это позволяет нам использовать возможности CDI в нашем классе MyTask:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MyTask implements Runnable {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyTask.class);
    @PersistenceContext
    EntityManager entityManager;
 
    @Override
    public void run() {
        UserTransaction userTransaction = null;
        try {
            userTransaction = lookup();
            userTransaction.begin();
            MyEntity myEntity = new MyEntity();
            myEntity.setName("name");
            entityManager.persist(myEntity);
            userTransaction.commit();
        } catch (Exception e) {
            try {
                if(userTransaction != null) {
                    userTransaction.rollback();
                }
            } catch (SystemException e1) {
                LOGGER.error("Failed to rollback transaction: "+e1.getMessage());
            }
        }
    }
 
    private UserTransaction lookup() throws NamingException {
        InitialContext ic = new InitialContext();
        return (UserTransaction)ic.lookup("java:comp/UserTransaction");
    }
}

Здесь мы можем добавить EntityManager для сохранения некоторых объектов в нашей базе данных. UserTransaction, который нам нужен для фиксации, должен быть получен из JNDI. Инъекция с использованием аннотации @Resource невозможна в пределах обычного управляемого компонента.

Чтобы обойти UserTransaction, мы, конечно, могли бы вызвать метод другого EJB и использовать транзакцию другого EJB для фиксации изменений в базе данных. В следующем коде показана альтернативная реализация, использующая вставленный EJB для сохранения сущности:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class MyTask implements Runnable {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyTask.class);
    @PersistenceContext
    EntityManager entityManager;
    @Inject
    MyBean myBean;
 
    @Override
    public void run() {
        MyEntity myEntity = new MyEntity();
        myBean.persit(myEntity);
    }
}

Теперь нам нужно только использовать JAX-RS для вызова функциональности через интерфейс REST:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Path("/myResource")
public class MyResource {
    @Inject
    private MyBean myBean;
 
    @Path("list")
    @GET
    @Produces("text/json")
    public List<MyEntity> list() {
        return myBean.list();
    }
 
    @Path("persist")
    @GET
    @Produces("text/html")
    public String persist() throws ExecutionException, InterruptedException {
        myBean.executeAsync();
        return "<html><h1>Successful!</h1></html>";
    }
}

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

Вывод

ManagedExecutorService — отличная функция для интеграции асинхронных функций с использованием всех стандартных функций Java EE, таких как JPA и транзакций, в корпоративные приложения. Я бы сказал, что ожидание того стоило.

  • Пример исходного кода можно найти на github .