Skip to content
This repository was archived by the owner on Jan 19, 2022. It is now read-only.

Commit d9ae0b3

Browse files
authored
Datastore reference query by example (backport) (#2614)
fixes #2611
1 parent f85169b commit d9ae0b3

File tree

5 files changed

+220
-8
lines changed

5 files changed

+220
-8
lines changed

spring-cloud-gcp-data-datastore/src/main/java/org/springframework/cloud/gcp/data/datastore/core/DatastoreTemplate.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@
8686
import org.springframework.data.mapping.AssociationHandler;
8787
import org.springframework.data.mapping.PersistentProperty;
8888
import org.springframework.data.mapping.PersistentPropertyAccessor;
89-
import org.springframework.data.support.ExampleMatcherAccessor;
9089
import org.springframework.data.util.ClassTypeInformation;
9190
import org.springframework.lang.Nullable;
9291
import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -818,19 +817,30 @@ private <T> StructuredQuery exampleToQuery(Example<T> example, DatastoreQueryOpt
818817
DatastorePersistentEntity<?> persistentEntity =
819818
this.datastoreMappingContext.getPersistentEntity(example.getProbeType());
820819

821-
StructuredQuery.Builder builder = keyQuery ? Query.newKeyQueryBuilder() : Query.newEntityQueryBuilder();
822-
builder.setKind(persistentEntity.kindName());
823-
824-
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
825-
matcherAccessor.getPropertySpecifiers();
826820
LinkedList<StructuredQuery.Filter> filters = new LinkedList<>();
821+
NullHandler nullHandler = example.getMatcher().getNullHandler();
827822
persistentEntity.doWithColumnBackedProperties((persistentProperty) -> {
828823
if (!ignoredProperty(example, persistentProperty)) {
829824
Value<?> value = getValue(example, probeEntity, persistentEntity, persistentProperty);
830-
NullHandler nullHandler = example.getMatcher().getNullHandler();
831825
addFilter(nullHandler, filters, persistentProperty.getFieldName(), value);
832826
}
833827
});
828+
persistentEntity.doWithAssociations((AssociationHandler<DatastorePersistentProperty>) association -> {
829+
PersistentPropertyAccessor<?> accessor = persistentEntity.getPropertyAccessor(example.getProbe());
830+
DatastorePersistentProperty property = association.getInverse();
831+
Object value = accessor.getProperty(property);
832+
Value<?> key = value == null
833+
? NullValue.of()
834+
: KeyValue.of(objectToKeyFactory.getKeyFromObject(value,
835+
this.datastoreMappingContext.getPersistentEntity(value.getClass())));
836+
addFilter(nullHandler, filters, property.getFieldName(), key);
837+
});
838+
839+
StructuredQuery.Builder<?> builder =
840+
keyQuery
841+
? Query.newKeyQueryBuilder()
842+
: Query.newEntityQueryBuilder();
843+
builder.setKind(persistentEntity.kindName());
834844

835845
if (!filters.isEmpty()) {
836846
builder.setFilter(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2017-2019 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+
* https://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+
17+
package org.springframework.cloud.gcp.data.datastore.entities;
18+
19+
import com.google.cloud.datastore.Key;
20+
21+
import org.springframework.data.annotation.Id;
22+
import org.springframework.data.annotation.Reference;
23+
24+
public class Product {
25+
@Id
26+
private Key id;
27+
28+
@Reference
29+
private Store store;
30+
31+
public Product(Store store) {
32+
this.store = store;
33+
}
34+
35+
@Override
36+
public String toString() {
37+
return "Product{" +
38+
"id=" + id +
39+
", store=" + store +
40+
'}';
41+
}
42+
43+
@Override
44+
public boolean equals(Object o) {
45+
if (this == o) {
46+
return true;
47+
}
48+
if (!(o instanceof Product)) {
49+
return false;
50+
}
51+
52+
Product product = (Product) o;
53+
54+
if (id != null ? !id.equals(product.id) : product.id != null) {
55+
return false;
56+
}
57+
return store != null ? store.equals(product.store) : product.store == null;
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
int result = id != null ? id.hashCode() : 0;
63+
result = 31 * result + (store != null ? store.hashCode() : 0);
64+
return result;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2017-2019 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+
* https://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+
17+
package org.springframework.cloud.gcp.data.datastore.entities;
18+
19+
import com.google.cloud.datastore.Key;
20+
21+
import org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
22+
import org.springframework.data.annotation.Id;
23+
24+
@Entity(name = "store")
25+
public class Store {
26+
@Id
27+
private Key id;
28+
29+
private String name;
30+
31+
public Store(String name) {
32+
this.name = name;
33+
}
34+
35+
@Override
36+
public String toString() {
37+
return "Store{" +
38+
"id=" + id +
39+
", name='" + name + '\'' +
40+
'}';
41+
}
42+
43+
@Override
44+
public boolean equals(Object o) {
45+
if (this == o) {
46+
return true;
47+
}
48+
if (!(o instanceof Store)) {
49+
return false;
50+
}
51+
52+
Store store = (Store) o;
53+
54+
if (id != null ? !id.equals(store.id) : store.id != null) {
55+
return false;
56+
}
57+
return name != null ? name.equals(store.name) : store.name == null;
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
int result = id != null ? id.hashCode() : 0;
63+
result = 31 * result + (name != null ? name.hashCode() : 0);
64+
return result;
65+
}
66+
}

spring-cloud-gcp-data-datastore/src/test/java/org/springframework/cloud/gcp/data/datastore/it/DatastoreIntegrationTests.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@
5656
import org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
5757
import org.springframework.cloud.gcp.data.datastore.core.mapping.Unindexed;
5858
import org.springframework.cloud.gcp.data.datastore.entities.CustomMap;
59+
import org.springframework.cloud.gcp.data.datastore.entities.Product;
5960
import org.springframework.cloud.gcp.data.datastore.entities.ServiceConfiguration;
61+
import org.springframework.cloud.gcp.data.datastore.entities.Store;
6062
import org.springframework.cloud.gcp.data.datastore.it.TestEntity.Shape;
6163
import org.springframework.cloud.gcp.data.datastore.repository.DatastoreRepository;
6264
import org.springframework.cloud.gcp.data.datastore.repository.query.Query;
@@ -67,6 +69,7 @@
6769
import org.springframework.data.domain.ExampleMatcher;
6870
import org.springframework.data.domain.Page;
6971
import org.springframework.data.domain.PageRequest;
72+
import org.springframework.data.domain.Pageable;
7073
import org.springframework.data.domain.Slice;
7174
import org.springframework.data.domain.Sort;
7275
import org.springframework.test.context.ContextConfiguration;
@@ -97,6 +100,9 @@ public class DatastoreIntegrationTests extends AbstractDatastoreIntegrationTests
97100
// consistency.
98101
private static final int WAIT_FOR_EVENTUAL_CONSISTENCY_SAFETY_MULTIPLE = 3;
99102

103+
@Autowired
104+
private ProductRepository productRepository;
105+
100106
@Autowired
101107
private TestEntityRepository testEntityRepository;
102108

@@ -162,6 +168,8 @@ public void deleteAll() {
162168
this.datastoreTemplate.deleteAll(PetOwner.class);
163169
this.datastoreTemplate.deleteAll(Event.class);
164170
this.datastoreTemplate.deleteAll(LazyEntity.class);
171+
this.datastoreTemplate.deleteAll(Product.class);
172+
this.datastoreTemplate.deleteAll(Store.class);
165173
this.testEntityRepository.deleteAll();
166174
if (this.keyForMap != null) {
167175
this.datastore.delete(this.keyForMap);
@@ -177,6 +185,40 @@ public void saveEntities() {
177185

178186
}
179187

188+
@Test
189+
public void testFindByExampleReference() {
190+
Store store1 = new Store("store1");
191+
Product product1 = new Product(store1);
192+
193+
productRepository.save(product1);
194+
195+
Store store2 = new Store("store2");
196+
Product product2 = new Product(store2);
197+
198+
productRepository.save(product2);
199+
200+
Pageable pageable = PageRequest.of(0, 3);
201+
Product product = new Product(store1);
202+
Example<Product> example = Example.of(product);
203+
Page<Product> pagedProduct = this.productRepository.findAll(example, pageable);
204+
205+
assertThat(pagedProduct).containsOnly(product1);
206+
207+
product = new Product(null);
208+
example = Example.of(product);
209+
pagedProduct = this.productRepository.findAll(example, pageable);
210+
211+
assertThat(pagedProduct).containsExactlyInAnyOrder(product1, product2);
212+
213+
product = new Product(null);
214+
example = Example.of(product, ExampleMatcher.matching()
215+
.withIgnorePaths("id")
216+
.withIncludeNullValues());
217+
pagedProduct = this.productRepository.findAll(example, pageable);
218+
219+
assertThat(pagedProduct).isEmpty();
220+
}
221+
180222
@Test
181223
public void testFindByExample() {
182224
assertThat(this.testEntityRepository
@@ -216,7 +258,7 @@ public void testFindByExample() {
216258
assertThat(this.testEntityRepository
217259
.findAll(
218260
Example.of(new TestEntity(null, null, null, null, null)),
219-
Sort.by(Sort.Direction.ASC, "size")))
261+
Sort.by(Sort.Direction.ASC, "id")))
220262
.containsExactly(this.testEntityA, this.testEntityB, this.testEntityC, this.testEntityD);
221263

222264
assertThat(this.testEntityRepository
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2017-2019 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+
* https://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+
17+
package org.springframework.cloud.gcp.data.datastore.it;
18+
19+
import com.google.cloud.datastore.Key;
20+
21+
import org.springframework.cloud.gcp.data.datastore.entities.Product;
22+
import org.springframework.cloud.gcp.data.datastore.repository.DatastoreRepository;
23+
import org.springframework.stereotype.Repository;
24+
25+
@Repository
26+
public interface ProductRepository extends DatastoreRepository<Product, Key> {
27+
28+
}

0 commit comments

Comments
 (0)