Интеграция транзакций Spring
|
Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine |
Интеграция транзакций на примере
Ниже пошагово рассматривается SpringTransactionIntegrationTest из основного codebase. Ниже также приведён Spring-конфигурационный файл, используемый в этом примере. Показанный фрагмент содержит dataSource, transactionManager, processEngine и сервисы процессного движка.
При передаче DataSource в SpringProcessEngineConfiguration через свойство dataSource OpenBPM Engine внутренне использует org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy, который оборачивает переданный DataSource. Это делается для того, чтобы SQL-соединения, получаемые из DataSource, и транзакции Spring корректно работали вместе. Это означает, что вам больше не нужно самостоятельно проксировать dataSource в конфигурации Spring, хотя по-прежнему допустимо передавать TransactionAwareDataSourceProxy в SpringProcessEngineConfiguration. В таком случае дополнительного оборачивания не произойдёт.
Если вы самостоятельно объявляете TransactionAwareDataSourceProxy в конфигурации Spring, убедитесь, что не используете его для ресурсов, которые уже знают о Spring-транзакциях, например DataSourceTransactionManager и JPATransactionManager должны получать не-проксированный dataSource.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:camunda;DB_CLOSE_DELAY=1000" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="processEngineConfiguration" class="io.openbpm.bpm.engine.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="jobExecutorActivate" value="false" />
</bean>
<bean id="processEngine" class="io.openbpm.bpm.engine.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
...
</beans>
|
Атрибуты |
Остальная часть этого Spring-конфигурационного файла содержит бины и настройки, которые используются в данном примере:
<beans>
...
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="userBean" class="io.openbpm.bpm.engine.spring.test.UserBean">
<property name="runtimeService" ref="runtimeService" />
</bean>
<bean id="printer" class="io.openbpm.bpm.engine.spring.test.Printer" />
</beans>
Сначала создаётся application context одним из стандартных способов Spring. В этом примере можно использовать classpath XML resource для настройки Spring application context:
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("mytest/SpringTransactionIntegrationTest-context.xml");
или, поскольку это тест:
@ContextConfiguration("classpath:mytest/SpringTransactionIntegrationTest-context.xml")
После этого можно получить сервисные бины и вызывать их методы. ProcessEngineFactoryBean добавляет к сервисам дополнительный interceptor, который применяет семантику транзакций Propagation.REQUIRED к методам сервисов движка. Например, repositoryService можно использовать для deployment процесса следующим образом:
RepositoryService repositoryService = (RepositoryService) applicationContext.getBean("repositoryService");
String deploymentId = repositoryService
.createDeployment()
.addClasspathResource("mytest/hello.bpmn20")
.addClasspathResource("mytest/hello.png")
.deploy()
.getId();
Обратный вариант тоже работает. В этом случае Spring-транзакция будет оборачивать метод userBean.hello(), а вызов метода сервиса движка присоединится к этой же транзакции.
UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();
UserBean выглядит следующим образом. Напомним, что выше в Spring-конфигурации мы внедрили repositoryService в userBean.
public class UserBean {
// injected by Spring
private RuntimeService runtimeService;
@Transactional
public void hello() {
// here you can do transactional stuff in your domain model
// and it will be combined in the same transaction as
// the startProcessInstanceByKey to the RuntimeService
runtimeService.startProcessInstanceByKey("helloProcess");
}
public void setRuntimeService(RuntimeService runtimeService) {
this.runtimeService = runtimeService;
}
}
Использование вложенных транзакций Spring
Когда вызовы Engine API выполняются в контексте Spring, Spring не делает для процессного движка прозрачным тот факт, что вложенные API-вызовы должны выполняться в отдельной транзакции, то есть с поведением Propagation.REQUIRES_NEW. Это плохо сочетается с тем, как Process Engine Context используется для разделения транзакционных данных, подробно описанных в разделе о транзакциях и Process Engine Context. Из-за этого, если внутренняя транзакция завершится ошибкой, изменения, сделанные вызовами Engine API, всё равно будут зафиксированы в базе данных OpenBPM Engine.
Решение состоит в том, чтобы явно сообщить процессному движку, что необходимо создать новый Process Engine Context, в котором все последующие вызовы Engine API будут сохранять изменения для базы данных. Если внутренняя транзакция завершится ошибкой, изменения в этом новом Process Engine Context будут откатены.
Process Engine Context необходимо объявлять всякий раз, когда внутренняя транзакция Spring с Propagation.REQUIRES_NEW определяется внутри уже выполняющейся транзакции.
Пример
В следующем фрагменте кода показана транзакция Spring с Propagation.REQUIRED, определённая на методе execute, и транзакция Spring с Propagation.REQUIRES_NEW, определённая на методе InnerProcessServiceImpl#startInnerProcess. Метод InnerProcessServiceImpl#startInnerProcess вызывается из метода execute, в результате чего возникает внутренняя транзакция. Можно также предположить, что при вызове Engine API startProcessInstanceByKey внутри внутренней транзакции выполняется пользовательский код, который выбрасывает исключение.
@Transactional(value = "transactionManager", propagation = Propagation.REQUIRED)
public void execute(DelegateExecution execution) throws Exception {
try {
innerProcessService.startInnerProcess(execution);
} catch (Exception ex) {
// noop
}
}
/* InnerProcessService implementation */
@Service
public class InnerProcessServiceImpl implements InnerProcessService {
@Override
@Transactional(value = "transactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = {Throwable.class})
public void startInnerProcess(DelegateExecution execution) {
execution.getProcessEngineServices()
.getRuntimeService()
.startProcessInstanceByKey("EXAMPLE_PROCESS");
// custom code continues
}
}
В подобной ситуации, поскольку Spring не сообщает процессному движку о том, что вызов Engine API startProcessInstanceByKey выполняется в новой внутренней транзакции, при сбое пользовательского кода внутренняя Spring-транзакция будет откатена, но данные о запущенном экземпляре процесса будут зафиксированы вместе с внешней транзакцией.
Описанную проблему можно решить с помощью статических методов класса io.openbpm.bpm.engine.context.ProcessEngineContext. Сначала вызывается ProcessEngineContext.requiresNew(), чтобы объявить необходимость нового контекста. Затем вызывается ProcessEngineContext.clear(), чтобы снять это объявление. Это важно, потому что новый Process Engine Context будет объявляться для каждого последующего вызова Engine API до тех пор, пока не будет вызван метод ProcessEngineContext#clear.
Рекомендуется использовать блок try-finally, чтобы гарантировать вызов и очистку статических методов ProcessEngineContext даже при возникновении исключений. Ниже показано решение, применённое к вызову Engine API startProcessInstanceByKey из примера выше:
try {
// declare new Process Engine Context
ProcessEngineContext.requiresNew();
// call engine APIs
execution.getProcessEngineServices()
.getRuntimeService()
.startProcessInstanceByKey("EXAMPLE_PROCESS");
} finally {
// clear declaration for new Process Engine Context
ProcessEngineContext.clear();
}
Лицензия и атрибуция
Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .
Оригинал документации: https://docs.camunda.org