Skip to content

Commit b3debff

Browse files
committed
DATAKV-137 - Fix cached query execution.
We now make sure that cached SpelCriteria's use the appropriate EvaluationContext containing parameters from the request instead of using the EvaluationContext of the cached query.
1 parent 6c25f88 commit b3debff

File tree

4 files changed

+173
-6
lines changed

4 files changed

+173
-6
lines changed

src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
*
4545
* @author Christoph Strobl
4646
* @author Oliver Gierke
47+
* @author Mark Paluch
4748
*/
4849
public class KeyValuePartTreeQuery implements RepositoryQuery {
4950

@@ -134,15 +135,17 @@ protected KeyValueQuery<?> prepareQuery(KeyValueQuery<?> instance, Object[] para
134135
ParametersParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(),
135136
parameters);
136137

137-
KeyValueQuery<?> query = new KeyValueQuery(instance.getCritieria());
138+
Object criteria = instance.getCritieria();
138139

139-
if (instance.getCritieria() instanceof SpelExpression) {
140+
if (criteria instanceof SpelCriteria || criteria instanceof SpelExpression) {
140141

142+
SpelExpression spelExpression = getSpelExpression(criteria);
141143
EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(),
142144
parameters);
143-
query = new KeyValueQuery(new SpelCriteria((SpelExpression) instance.getCritieria(), context));
145+
criteria = new SpelCriteria(spelExpression, context);
144146
}
145-
147+
148+
KeyValueQuery<?> query = new KeyValueQuery(criteria);
146149
Pageable pageable = accessor.getPageable();
147150
Sort sort = accessor.getSort();
148151

@@ -153,6 +156,19 @@ protected KeyValueQuery<?> prepareQuery(KeyValueQuery<?> instance, Object[] para
153156
return query;
154157
}
155158

159+
private SpelExpression getSpelExpression(Object criteria) {
160+
161+
if(criteria instanceof SpelExpression){
162+
return (SpelExpression) criteria;
163+
}
164+
165+
if(criteria instanceof SpelCriteria){
166+
return getSpelExpression(((SpelCriteria) criteria).getExpression());
167+
}
168+
169+
throw new IllegalStateException(String.format("Cannot retrieve SpelExpression from %s", criteria));
170+
}
171+
156172
public KeyValueQuery<?> createQuery(ParameterAccessor accessor) {
157173

158174
PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.keyvalue.repository.query;
17+
18+
import static org.hamcrest.core.IsNot.*;
19+
import static org.hamcrest.core.IsSame.*;
20+
import static org.junit.Assert.*;
21+
import static org.mockito.Matchers.any;
22+
import static org.mockito.Mockito.*;
23+
24+
import java.lang.reflect.Method;
25+
import java.util.List;
26+
27+
import org.junit.Before;
28+
import org.junit.Test;
29+
import org.junit.runner.RunWith;
30+
import org.mockito.Mock;
31+
import org.mockito.runners.MockitoJUnitRunner;
32+
import org.springframework.data.keyvalue.Person;
33+
import org.springframework.data.keyvalue.core.KeyValueOperations;
34+
import org.springframework.data.keyvalue.core.SpelCriteria;
35+
import org.springframework.data.projection.ProjectionFactory;
36+
import org.springframework.data.repository.core.RepositoryMetadata;
37+
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
38+
import org.springframework.data.repository.query.QueryMethod;
39+
40+
/**
41+
* Unit tests for {@link CachingKeyValuePartTreeQuery}.
42+
*
43+
* @author Mark Paluch
44+
*/
45+
@RunWith(MockitoJUnitRunner.class)
46+
public class CachingKeyValuePartTreeQueryUnitTests {
47+
48+
@Mock KeyValueOperations kvOpsMock;
49+
@Mock RepositoryMetadata metadataMock;
50+
@Mock ProjectionFactory projectionFactoryMock;
51+
52+
@Before
53+
public void setUp() throws Exception {
54+
55+
when(metadataMock.getDomainType()).thenReturn((Class) Person.class);
56+
when(metadataMock.getReturnedDomainClass(any(Method.class))).thenReturn((Class) Person.class);
57+
}
58+
59+
/**
60+
* @see DATAKV-137
61+
*/
62+
@Test
63+
@SuppressWarnings({ "unchecked", "rawtypes" })
64+
public void cachedSpelExpressionShouldBeReusedWithNewContext() throws NoSuchMethodException, SecurityException {
65+
66+
QueryMethod qm = new QueryMethod(Repo.class.getMethod("findByFirstname", String.class), metadataMock,
67+
projectionFactoryMock);
68+
69+
KeyValuePartTreeQuery query = new CachingKeyValuePartTreeQuery(qm, DefaultEvaluationContextProvider.INSTANCE,
70+
kvOpsMock, SpelQueryCreator.class);
71+
72+
Object[] args = new Object[] { "foo" };
73+
74+
SpelCriteria first = (SpelCriteria) query.prepareQuery(args).getCritieria();
75+
SpelCriteria second = (SpelCriteria) query.prepareQuery(args).getCritieria();
76+
77+
assertThat(first.getExpression(), sameInstance(second.getExpression()));
78+
assertThat(first.getContext(), not(sameInstance(second.getContext())));
79+
}
80+
81+
static interface Repo {
82+
83+
List<Person> findByFirstname(String firstname);
84+
}
85+
}

src/test/java/org/springframework/data/map/AbstractRepositoryUnitTests.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 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.
@@ -43,6 +43,7 @@
4343
* @author Christoph Strobl
4444
* @author Oliver Gierke
4545
* @author Thomas Darimont
46+
* @author Mark Paluch
4647
*/
4748
public abstract class AbstractRepositoryUnitTests<T extends AbstractRepositoryUnitTests.PersonRepository> {
4849

@@ -61,7 +62,7 @@ public abstract class AbstractRepositoryUnitTests<T extends AbstractRepositoryUn
6162
public void setup() {
6263

6364
KeyValueOperations operations = new KeyValueTemplate(new MapKeyValueAdapter());
64-
KeyValueRepositoryFactory keyValueRepositoryFactory = new KeyValueRepositoryFactory(operations);
65+
KeyValueRepositoryFactory keyValueRepositoryFactory = createKeyValueRepositoryFactory(operations);
6566

6667
this.repository = getRepository(keyValueRepositoryFactory);
6768
}
@@ -77,15 +78,29 @@ public void findBy() {
7778
assertThat(repository.findByAge(19), hasItems(CERSEI, JAIME));
7879
}
7980

81+
/**
82+
* @see DATAKV-137
83+
*/
84+
@Test
85+
public void findByFirstname() {
86+
87+
repository.save(LENNISTERS);
88+
89+
assertThat(repository.findByFirstname(CERSEI.getFirstname()), hasItems(CERSEI));
90+
assertThat(repository.findByFirstname(JAIME.getFirstname()), hasItems(JAIME));
91+
}
92+
8093
/**
8194
* @see DATACMNS-525
95+
* @see DATAKV-137
8296
*/
8397
@Test
8498
public void combindedFindUsingAnd() {
8599

86100
repository.save(LENNISTERS);
87101

88102
assertThat(repository.findByFirstnameAndAge(JAIME.getFirstname(), 19), hasItem(JAIME));
103+
assertThat(repository.findByFirstnameAndAge(TYRION.getFirstname(), 17), hasItem(TYRION));
89104
}
90105

91106
/**
@@ -120,13 +135,15 @@ public void findByConnectingOr() {
120135

121136
/**
122137
* @see DATACMNS-525
138+
* @see DATAKV-137
123139
*/
124140
@Test
125141
public void singleEntityExecution() {
126142

127143
repository.save(LENNISTERS);
128144

129145
assertThat(repository.findByAgeAndFirstname(TYRION.getAge(), TYRION.getFirstname()), is(TYRION));
146+
assertThat(repository.findByAgeAndFirstname(CERSEI.getAge(), CERSEI.getFirstname()), is(CERSEI));
130147
}
131148

132149
/**
@@ -183,6 +200,10 @@ public void projectsResultToDynamicInterface() {
183200
assertThat(result.get(0).getFirstname(), is(CERSEI.getFirstname()));
184201
}
185202

203+
protected KeyValueRepositoryFactory createKeyValueRepositoryFactory(KeyValueOperations operations) {
204+
return new KeyValueRepositoryFactory(operations);
205+
}
206+
186207
protected abstract T getRepository(KeyValueRepositoryFactory factory);
187208

188209
public static interface PersonRepository extends CrudRepository<Person, String>, KeyValueRepository<Person, String> {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.map;
17+
18+
import static org.hamcrest.Matchers.hasItems;
19+
import static org.junit.Assert.assertThat;
20+
21+
import org.junit.Test;
22+
import org.springframework.data.keyvalue.core.KeyValueOperations;
23+
import org.springframework.data.keyvalue.repository.query.CachingKeyValuePartTreeQuery;
24+
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
25+
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
26+
import org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository;
27+
import org.springframework.data.map.AbstractRepositoryUnitTests.PersonRepository;
28+
29+
/**
30+
* Unit tests for {@link SimpleKeyValueRepository} using {@link CachingKeyValuePartTreeQuery} and {@link SpelQueryCreator}.
31+
*
32+
* @author Mark Paluch
33+
*/
34+
public class CachingQuerySimpleKeyValueRepositoryUnitTests extends AbstractRepositoryUnitTests<PersonRepository> {
35+
36+
@Override
37+
protected KeyValueRepositoryFactory createKeyValueRepositoryFactory(KeyValueOperations operations) {
38+
return new KeyValueRepositoryFactory(operations, SpelQueryCreator.class, CachingKeyValuePartTreeQuery.class);
39+
}
40+
41+
@Override
42+
protected PersonRepository getRepository(KeyValueRepositoryFactory factory) {
43+
return factory.getRepository(PersonRepository.class);
44+
}
45+
}

0 commit comments

Comments
 (0)