Skip to content

Commit c2b5dc4

Browse files
committed
Extend query hints support across JPA Item Readers
Enhanced `JpaCursorItemReader`, `JpaCursorItemReaderBuilder`, `JpaPagingItemReader`, and `JpaPagingItemReaderBuilder` with query hints configuration. The inclusion of query hints in both cursor and paging item readers improves query execution strategies, optimizing performance for complex data retrieval scenarios.
1 parent 0d1d89c commit c2b5dc4

File tree

6 files changed

+110
-42
lines changed

6 files changed

+110
-42
lines changed

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaCursorItemReader.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* The implementation is <b>not</b> thread-safe.
4444
*
4545
* @author Mahmoud Ben Hassine
46+
* @author Jinwoo Bae
4647
* @param <T> type of items to read
4748
* @since 4.3
4849
*/
@@ -58,6 +59,8 @@ public class JpaCursorItemReader<T> extends AbstractItemCountingItemStreamItemRe
5859

5960
private Map<String, Object> parameterValues;
6061

62+
private Map<String, Object> hintValues;
63+
6164
private Iterator<T> iterator;
6265

6366
/**
@@ -100,6 +103,16 @@ public void setParameterValues(Map<String, Object> parameterValues) {
100103
this.parameterValues = parameterValues;
101104
}
102105

106+
/**
107+
* Set the query hint values for the JPA query. Query hints can be used to give
108+
* instructions to the JPA provider
109+
* @param hintValues a map where each key is the name of the hint, and the corresponding
110+
* value is the hint value.
111+
*/
112+
public void setHintValues(Map<String, Object> hintValues) {
113+
this.hintValues = hintValues;
114+
}
115+
103116
@Override
104117
public void afterPropertiesSet() throws Exception {
105118
Assert.state(this.entityManagerFactory != null, "EntityManagerFactory is required");
@@ -123,6 +136,10 @@ protected void doOpen() throws Exception {
123136
if (this.parameterValues != null) {
124137
this.parameterValues.forEach(query::setParameter);
125138
}
139+
if (this.hintValues != null) {
140+
this.hintValues.forEach(query::setHint);
141+
}
142+
126143
this.iterator = query.getResultStream().iterator();
127144
}
128145

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaPagingItemReader.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2022 the original author or authors.
2+
* Copyright 2006-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,6 +80,7 @@
8080
* @author Dave Syer
8181
* @author Will Schipp
8282
* @author Mahmoud Ben Hassine
83+
* @author Jinwoo Bae
8384
* @since 2.0
8485
*/
8586
public class JpaPagingItemReader<T> extends AbstractPagingItemReader<T> {
@@ -96,6 +97,8 @@ public class JpaPagingItemReader<T> extends AbstractPagingItemReader<T> {
9697

9798
private Map<String, Object> parameterValues;
9899

100+
private Map<String, Object> hintValues;
101+
99102
private boolean transacted = true;// default value
100103

101104
public JpaPagingItemReader() {
@@ -128,6 +131,17 @@ public void setParameterValues(Map<String, Object> parameterValues) {
128131
this.parameterValues = parameterValues;
129132
}
130133

134+
/**
135+
* Set the query hint values for the JPA query. Query hints can be used to give
136+
* instructions to the JPA provider
137+
* @param hintValues a map where each key is the name of the hint, and the corresponding
138+
* value is the hint's value.
139+
*/
140+
public void setHintValues(Map<String, Object> hintValues) {
141+
this.hintValues = hintValues;
142+
}
143+
144+
131145
/**
132146
* By default (true) the EntityTransaction will be started and committed around the
133147
* read. Can be overridden (false) in cases where the JPA implementation doesn't
@@ -202,6 +216,10 @@ protected void doReadPage() {
202216
}
203217
}
204218

219+
if (this.hintValues != null) {
220+
this.hintValues.forEach(query::setHint);
221+
}
222+
205223
if (results == null) {
206224
results = new CopyOnWriteArrayList<>();
207225
}

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilder.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2021 the original author or authors.
2+
* Copyright 2020-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
* Builder for {@link JpaCursorItemReader}.
3131
*
3232
* @author Mahmoud Ben Hassine
33+
* @author Jinwoo Bae
3334
* @since 4.3
3435
*/
3536
public class JpaCursorItemReaderBuilder<T> {
@@ -42,6 +43,8 @@ public class JpaCursorItemReaderBuilder<T> {
4243

4344
private Map<String, Object> parameterValues;
4445

46+
private Map<String, Object> hintValues;
47+
4548
private boolean saveState = true;
4649

4750
private String name;
@@ -112,6 +115,18 @@ public JpaCursorItemReaderBuilder<T> parameterValues(Map<String, Object> paramet
112115
return this;
113116
}
114117

118+
/**
119+
* A map of hint values to be set on the query. The key of the map is the name of
120+
* the hint to be applied, with the value being the specific setting for that hint.
121+
* @param hintValues map of query hints
122+
* @return this instance for method chaining
123+
* @see JpaCursorItemReader#setHintValues(Map)
124+
*/
125+
public JpaCursorItemReaderBuilder<T> queryHints(Map<String, Object> hintValues) {
126+
this.hintValues = hintValues;
127+
return this;
128+
}
129+
115130
/**
116131
* A query provider. This should be set only if {@link #queryString(String)} have not
117132
* been set.
@@ -169,10 +184,12 @@ public JpaCursorItemReader<T> build() {
169184
reader.setQueryProvider(this.queryProvider);
170185
reader.setQueryString(this.queryString);
171186
reader.setParameterValues(this.parameterValues);
187+
reader.setHintValues(this.hintValues);
172188
reader.setCurrentItemCount(this.currentItemCount);
173189
reader.setMaxItemCount(this.maxItemCount);
174190
reader.setSaveState(this.saveState);
175191
reader.setName(this.name);
192+
176193
return reader;
177194
}
178195

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilder.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
*
2828
* @author Michael Minella
2929
* @author Glenn Renfro
30+
* @author Jinwoo Bae
3031
* @since 4.0
3132
*/
3233

@@ -38,6 +39,8 @@ public class JpaPagingItemReaderBuilder<T> {
3839

3940
private Map<String, Object> parameterValues;
4041

42+
private Map<String, Object> hintValues;
43+
4144
private boolean transacted = true;
4245

4346
private String queryString;
@@ -129,6 +132,18 @@ public JpaPagingItemReaderBuilder<T> parameterValues(Map<String, Object> paramet
129132
return this;
130133
}
131134

135+
/**
136+
* A map of hint values to be set on the query. The key of the map is the name of
137+
* the hint to be applied, with the value being the specific setting for that hint.
138+
* @param hintValues map of query hints
139+
* @return this instance for method chaining
140+
* @see JpaPagingItemReader#setHintValues(Map)
141+
*/
142+
public JpaPagingItemReaderBuilder<T> queryHints(Map<String, Object> hintValues) {
143+
this.hintValues = hintValues;
144+
return this;
145+
}
146+
132147
/**
133148
* A query provider. This should be set only if {@link #queryString(String)} have not
134149
* been set.
@@ -204,6 +219,7 @@ public JpaPagingItemReader<T> build() {
204219
reader.setQueryString(this.queryString);
205220
reader.setPageSize(this.pageSize);
206221
reader.setParameterValues(this.parameterValues);
222+
reader.setHintValues(this.hintValues);
207223
reader.setEntityManagerFactory(this.entityManagerFactory);
208224
reader.setQueryProvider(this.queryProvider);
209225
reader.setTransacted(this.transacted);

spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilderTests.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ void tearDown() {
7474
@Test
7575
void testConfiguration() throws Exception {
7676
JpaCursorItemReader<Foo> reader = new JpaCursorItemReaderBuilder<Foo>().name("fooReader")
77-
.entityManagerFactory(this.entityManagerFactory)
78-
.currentItemCount(2)
79-
.maxItemCount(4)
80-
.queryString("select f from Foo f ")
81-
.build();
77+
.entityManagerFactory(this.entityManagerFactory)
78+
.currentItemCount(2)
79+
.maxItemCount(4)
80+
.queryString("select f from Foo f ")
81+
.build();
8282

8383
reader.afterPropertiesSet();
8484

@@ -107,11 +107,11 @@ void testConfigurationNoSaveState() throws Exception {
107107
parameters.put("value", 2);
108108

109109
JpaCursorItemReader<Foo> reader = new JpaCursorItemReaderBuilder<Foo>().name("fooReader")
110-
.entityManagerFactory(this.entityManagerFactory)
111-
.queryString("select f from Foo f where f.id > :value")
112-
.parameterValues(parameters)
113-
.saveState(false)
114-
.build();
110+
.entityManagerFactory(this.entityManagerFactory)
111+
.queryString("select f from Foo f where f.id > :value")
112+
.parameterValues(parameters)
113+
.saveState(false)
114+
.build();
115115

116116
reader.afterPropertiesSet();
117117

@@ -139,9 +139,9 @@ void testConfigurationNamedQueryProvider() throws Exception {
139139
namedQueryProvider.afterPropertiesSet();
140140

141141
JpaCursorItemReader<Foo> reader = new JpaCursorItemReaderBuilder<Foo>().name("fooReader")
142-
.entityManagerFactory(this.entityManagerFactory)
143-
.queryProvider(namedQueryProvider)
144-
.build();
142+
.entityManagerFactory(this.entityManagerFactory)
143+
.queryProvider(namedQueryProvider)
144+
.build();
145145

146146
reader.afterPropertiesSet();
147147

@@ -173,9 +173,9 @@ void testConfigurationNativeQueryProvider() throws Exception {
173173
provider.afterPropertiesSet();
174174

175175
JpaCursorItemReader<Foo> reader = new JpaCursorItemReaderBuilder<Foo>().name("fooReader")
176-
.entityManagerFactory(this.entityManagerFactory)
177-
.queryProvider(provider)
178-
.build();
176+
.entityManagerFactory(this.entityManagerFactory)
177+
.queryProvider(provider)
178+
.build();
179179

180180
reader.afterPropertiesSet();
181181

@@ -205,7 +205,7 @@ void testValidation() {
205205
assertEquals("A name is required when saveState is set to true", exception.getMessage());
206206

207207
builder = new JpaCursorItemReaderBuilder<Foo>().entityManagerFactory(this.entityManagerFactory)
208-
.saveState(false);
208+
.saveState(false);
209209
exception = assertThrows(IllegalArgumentException.class, builder::build);
210210
assertEquals("Query string is required when queryProvider is null", exception.getMessage());
211211
}
@@ -242,4 +242,4 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
242242

243243
}
244244

245-
}
245+
}

spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilderTests.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ void tearDown() {
7777
@Test
7878
void testConfiguration() throws Exception {
7979
JpaPagingItemReader<Foo> reader = new JpaPagingItemReaderBuilder<Foo>().name("fooReader")
80-
.entityManagerFactory(this.entityManagerFactory)
81-
.currentItemCount(2)
82-
.maxItemCount(4)
83-
.pageSize(5)
84-
.transacted(false)
85-
.queryString("select f from Foo f ")
86-
.build();
80+
.entityManagerFactory(this.entityManagerFactory)
81+
.currentItemCount(2)
82+
.maxItemCount(4)
83+
.pageSize(5)
84+
.transacted(false)
85+
.queryString("select f from Foo f ")
86+
.build();
8787

8888
reader.afterPropertiesSet();
8989

@@ -114,11 +114,11 @@ void testConfigurationNoSaveState() throws Exception {
114114
parameters.put("value", 2);
115115

116116
JpaPagingItemReader<Foo> reader = new JpaPagingItemReaderBuilder<Foo>().name("fooReader")
117-
.entityManagerFactory(this.entityManagerFactory)
118-
.queryString("select f from Foo f where f.id > :value")
119-
.parameterValues(parameters)
120-
.saveState(false)
121-
.build();
117+
.entityManagerFactory(this.entityManagerFactory)
118+
.queryString("select f from Foo f where f.id > :value")
119+
.parameterValues(parameters)
120+
.saveState(false)
121+
.build();
122122

123123
reader.afterPropertiesSet();
124124

@@ -146,9 +146,9 @@ void testConfigurationNamedQueryProvider() throws Exception {
146146
namedQueryProvider.afterPropertiesSet();
147147

148148
JpaPagingItemReader<Foo> reader = new JpaPagingItemReaderBuilder<Foo>().name("fooReader")
149-
.entityManagerFactory(this.entityManagerFactory)
150-
.queryProvider(namedQueryProvider)
151-
.build();
149+
.entityManagerFactory(this.entityManagerFactory)
150+
.queryProvider(namedQueryProvider)
151+
.build();
152152

153153
reader.afterPropertiesSet();
154154

@@ -180,9 +180,9 @@ void testConfigurationNativeQueryProvider() throws Exception {
180180
provider.afterPropertiesSet();
181181

182182
JpaPagingItemReader<Foo> reader = new JpaPagingItemReaderBuilder<Foo>().name("fooReader")
183-
.entityManagerFactory(this.entityManagerFactory)
184-
.queryProvider(provider)
185-
.build();
183+
.entityManagerFactory(this.entityManagerFactory)
184+
.queryProvider(provider)
185+
.build();
186186

187187
reader.afterPropertiesSet();
188188

@@ -204,7 +204,7 @@ void testConfigurationNativeQueryProvider() throws Exception {
204204
@Test
205205
void testValidation() {
206206
var builder = new JpaPagingItemReaderBuilder<Foo>().entityManagerFactory(this.entityManagerFactory)
207-
.pageSize(-2);
207+
.pageSize(-2);
208208
Exception exception = assertThrows(IllegalArgumentException.class, builder::build);
209209
assertEquals("pageSize must be greater than zero", exception.getMessage());
210210

@@ -217,7 +217,7 @@ void testValidation() {
217217
assertEquals("A name is required when saveState is set to true", exception.getMessage());
218218

219219
builder = new JpaPagingItemReaderBuilder<Foo>().entityManagerFactory(this.entityManagerFactory)
220-
.saveState(false);
220+
.saveState(false);
221221
exception = assertThrows(IllegalArgumentException.class, builder::build);
222222
assertEquals("Query string is required when queryProvider is null", exception.getMessage());
223223
}
@@ -254,4 +254,4 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Exce
254254

255255
}
256256

257-
}
257+
}

0 commit comments

Comments
 (0)