четверг, 28 июля 2011 г.

Транзакции, управляемые приложением

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

Транзакции, управляемые контейнером имеют одно ограничение - когда метод запускается, он может быть ассоциирован только с одной транзакцией или без транзакций вовсе. Если требуется иное, имеет смысл использовать транзакции, управляемые бином (bean-managed transactions).

Когда транзакции находятся под управлением приложения, можно использовать JDBC или JTA-транзакции.

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

Для управления транзакциями используются следующие методы: begin, commit и rollback. В JDBC-транзакции метод begin опускается, т.к. Подразумевается, что транзакция стартовала перед началом работы с БД.

Метод stateless session бина должен завершить транзакцию до того, как вернет результат, в то время как stateful session bean может сохранять состояние транзакции между обращениями клиента к методу. Даже если клиент закрывал и заново открывал соединение с БД, транзакция будет существовать до тех пор, пока не будет вызван метод commit или rollback.

Если stateful session bean использует JDBC-транзакцию, то её состояние так же может сохраняться между вызовами пользователя за тем исключением, что если jdbc-соединение будет закрыто - транзакция оборвется.

В транзакциях, управляемых приложением нельзя запускать методы управления транзакциями уровня контейнера getRollbackOnly и setRollbackOnly интерфейса EJBContext. Вместо этого надо использовать методы getStatus и rollback интерфейса UserTransaction.

Пример JDBC-транзакции:
public void ship (String productId, String orderId, int quantity) {
    try {
        con.setAutoCommit(false);
        updateOrderItem(productId, orderId);
        updateInventory(productId, quantity);
        con.commit();
    } catch (Exception ex) {
        try {
            con.rollback();
            throw new EJBException("Transaction failed:" +
            ex.getMessage());
        } catch (SQLException sqx) {
            throw new EJBException("Rollback failed:" +
            sqx.getMessage());
        }
    }
}
Пример JTA-транзакции:
public void withdrawCash(double amount) {
    UserTransaction ut = context.getUserTransaction();
    try {
        ut.begin();
        updateChecking(amount);
        machineBalance -= amount;
        insertMachine(machineBalance);
        ut.commit();
    } catch (Exception ex) {
        try {
            ut.rollback();
        } catch (SystemException syex) {
            throw new EJBException ("Rollback failed:" + syex.getMessage());
        }
        throw new EJBException ("Transaction failed:" + ex.getMessage());
    }
}

Таймаут транзакций под управлением EBJ-контейнера выставляется сервером приложений, а транзакций под управлением приложением - методом setTransactionTimeout интерфейса  UserTransaction. По умолчанию таймаут равен нулю, что значит, что он никогда не наступит.

Транзакции, управляемые контейнером EJB.

Enterprise beans поддерживают два типа управления транцакциями: container-managed или bean-managed. По умолчанию, если явно не задано, то используется первый, с которым я сегодня разбирался и хочу рассказать.

По сути, это просто заметки, сделанные с документации Java EE 6 Tutorial.

Container-managed транзакции упрощают разработку, т.к. за границами транзакции следит контейнер EJB. Транзакции, управляемые контейнером, могут работать как с session, так и с message-driven бинами.

Транзакция автоматически стартует перед началом запуска метода в бине и заканчивается по завершении этого метода. Container-managed транзакции не поддерживают вложенные и мульти-транзакции.

Container-managed транзакция не должна использовать методы управления транзакциями, т. к. эти операции возложены на контейнер EJB.

Существует возможность управления запуском транзакций для ejb-методов. Если внутри транцакции вызывается метод другого бина, то как должна вести себя транзакция с этим методом? На этот метод можно поставить различные ограничители принадлежности к текущей транзакции, при помощи аннотации @TransactionAttribute:
  • REQUIRED - если метод запущен внутри транзакции, то он является частью транзакции. Если запущен вне транзакции, то в нем стартует новая транзакция.
  • REQUIRES_NEW - если метод запускается внутри транзакции, то она останавливается, запускается новая, отрабатывается, а затем продолжает работу первая. Если метод запускается самостоятельно, то просто стартует новая транзакция. Данный модификатор служит для того, что бы быть уверенным, что всегда будет запущена новая транзакция.
  • MANDATORY - если метод запущен внутри транзакции, то он является её частью. Если же запускается вне транзакции, то бросается исключение TransactionRequiredException.
  • NOT_SUPPORTED - если метод запускается внутри транзакции, то на время работы метода транзакция останавливается, пока не отработается метод, травзакция в которм не стартуетс, затем транзакция продолжает работу. Если метод запускается вне транзакции, то транзакция в нем не стартует. Этот атрибут увеличивает производительность, т.к. транзакции связаны с дополнительными нагрузками.
  • SUPPORTS - если внутри транзакции, то является частью транзакции, если запускается самостоятельно, то транзакция не стартует.
  • NEVER - Если внутри транзакции, то бросается исключение RemoteException, если вне транзакции, то метод запускается без старта транзакции.
Аннотация  @TransactionAttribute, может быть установлена на весь класс или на отдельные методы.
Пример:
@TransactionAttribute(NOT_SUPPORTED)
@Stateful
public class TransactionBean implements Transaction {
    ...
    @TransactionAttribute(REQUIRES_NEW)
    public void firstMethod() {...}
    @TransactionAttribute(REQUIRED)
    public void secondMethod() {...}
    public void thirdMethod() {...}
    public void fourthMethod() {...}
}
Если во время работы транзакции бросается системное исключение, то автоматом происходит откат транзакции. Управление откатом при эксепшенах приложения задается в методе setRollbackOnly интерфейса EJBContext, который обрабатывается с REQUIRED, REQUIRES_NEW, или MANDATORY атрибутами транзакции. Если данный метод не будет запущен — транзакция завершится успешно:

public void transferToSaving(double amount) throws
InsufficientBalanceException {
    checkingBalance -= amount;
    savingBalance += amount;
    try {
        updateChecking(checkingBalance);
        if (checkingBalance < 0.00) {
            context.setRollbackOnly();
            throw new InsufficientBalanceException();
        }
        updateSaving(savingBalance);
    } catch (SQLException ex) {
        throw new EJBException
        ("Transaction failed due to SQLException: "
        + ex.getMessage());
    }
}
Session Beans поддерживают не обязательный интерфейс SessionSynchronization, который позволяет автоматически запускать методы на разных этапах выполнения транзакции: afterBegin, beforeCompletion, and afterCompletion. Первый может уведомлять сущность, что транзакция стартовала, второй запускается перед коммитом - последняя возможность сделать роллбек. Третий уведомляет, что транзакция выполнена и имеет только один булевый параметр со значением - true, если транзакция закоммичена или если роллбек - false

в container-managed транзакциях существуют запрещенные методы, т.к. могут пересекаться с границами текущей транзакции:
  • commit, setAutoCommit, и rollback методы класса java.sql.Connection
  • getUserTransaction метод класса javax.ejb.EJBContext
  • любой метод класса javax.transaction.UserTransaction

понедельник, 25 июля 2011 г.

Создавайте объекты только тогда, когда это необходимо

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

public class Messages {
    private List<String> messages;
    public List<String> getMessages () {
        if(messages == null) {
            messages = new ArrayList<String>();
        }
        return messages;
    }
}