Skip to content

Commit 40b4be3

Browse files
committed
Add transaction support in JobExplorerFactoryBean
This commit adds support to create a transactional proxy around the JobExplorer created by the JobExplorerFactoryBean. Resolves #1307
1 parent 0a71cb7 commit 40b4be3

File tree

20 files changed

+105
-20
lines changed

20 files changed

+105
-20
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchPro
136136
String dataSourceRef = batchAnnotation.dataSourceRef();
137137
beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef);
138138

139+
String transactionManagerRef = batchAnnotation.transactionManagerRef();
140+
beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef);
141+
139142
// set optional properties
140143
String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef();
141144
if (registry.containsBeanDefinition(executionContextSerializerRef)) {

spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ public JobLauncher jobLauncher() throws BatchConfigurationException {
157157
public JobExplorer jobExplorer() throws BatchConfigurationException {
158158
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
159159
jobExplorerFactoryBean.setDataSource(getDataSource());
160+
jobExplorerFactoryBean.setTransactionManager(getTransactionManager());
160161
jobExplorerFactoryBean.setJdbcOperations(getJdbcOperations());
161162
jobExplorerFactoryBean.setCharset(getCharset());
162163
jobExplorerFactoryBean.setTablePrefix(getTablePrefix());

spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,23 @@
1616

1717
package org.springframework.batch.core.explore.support;
1818

19+
import java.util.Properties;
20+
21+
import org.springframework.aop.framework.ProxyFactory;
1922
import org.springframework.batch.core.explore.JobExplorer;
2023
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
2124
import org.springframework.batch.core.repository.dao.JobExecutionDao;
2225
import org.springframework.batch.core.repository.dao.JobInstanceDao;
2326
import org.springframework.batch.core.repository.dao.StepExecutionDao;
2427
import org.springframework.beans.factory.FactoryBean;
28+
import org.springframework.beans.factory.InitializingBean;
29+
import org.springframework.transaction.PlatformTransactionManager;
30+
import org.springframework.transaction.TransactionManager;
31+
import org.springframework.transaction.annotation.Isolation;
32+
import org.springframework.transaction.annotation.Propagation;
33+
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
34+
import org.springframework.transaction.interceptor.TransactionInterceptor;
35+
import org.springframework.util.Assert;
2536

2637
/**
2738
* A {@link FactoryBean} that automates the creation of a {@link SimpleJobExplorer}. It
@@ -32,7 +43,15 @@
3243
* @author Mahmoud Ben Hassine
3344
* @since 2.0
3445
*/
35-
public abstract class AbstractJobExplorerFactoryBean implements FactoryBean<JobExplorer> {
46+
public abstract class AbstractJobExplorerFactoryBean implements FactoryBean<JobExplorer>, InitializingBean {
47+
48+
private static final String TRANSACTION_ISOLATION_LEVEL_PREFIX = "ISOLATION_";
49+
50+
private static final String TRANSACTION_PROPAGATION_PREFIX = "PROPAGATION_";
51+
52+
private PlatformTransactionManager transactionManager;
53+
54+
private ProxyFactory proxyFactory = new ProxyFactory();
3655

3756
/**
3857
* Creates a job instance data access object (DAO).
@@ -62,6 +81,30 @@ public abstract class AbstractJobExplorerFactoryBean implements FactoryBean<JobE
6281
*/
6382
protected abstract ExecutionContextDao createExecutionContextDao() throws Exception;
6483

84+
/**
85+
* Public setter for the {@link PlatformTransactionManager}.
86+
* @param transactionManager the transactionManager to set
87+
* @since 5.0
88+
*/
89+
public void setTransactionManager(PlatformTransactionManager transactionManager) {
90+
this.transactionManager = transactionManager;
91+
}
92+
93+
/**
94+
* The transaction manager used in this factory. Useful to inject into steps and jobs,
95+
* to ensure that they are using the same instance.
96+
* @return the transactionManager
97+
* @since 5.0
98+
*/
99+
public PlatformTransactionManager getTransactionManager() {
100+
return this.transactionManager;
101+
}
102+
103+
@Override
104+
public void afterPropertiesSet() throws Exception {
105+
Assert.notNull(this.transactionManager, "TransactionManager must not be null.");
106+
}
107+
65108
/**
66109
* Returns the type of object to be returned from {@link #getObject()}.
67110
* @return {@code JobExplorer.class}
@@ -77,4 +120,27 @@ public boolean isSingleton() {
77120
return true;
78121
}
79122

123+
@Override
124+
public JobExplorer getObject() throws Exception {
125+
Properties transactionAttributes = new Properties();
126+
String transactionProperties = String.join(",", TRANSACTION_PROPAGATION_PREFIX + Propagation.SUPPORTS,
127+
TRANSACTION_ISOLATION_LEVEL_PREFIX + Isolation.READ_COMMITTED);
128+
transactionAttributes.setProperty("get*", transactionProperties);
129+
transactionAttributes.setProperty("find*", transactionProperties);
130+
NameMatchTransactionAttributeSource transactionAttributeSource = new NameMatchTransactionAttributeSource();
131+
transactionAttributeSource.setProperties(transactionAttributes);
132+
TransactionInterceptor advice = new TransactionInterceptor((TransactionManager) transactionManager,
133+
transactionAttributeSource);
134+
proxyFactory.addAdvice(advice);
135+
proxyFactory.setProxyTargetClass(false);
136+
proxyFactory.addInterface(JobExplorer.class);
137+
proxyFactory.setTarget(getTarget());
138+
return (JobExplorer) proxyFactory.getProxy(getClass().getClassLoader());
139+
}
140+
141+
private JobExplorer getTarget() throws Exception {
142+
return new SimpleJobExplorer(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao(),
143+
createExecutionContextDao());
144+
}
145+
80146
}

spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,8 @@ public void afterPropertiesSet() throws Exception {
147147
if (serializer == null) {
148148
serializer = new Jackson2ExecutionContextStringSerializer();
149149
}
150-
}
151150

152-
private JobExplorer getTarget() throws Exception {
153-
return new SimpleJobExplorer(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao(),
154-
createExecutionContextDao());
151+
super.afterPropertiesSet();
155152
}
156153

157154
@Override
@@ -196,9 +193,4 @@ protected StepExecutionDao createStepExecutionDao() throws Exception {
196193
return dao;
197194
}
198195

199-
@Override
200-
public JobExplorer getObject() throws Exception {
201-
return getTarget();
202-
}
203-
204196
}

spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBeanTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
import org.springframework.jdbc.core.JdbcOperations;
3030
import org.springframework.jdbc.core.JdbcTemplate;
3131
import org.springframework.test.util.ReflectionTestUtils;
32+
import org.springframework.transaction.PlatformTransactionManager;
3233

3334
/**
3435
* @author Dave Syer
3536
* @author Will Schipp
37+
* @author Mahmoud Ben Hassine
3638
*
3739
*/
3840
class JobExplorerFactoryBeanTests {
@@ -46,7 +48,9 @@ void setUp() {
4648

4749
factory = new JobExplorerFactoryBean();
4850
DataSource dataSource = mock(DataSource.class);
51+
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
4952
factory.setDataSource(dataSource);
53+
factory.setTransactionManager(transactionManager);
5054
factory.setTablePrefix(tablePrefix);
5155

5256
}
@@ -78,6 +82,16 @@ void testMissingDataSource() {
7882

7983
}
8084

85+
@Test
86+
void testMissingTransactionManager() {
87+
88+
factory.setTransactionManager(null);
89+
Exception exception = assertThrows(IllegalArgumentException.class, factory::afterPropertiesSet);
90+
String message = exception.getMessage();
91+
assertTrue(message.contains("TransactionManager"), "Wrong message: " + message);
92+
93+
}
94+
8195
@Test
8296
void testCreateExplorer() throws Exception {
8397

spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public JobExplorer jobExplorer() throws Exception {
8686
public JobExplorerFactoryBean jobExplorerFactoryBean() {
8787
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
8888
jobExplorerFactoryBean.setDataSource(dataSource());
89+
jobExplorerFactoryBean.setTransactionManager(transactionManager(dataSource()));
8990
return jobExplorerFactoryBean;
9091
}
9192

spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void setUp() throws Exception {
105105
this.jobRepository = repositoryFactoryBean.getObject();
106106
JobExplorerFactoryBean explorerFactoryBean = new JobExplorerFactoryBean();
107107
explorerFactoryBean.setDataSource(embeddedDatabase);
108+
explorerFactoryBean.setTransactionManager(transactionManager);
108109
explorerFactoryBean.afterPropertiesSet();
109110
this.jobExplorer = explorerFactoryBean.getObject();
110111
job = new SimpleJob();

spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,18 @@ void setUp() throws Exception {
7676
EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder()
7777
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
7878
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").build();
79+
JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase);
7980
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
8081
factory.setDataSource(embeddedDatabase);
81-
factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase));
82+
factory.setTransactionManager(transactionManager);
8283
factory.afterPropertiesSet();
8384
this.jobRepository = factory.getObject();
8485
job.setJobRepository(this.jobRepository);
8586
this.jobExecution = this.jobRepository.createJobExecution("job", new JobParameters());
8687

8788
JobExplorerFactoryBean explorerFactoryBean = new JobExplorerFactoryBean();
8889
explorerFactoryBean.setDataSource(embeddedDatabase);
90+
explorerFactoryBean.setTransactionManager(transactionManager);
8991
explorerFactoryBean.afterPropertiesSet();
9092
this.jobExplorer = explorerFactoryBean.getObject();
9193
}

spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregatorTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@ void init() throws Exception {
5353
EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder()
5454
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
5555
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").build();
56+
JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase);
5657
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
5758
factory.setDataSource(embeddedDatabase);
58-
factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase));
59+
factory.setTransactionManager(transactionManager);
5960
factory.afterPropertiesSet();
6061
JobRepository jobRepository = factory.getObject();
6162
JobExplorerFactoryBean explorerFactoryBean = new JobExplorerFactoryBean();
6263
explorerFactoryBean.setDataSource(embeddedDatabase);
64+
explorerFactoryBean.setTransactionManager(transactionManager);
6365
explorerFactoryBean.afterPropertiesSet();
6466
aggregator.setJobExplorer(explorerFactoryBean.getObject());
6567
jobExecution = jobRepository.createJobExecution("job", new JobParameters());

spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
<bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
3232
<property name="dataSource" ref="dataSource"/>
33+
<property name="transactionManager" ref="transactionManager"/>
3334
</bean>
3435

3536
<bean id="tasklet" class="org.springframework.batch.core.configuration.xml.FailingTasklet"/>

spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/InlineItemHandlerWithStepScopeParserTests-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
<beans:bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
2323
<beans:property name="dataSource" ref="dataSource"/>
24+
<beans:property name="transactionManager" ref="transactionManager"/>
2425
</beans:bean>
2526

2627

spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
<beans:bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
2323
<beans:property name="dataSource" ref="dataSource"/>
24+
<beans:property name="transactionManager" ref="transactionManager"/>
2425
</beans:bean>
2526

2627
<beans:bean id="step1" class="org.springframework.batch.core.step.tasklet.TaskletStep">

spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
<bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
5252
<property name="dataSource" ref="dataSource"/>
53+
<property name="transactionManager" ref="transactionManager"/>
5354
</bean>
5455

5556
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry"/>

spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
<beans:bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
3232
<beans:property name="dataSource" ref="dataSource"/>
33+
<beans:property name="transactionManager" ref="transactionManager"/>
3334
</beans:bean>
3435

3536
<beans:bean id="jobLauncher" class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher"

spring-batch-core/src/test/resources/simple-job-launcher-context.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
<bean id="jobExplorer"
2828
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
29-
p:dataSource-ref="dataSource" />
29+
p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager" />
3030

3131
<bean id="jobRegistry"
3232
class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676

7777
<bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
7878
<property name="dataSource" ref="dataSource"/>
79+
<property name="transactionManager" ref="transactionManager"/>
7980
</bean>
8081

8182
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher">

spring-batch-integration/src/test/resources/simple-job-launcher-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
<bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
2525
<property name="dataSource" ref="dataSource"/>
26+
<property name="transactionManager" ref="transactionManager"/>
2627
</bean>
2728

2829
<bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob" abstract="true">

spring-batch-samples/src/main/resources/adhoc-job-launcher-context.xml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@
3333
<bean id="notificationPublisher" class="org.springframework.batch.sample.jmx.JobExecutionNotificationPublisher" />
3434
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
3535
<bean id="jobOperator" class="org.springframework.batch.core.launch.support.SimpleJobOperator">
36-
<property name="jobExplorer">
37-
<bean class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
38-
<property name="dataSource" ref="dataSource" />
39-
</bean>
40-
</property>
36+
<property name="jobExplorer" ref="jobExplorer"/>
4137
<property name="jobRepository" ref="jobRepository" />
4238
<property name="jobRegistry" ref="jobRegistry" />
4339
<property name="jobLauncher">

spring-batch-samples/src/main/resources/simple-job-launcher-context.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
<bean id="jobExplorer"
2929
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
30-
p:dataSource-ref="dataSource" />
30+
p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/>
3131

3232
<bean id="jobRegistry"
3333
class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

spring-batch-samples/src/main/resources/skipSample-job-launcher-context.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
p:jobExplorer-ref="jobExplorer" p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" />
1919

2020
<bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
21-
p:dataSource-ref="dataSource" />
21+
p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/>
2222

2323
<bean class="org.springframework.batch.core.configuration.support.AutomaticJobRegistrar">
2424
<property name="applicationContextFactories">

0 commit comments

Comments
 (0)