Статьи

Управление транзакциями: EJB3 против Spring

Управление транзакциями является предметом, который обычно остается на усмотрение старшего разработчика (или архитектора). Учитывая сообщения некоторых участников сообщества JavaEE о том, что с более новыми версиями JavaEE вам больше не нужен Spring, меня заинтересовала некоторая проверка фактов о том, как обрабатывается управление транзакциями в обеих технологиях. Примечание: эти сообщения были отправлены полтора года назад и побудили меня написать эту статью .

Демаркация транзакции

Обратите внимание, что хотя обе технологии обеспечивают программную разграничение транзакций (запуск транзакции, а затем принятие / откат), мы сосредоточимся на декларативном разграничении, поскольку их проще использовать в реальной жизни. В EJB3 транзакции ограничиваются аннотацией @TransactionAttribute. Аннотация может быть установлена ​​для класса, и в этом случае каждый метод будет иметь транзакционный атрибут или для каждого метода. Аннотация на уровне метода может переопределить аннотацию на уровне класса. Аннотации автоматически обнаруживаются сервером приложений, совместимым с JavaEE. Весной прежняя аннотация заменяется собственной аннотацией @Transactional. Поведение точно такое же (аннотация на уровне метода / класса и возможность переопределения). Аннотации можно найти только в том случае, если файл определения bean-компонента Spring содержит http: // www.springframework.org/schema/tx namespace, а также следующий фрагмент:

<tx:annotation-driven />

Альтернативно, обе технологии предоставляют ортогональный способ установки разграничения транзакций: в EJB3 можно использовать дескриптор развертывания EJB JAR (ejb-jar.xml), а в Spring подойдет любой файл конфигурации Spring.

Распространение транзакций

В EJB3 существует ровно 6 возможных значений распространения: ОБЯЗАТЕЛЬНЫЕ, ТРЕБУЕМЫЕ (по умолчанию), REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED и НИКОГДА. Spring добавляет поддержку NESTED (см. Ниже).

Вложенные транзакции

Вложенные транзакции — это транзакции, которые запускаются, затем фиксируются / откатываются во время выполнения корневой транзакции. Вложенные результаты транзакции ограничены только областью действия этой транзакции (она не влияет на зонтичную транзакцию). Вложенные транзакции не допускаются в EJB3; они весной.

Только для чтения транзакция

Транзакции только для чтения лучше всего использовать с определенными базами данных или средами ORM, такими как Hibernate. В последнем случае Hibernate оптимизирует сеансы так, что они никогда не сбрасываются ( т.е. никогда не переносят изменения из кэша в базовую базу данных). Я не нашел способа (пока) квалифицировать транзакцию как доступную только для чтения в EJB3 (помощь приветствуется). Весной, @Transactional имеет параметр readOnly для реализации транзакций только для чтения:

@Transactional(readOnly = true)

Локальные вызовы методов

В локальных вызовах методов метод бина A () вызывает другой метод B () в том же экземпляре. Ожидаемое поведение должно заключаться в том, что атрибуты транзакции метода B () принимаются во внимание. Это не относится ни к одной из технологий, но оба предлагают некоторый обходной путь. В EJB3 вы должны внедрить экземпляр того же класса и вызвать метод для этого экземпляра, чтобы использовать атрибуты транзакции. Например:

@Stateless
public MyServiceBean implements MyServiceLocal {

    @EJB
    private MyServiceLocal service;

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void A() {

        service.B();
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void B() {

        ...
    }
}

В Spring по умолчанию управление транзакциями осуществляется через чистые Java-прокси. Если вы хотите иметь управление транзакциями в локальных вызовах методов, вам нужно включить AspectJ. Это легко сделать в файле определения bean-компонентов Spring (но остерегайтесь побочных эффектов, более подробную информацию смотрите в документации Spring ):

<tx:annotation-driven mode="aspectj" />

Обработка исключений и откат

Обработка исключений — это область с большими различиями между Spring и EJB3. В EJB3 только исключения времени выполнения, вызванные методом, откатывают транзакцию, ограниченную этим методом по умолчанию. Чтобы имитировать это поведение для проверенных исключений, необходимо аннотировать класс исключений с помощью @ApplicationException (rollback = true). Аналогично, если вы хотите отказаться от этого поведения для исключений времени выполнения, аннотируйте свой класс исключений с помощью @ApplicationException (rollback = false). Это имеет недостаток, заключающийся в невозможности использовать один и тот же класс исключений для отката в методе и для фиксации, несмотря на исключение в другом методе. Для этого вам необходимо программно управлять транзакцией:

@Stateless
public MyServiceBean implements MyServiceLocal {

    @Resource
    private SessionContext context;

    public void A() {

        try {

            ...

        } catch (MyException e) {

            context.setRollbackOnly();
        }
    }
}

In Spring, runtime exceptions also cause transaction rollback. In order to change this behavior, use the rollbackFor or noRollbackFor attributes of @Transactional:

public MyServiceImpl {

    @Resource
    private SessionContext context;

	@Transactional(rollbackFor = MyException.class)
    public void A() {

		...
    }
}

Conclusion

There’s no denying that JavaEE has made giant steps in the right direction with its 6th version. And yet, small details keep pointing me toward Spring. If you know only about one — Spring or JavaEE 6, I encourage you to try «the other» and see for yourself which one you’re more comfortable with. To go further: