-
Notifications
You must be signed in to change notification settings - Fork 327
/
Copy pathdatastore.md
1600 lines (1166 loc) · 58.7 KB
/
datastore.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
## Spring Data Cloud Datastore
<div class="note">
This integration is fully compatible with [Firestore in Datastore
Mode](https://cloud.google.com/datastore/docs/), but not with Firestore
in Native Mode.
</div>
[Spring Data](https://projects.spring.io/spring-data/) is an abstraction
for storing and retrieving POJOs in numerous storage technologies.
Spring Framework on Google Cloud adds Spring Data support for [Google Cloud
Firestore](https://cloud.google.com/firestore/) in Datastore mode.
Maven coordinates for this module only,
using [Spring Framework on Google Cloud BOM](getting-started.xml#bill-of-materials):
``` xml
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-data-datastore</artifactId>
</dependency>
```
Gradle coordinates:
dependencies {
implementation("com.google.cloud:spring-cloud-gcp-data-datastore")
}
We provide a [Spring Boot Starter for Spring Data
Datastore](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore),
with which you can use our recommended auto-configuration setup. To use
the starter, see the coordinates below.
Maven:
``` xml
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
</dependency>
```
Gradle:
dependencies {
implementation("com.google.cloud:spring-cloud-gcp-starter-data-datastore")
}
This setup takes care of bringing in the latest compatible version of
Cloud Java Cloud Datastore libraries as well.
### Configuration
To setup Spring Data Cloud Datastore, you have to configure the
following:
- Setup the connection details to Google Cloud Datastore.
#### Cloud Datastore settings
You can use the [Spring Boot Starter for Spring Data
Datastore](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore)
to autoconfigure Google Cloud Datastore in your Spring application. It
contains all the necessary setup that makes it easy to authenticate with
your Google Cloud project. The following configuration options are
available:
| | | | |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name | Description | Required | Default value |
| `spring.cloud.gcp.datastore.enabled` | Enables the Cloud Datastore client | No | `true` |
| `spring.cloud.gcp.datastore.project-id` | Google Cloud project ID where the Google Cloud Datastore API is hosted, if different from the one in the [Spring Framework on Google Cloud Core Module](#spring-framework-on-google-cloud-core) | No | |
| `spring.cloud.gcp.datastore.credentials.location` | OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the [Spring Framework on Google Cloud Core Module](#spring-framework-on-google-cloud-core) | No | |
| `spring.cloud.gcp.datastore.credentials.encoded-key` | Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the [Spring Framework on Google Cloud Core Module](#spring-framework-on-google-cloud-core) | No | |
| `spring.cloud.gcp.datastore.credentials.scopes` | [OAuth2 scope](https://developers.google.com/identity/protocols/googlescopes) for Spring Framework on Google CloudDatastore credentials | No | <https://www.googleapis.com/auth/datastore> |
| `spring.cloud.gcp.datastore.namespace` | The Cloud Datastore namespace to use | No | the Default namespace of Cloud Datastore in your Google Cloud project |
| `spring.cloud.gcp.datastore.host` | The `hostname:port` of the datastore service or emulator to connect to. Can be used to connect to a manually started [Datastore Emulator](https://cloud.google.com/datastore/docs/tools/datastore-emulator). If the autoconfigured emulator is enabled, this property will be ignored and `localhost:<emulator_port>` will be used. | No | |
| `spring.cloud.gcp.datastore.emulator.enabled` | To enable the auto configuration to start a local instance of the Datastore Emulator. | No | `false` |
| `spring.cloud.gcp.datastore.emulator.port` | The local port to use for the Datastore Emulator | No | `8081` |
| `spring.cloud.gcp.datastore.emulator.consistency` | The [consistency](https://cloud.google.com/sdk/gcloud/reference/beta/emulators/datastore/start?#--consistency) to use for the Datastore Emulator instance | No | `0.9` |
| `spring.cloud.gcp.datastore.emulator.store-on-disk` | Configures whether or not the emulator should persist any data to disk. | No | `true` |
| `spring.cloud.gcp.datastore.emulator.data-dir` | The directory to be used to store/retrieve data/config for an emulator run. | No | The default value is `<USER_CONFIG_DIR>/emulators/datastore`. See the [gcloud documentation](https://cloud.google.com/sdk/gcloud/reference/beta/emulators/datastore/start) for finding your `USER_CONFIG_DIR`. |
#### Repository settings
Spring Data Repositories can be configured via the
`@EnableDatastoreRepositories` annotation on your main `@Configuration`
class. With our Spring Boot Starter for Spring Data Cloud Datastore,
`@EnableDatastoreRepositories` is automatically added. It is not
required to add it to any other class, unless there is a need to
override finer grain configuration parameters provided by
[`@EnableDatastoreRepositories`](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/blob/main/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/config/EnableDatastoreRepositories.java).
#### Autoconfiguration
Our Spring Boot autoconfiguration creates the following beans available
in the Spring application context:
- an instance of `DatastoreTemplate`
- an instance of all user defined repositories extending
`CrudRepository`, `PagingAndSortingRepository`, and
`DatastoreRepository` (an extension of `PagingAndSortingRepository`
with additional Cloud Datastore features) when repositories are
enabled
- an instance of `Datastore` from the Google Cloud Java Client for
Datastore, for convenience and lower level API access
#### Datastore Emulator Autoconfiguration
This Spring Boot autoconfiguration can also configure and start a local
Datastore Emulator server if enabled by property.
It is useful for integration testing, but not for production.
When enabled, the `spring.cloud.gcp.datastore.host` property will be
ignored and the Datastore autoconfiguration itself will be forced to
connect to the autoconfigured local emulator instance.
It will create an instance of `LocalDatastoreHelper` as a bean that
stores the `DatastoreOptions` to get the `Datastore` client connection
to the emulator for convenience and lower level API for local access.
The emulator will be properly stopped after the Spring application
context shutdown.
### Object Mapping
Spring Data Cloud Datastore allows you to map domain POJOs to Cloud
Datastore kinds and entities via annotations:
``` java
@Entity(name = "traders")
public class Trader {
@Id
@Field(name = "trader_id")
String traderId;
String firstName;
String lastName;
@Transient
Double temporaryNumber;
}
```
Spring Data Cloud Datastore will ignore any property annotated with
`@Transient`. These properties will not be written to or read from Cloud
Datastore.
#### Constructors
Simple constructors are supported on POJOs. The constructor arguments
can be a subset of the persistent properties. Every constructor argument
needs to have the same name and type as a persistent property on the
entity and the constructor should set the property from the given
argument. Arguments that are not directly set to properties are not
supported.
``` java
@Entity(name = "traders")
public class Trader {
@Id
@Field(name = "trader_id")
String traderId;
String firstName;
String lastName;
@Transient
Double temporaryNumber;
public Trader(String traderId, String firstName) {
this.traderId = traderId;
this.firstName = firstName;
}
}
```
#### Kind
The `@Entity` annotation can provide the name of the Cloud Datastore
kind that stores instances of the annotated class, one per row.
#### Keys
`@Id` identifies the property corresponding to the ID value.
You must annotate one of your POJO’s fields as the ID value, because
every entity in Cloud Datastore requires a single ID value:
``` java
@Entity(name = "trades")
public class Trade {
@Id
@Field(name = "trade_id")
String tradeId;
@Field(name = "trader_id")
String traderId;
String action;
Double price;
Double shares;
String symbol;
}
```
Datastore can automatically allocate integer ID values. If a POJO
instance with a `Long` ID property is written to Cloud Datastore with
`null` as the ID value, then Spring Data Cloud Datastore will obtain a
newly allocated ID value from Cloud Datastore and set that in the POJO
for saving. Because primitive `long` ID properties cannot be `null` and
default to `0`, keys will not be allocated.
#### Fields
All accessible properties on POJOs are automatically recognized as a
Cloud Datastore field. Field naming is generated by the
`PropertyNameFieldNamingStrategy` by default defined on the
`DatastoreMappingContext` bean. The `@Field` annotation optionally
provides a different field name than that of the property.
#### Supported Types
Spring Data Cloud Datastore supports the following types for regular
fields and elements of collections:
| Type | Stored as |
| ----------------------------------- | ----------------------------------------- |
| `com.google.cloud.Timestamp` | com.google.cloud.datastore.TimestampValue |
| `com.google.cloud.datastore.Blob` | com.google.cloud.datastore.BlobValue |
| `com.google.cloud.datastore.LatLng` | com.google.cloud.datastore.LatLngValue |
| `java.lang.Boolean`, `boolean` | com.google.cloud.datastore.BooleanValue |
| `java.lang.Double`, `double` | com.google.cloud.datastore.DoubleValue |
| `java.lang.Long`, `long` | com.google.cloud.datastore.LongValue |
| `java.lang.Integer`, `int` | com.google.cloud.datastore.LongValue |
| `java.lang.String` | com.google.cloud.datastore.StringValue |
| `com.google.cloud.datastore.Entity` | com.google.cloud.datastore.EntityValue |
| `com.google.cloud.datastore.Key` | com.google.cloud.datastore.KeyValue |
| `byte[]` | com.google.cloud.datastore.BlobValue |
| Java `enum` values | com.google.cloud.datastore.StringValue |
In addition, all types that can be converted to the ones listed in the
table by
`org.springframework.core.convert.support.DefaultConversionService` are
supported.
#### Custom types
Custom converters can be used extending the type support for user
defined types.
1. Converters need to implement the
`org.springframework.core.convert.converter.Converter` interface in
both directions.
2. The user defined type needs to be mapped to one of the basic types
supported by Cloud Datastore.
3. An instance of both Converters (read and write) needs to be passed
to the `DatastoreCustomConversions` constructor, which then has to
be made available as a `@Bean` for `DatastoreCustomConversions`.
For example:
We would like to have a field of type `Album` on our `Singer` POJO and
want it to be stored as a string property:
``` java
@Entity
public class Singer {
@Id
String singerId;
String name;
Album album;
}
```
Where Album is a simple class:
``` java
public class Album {
String albumName;
LocalDate date;
}
```
We have to define the two converters:
``` java
// Converter to write custom Album type
static final Converter<Album, String> ALBUM_STRING_CONVERTER =
new Converter<Album, String>() {
@Override
public String convert(Album album) {
return album.getAlbumName() + " " + album.getDate().format(DateTimeFormatter.ISO_DATE);
}
};
// Converters to read custom Album type
static final Converter<String, Album> STRING_ALBUM_CONVERTER =
new Converter<String, Album>() {
@Override
public Album convert(String s) {
String[] parts = s.split(" ");
return new Album(parts[0], LocalDate.parse(parts[parts.length - 1], DateTimeFormatter.ISO_DATE));
}
};
```
That will be configured in our `@Configuration` file:
``` java
@Configuration
public class ConverterConfiguration {
@Bean
public DatastoreCustomConversions datastoreCustomConversions() {
return new DatastoreCustomConversions(
Arrays.asList(
ALBUM_STRING_CONVERTER,
STRING_ALBUM_CONVERTER));
}
}
```
#### Collections and arrays
Arrays and collections (types that implement `java.util.Collection`) of
supported types are supported. They are stored as
`com.google.cloud.datastore.ListValue`. Elements are converted to Cloud
Datastore supported types individually. `byte[]` is an exception, it is
converted to `com.google.cloud.datastore.Blob`.
#### Custom Converter for collections
Users can provide converters from `List<?>` to the custom collection
type. Only read converter is necessary, the Collection API is used on
the write side to convert a collection to the internal list type.
Collection converters need to implement the
`org.springframework.core.convert.converter.Converter` interface.
Example:
Let’s improve the Singer class from the previous example. Instead of a
field of type `Album`, we would like to have a field of type
`Set<Album>`:
``` java
@Entity
public class Singer {
@Id
String singerId;
String name;
Set<Album> albums;
}
```
We have to define a read converter only:
``` java
static final Converter<List<?>, Set<?>> LIST_SET_CONVERTER =
new Converter<List<?>, Set<?>>() {
@Override
public Set<?> convert(List<?> source) {
return Collections.unmodifiableSet(new HashSet<>(source));
}
};
```
And add it to the list of custom converters:
``` java
@Configuration
public class ConverterConfiguration {
@Bean
public DatastoreCustomConversions datastoreCustomConversions() {
return new DatastoreCustomConversions(
Arrays.asList(
LIST_SET_CONVERTER,
ALBUM_STRING_CONVERTER,
STRING_ALBUM_CONVERTER));
}
}
```
#### Inheritance Hierarchies
Java entity types related by inheritance can be stored in the same Kind.
When reading and querying entities using `DatastoreRepository` or
`DatastoreTemplate` with a superclass as the type parameter, you can
receive instances of subclasses if you annotate the superclass and its
subclasses with `DiscriminatorField` and `DiscriminatorValue`:
``` java
@Entity(name = "pets")
@DiscriminatorField(field = "pet_type")
abstract class Pet {
@Id
Long id;
abstract String speak();
}
@DiscriminatorValue("cat")
class Cat extends Pet {
@Override
String speak() {
return "meow";
}
}
@DiscriminatorValue("dog")
class Dog extends Pet {
@Override
String speak() {
return "woof";
}
}
@DiscriminatorValue("pug")
class Pug extends Dog {
@Override
String speak() {
return "woof woof";
}
}
```
Instances of all 3 types are stored in the `pets` Kind. Because a single
Kind is used, all classes in the hierarchy must share the same ID
property and no two instances of any type in the hierarchy can share the
same ID value.
Entity rows in Cloud Datastore store their respective types'
`DiscriminatorValue` in a field specified by the root superclass’s
`DiscriminatorField` (`pet_type` in this case). Reads and queries using
a given type parameter will match each entity with its specific type.
For example, reading a `List<Pet>` will produce a list containing
instances of all 3 types. However, reading a `List<Dog>` will produce a
list containing only `Dog` and `Pug` instances. You can include the
`pet_type` discrimination field in your Java entities, but its type must
be convertible to a collection or array of `String`. Any value set in
the discrimination field will be overwritten upon write to Cloud
Datastore.
### Relationships
There are three ways to represent relationships between entities that
are described in this section:
- Embedded entities stored directly in the field of the containing
entity
- `@Descendant` annotated properties for one-to-many relationships
- `@Reference` annotated properties for general relationships without
hierarchy
- `@LazyReference` similar to `@Reference`, but the entities are
lazy-loaded when the property is accessed. (Note that the keys of
the children are retrieved when the parent entity is loaded.)
#### Embedded Entities
Fields whose types are also annotated with `@Entity` are converted to
`EntityValue` and stored inside the parent entity.
Here is an example of Cloud Datastore entity containing an embedded
entity in JSON:
``` json
{
"name" : "Alexander",
"age" : 47,
"child" : {"name" : "Philip" }
}
```
This corresponds to a simple pair of Java entities:
``` java
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
@Entity("parents")
public class Parent {
@Id
String name;
Child child;
}
@Entity
public class Child {
String name;
}
```
`Child` entities are not stored in their own kind. They are stored in
their entirety in the `child` field of the `parents` kind.
Multiple levels of embedded entities are supported.
<div class="note">
Embedded entities don’t need to have `@Id` field, it is only required
for top level entities.
</div>
Example:
Entities can hold embedded entities that are their own type. We can
store trees in Cloud Datastore using this feature:
``` java
import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
@Entity
public class EmbeddableTreeNode {
@Id
long value;
EmbeddableTreeNode left;
EmbeddableTreeNode right;
Map<String, Long> longValues;
Map<String, List<Timestamp>> listTimestamps;
public EmbeddableTreeNode(long value, EmbeddableTreeNode left, EmbeddableTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
}
```
##### Maps
Maps will be stored as embedded entities where the key values become the
field names in the embedded entity. The value types in these maps can be
any regularly supported property type, and the key values will be
converted to String using the configured converters.
Also, a collection of entities can be embedded; it will be converted to
`ListValue` on write.
Example:
Instead of a binary tree from the previous example, we would like to
store a general tree (each node can have an arbitrary number of
children) in Cloud Datastore. To do that, we need to create a field of
type `List<EmbeddableTreeNode>`:
``` java
import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
import org.springframework.data.annotation.Id;
public class EmbeddableTreeNode {
@Id
long value;
List<EmbeddableTreeNode> children;
Map<String, EmbeddableTreeNode> siblingNodes;
Map<String, Set<EmbeddableTreeNode>> subNodeGroups;
public EmbeddableTreeNode(List<EmbeddableTreeNode> children) {
this.children = children;
}
}
```
Because Maps are stored as entities, they can further hold embedded
entities:
- Singular embedded objects in the value can be stored in the values
of embedded Maps.
- Collections of embedded objects in the value can also be stored as
the values of embedded Maps.
- Maps in the value are further stored as embedded entities with the
same rules applied recursively for their values.
#### Ancestor-Descendant Relationships
Parent-child relationships are supported via the `@Descendants`
annotation.
Unlike embedded children, descendants are fully-formed entities residing
in their own kinds. The parent entity does not have an extra field to
hold the descendant entities. Instead, the relationship is captured in
the descendants' keys, which refer to their parent entities:
``` java
import com.google.cloud.spring.data.datastore.core.mapping.Descendants;
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
@Entity("orders")
public class ShoppingOrder {
@Id
long id;
@Descendants
List<Item> items;
}
@Entity("purchased_item")
public class Item {
@Id
Key purchasedItemKey;
String name;
Timestamp timeAddedToOrder;
}
```
For example, an instance of a GQL key-literal representation for `Item`
would also contain the parent `ShoppingOrder` ID value:
Key(orders, '12345', purchased_item, 'eggs')
The GQL key-literal representation for the parent `ShoppingOrder` would
be:
Key(orders, '12345')
The Cloud Datastore entities exist separately in their own kinds.
The `ShoppingOrder`:
{
"id" : 12345
}
The two items inside that order:
{
"purchasedItemKey" : Key(orders, '12345', purchased_item, 'eggs'),
"name" : "eggs",
"timeAddedToOrder" : "2014-09-27 12:30:00.45-8:00"
}
{
"purchasedItemKey" : Key(orders, '12345', purchased_item, 'sausage'),
"name" : "sausage",
"timeAddedToOrder" : "2014-09-28 11:30:00.45-9:00"
}
The parent-child relationship structure of objects is stored in Cloud
Datastore using Datastore’s [ancestor
relationships](https://cloud.google.com/datastore/docs/concepts/entities#ancestor_paths).
Because the relationships are defined by the Ancestor mechanism, there
is no extra column needed in either the parent or child entity to store
this relationship. The relationship link is part of the descendant
entity’s key value. These relationships can be many levels deep.
Properties holding child entities must be collection-like, but they can
be any of the supported inter-convertible collection-like types that are
supported for regular properties such as `List`, arrays, `Set`, etc…
Child items must have `Key` as their ID type because Cloud Datastore
stores the ancestor relationship link inside the keys of the children.
Reading or saving an entity automatically causes all subsequent levels
of children under that entity to be read or saved, respectively. If a
new child is created and added to a property annotated `@Descendants`
and the key property is left null, then a new key will be allocated for
that child. The ordering of the retrieved children may not be the same
as the ordering in the original property that was saved.
Child entities cannot be moved from the property of one parent to that
of another unless the child’s key property is set to `null` or a value
that contains the new parent as an ancestor. Since Cloud Datastore
entity keys can have multiple parents, it is possible that a child
entity appears in the property of multiple parent entities. Because
entity keys are immutable in Cloud Datastore, to change the key of a
child you must delete the existing one and re-save it with the new key.
#### Key Reference Relationships
General relationships can be stored using the `@Reference` annotation.
``` java
import org.springframework.data.annotation.Reference;
import org.springframework.data.annotation.Id;
@Entity
public class ShoppingOrder {
@Id
long id;
@Reference
List<Item> items;
@Reference
Item specialSingleItem;
}
@Entity
public class Item {
@Id
Key purchasedItemKey;
String name;
Timestamp timeAddedToOrder;
}
```
`@Reference` relationships are between fully-formed entities residing in
their own kinds. The relationship between `ShoppingOrder` and `Item`
entities are stored as a Key field inside `ShoppingOrder`, which are
resolved to the underlying Java entity type by Spring Data Cloud
Datastore:
{
"id" : 12345,
"specialSingleItem" : Key(item, "milk"),
"items" : [ Key(item, "eggs"), Key(item, "sausage") ]
}
Reference properties can either be singular or collection-like. These
properties correspond to actual columns in the entity and Cloud
Datastore Kind that hold the key values of the referenced entities. The
referenced entities are full-fledged entities of other Kinds.
Similar to the `@Descendants` relationships, reading or writing an
entity will recursively read or write all of the referenced entities at
all levels. If referenced entities have `null` ID values, then they will
be saved as new entities and will have ID values allocated by Cloud
Datastore. There are no requirements for relationships between the key
of an entity and the keys that entity holds as references. The order of
collection-like reference properties is not preserved when reading back
from Cloud Datastore.
### Datastore Operations & Template
`DatastoreOperations` and its implementation, `DatastoreTemplate`,
provides the Template pattern familiar to Spring developers.
Using the auto-configuration provided by Spring Boot Starter for
Datastore, your Spring application context will contain a fully
configured `DatastoreTemplate` object that you can autowire in your
application:
``` java
@SpringBootApplication
public class DatastoreTemplateExample {
@Autowired
DatastoreTemplate datastoreTemplate;
public void doSomething() {
this.datastoreTemplate.deleteAll(Trader.class);
//...
Trader t = new Trader();
//...
this.datastoreTemplate.save(t);
//...
List<Trader> traders = datastoreTemplate.findAll(Trader.class);
//...
}
}
```
The Template API provides convenience methods for:
- Write operations (saving and deleting)
- Read-write transactions
#### GQL Query
In addition to retrieving entities by their IDs, you can also submit
queries.
``` java
<T> Iterable<T> query(Query<? extends BaseEntity> query, Class<T> entityClass);
<A, T> Iterable<T> query(Query<A> query, Function<A, T> entityFunc);
Iterable<Key> queryKeys(Query<Key> query);
```
These methods, respectively, allow querying for:
- entities mapped by a given entity class using all the same mapping
and converting features
- arbitrary types produced by a given mapping function
- only the Cloud Datastore keys of the entities found by the query
#### Find by ID(s)
Using `DatastoreTemplate` you can find entities by id. For example:
``` java
Trader trader = this.datastoreTemplate.findById("trader1", Trader.class);
List<Trader> traders = this.datastoreTemplate.findAllById(Arrays.asList("trader1", "trader2"), Trader.class);
List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class);
```
Cloud Datastore uses key-based reads with strong consistency, but
queries with eventual consistency. In the example above the first two
reads utilize keys, while the third is run by using a query based on the
corresponding Kind of `Trader`.
##### Indexes
By default, all fields are indexed. To disable indexing on a particular
field, `@Unindexed` annotation can be used.
Example:
``` java
import com.google.cloud.spring.data.datastore.core.mapping.Unindexed;
public class ExampleItem {
long indexedField;
@Unindexed
long unindexedField;
@Unindexed
List<String> unindexedListField;
}
```
When using queries directly or via Query Methods, Cloud Datastore
requires [composite custom
indexes](https://cloud.google.com/datastore/docs/concepts/indexes) if
the select statement is not `SELECT *` or if there is more than one
filtering condition in the `WHERE` clause.
##### Read with offsets, limits, and sorting
`DatastoreRepository` and custom-defined entity repositories implement
the Spring Data `PagingAndSortingRepository`, which supports offsets and
limits using page numbers and page sizes. Paging and sorting options are
also supported in `DatastoreTemplate` by supplying a
`DatastoreQueryOptions` to `findAll`.
##### Partial read
This feature is not supported yet.
#### Write / Update
The write methods of `DatastoreOperations` accept a POJO and writes all
of its properties to Datastore. The required Datastore kind and entity
metadata is obtained from the given object’s actual type.
If a POJO was retrieved from Datastore and its ID value was changed and
then written or updated, the operation will occur as if against a row
with the new ID value. The entity with the original ID value will not be
affected.
``` java
Trader t = new Trader();
this.datastoreTemplate.save(t);
```
The `save` method behaves as update-or-insert.
In contrast, the `insert` method will fail if an entity already exists.
##### Partial Update
This feature is not supported yet.
#### Transactions
Read and write transactions are provided by `DatastoreOperations` via
the `performTransaction` method:
``` java
@Autowired
DatastoreOperations myDatastoreOperations;
public String doWorkInsideTransaction() {
return myDatastoreOperations.performTransaction(
transactionDatastoreOperations -> {
// Work with transactionDatastoreOperations here.
// It is also a DatastoreOperations object.
return "transaction completed";
}
);
}
```
The `performTransaction` method accepts a `Function` that is provided an
instance of a `DatastoreOperations` object. The final returned value and
type of the function is determined by the user. You can use this object
just as you would a regular `DatastoreOperations` with an exception:
- It cannot perform sub-transactions.
Because of Cloud Datastore’s consistency guarantees, there are
[limitations](https://cloud.google.com/datastore/docs/concepts/transactions#what_can_be_done_in_a_transaction)
to the operations and relationships among entities used inside
transactions.
##### Declarative Transactions with @Transactional Annotation
This feature requires a bean of `DatastoreTransactionManager`, which is
provided when using `spring-cloud-gcp-starter-data-datastore`.
`DatastoreTemplate` and `DatastoreRepository` support running methods
with the `@Transactional`
[annotation](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative)
as transactions. If a method annotated with `@Transactional` calls
another method also annotated, then both methods will work within the
same transaction. `performTransaction` cannot be used in
`@Transactional` annotated methods because Cloud Datastore does not
support transactions within transactions.
#### Read-Write Support for Maps
You can work with Maps of type `Map<String, ?>` instead of with entity
objects by directly reading and writing them to and from Cloud
Datastore.
<div class="note">
This is a different situation than using entity objects that contain Map
properties.
</div>
The map keys are used as field names for a Datastore entity and map
values are converted to Datastore supported types. Only simple types are
supported (i.e. collections are not supported). Converters for custom
value types can be added (see [Custom types](#_custom_types) section).
Example:
``` java
Map<String, Long> map = new HashMap<>();
map.put("field1", 1L);
map.put("field2", 2L);
map.put("field3", 3L);
keyForMap = datastoreTemplate.createKey("kindName", "id");
// write a map
datastoreTemplate.writeMap(keyForMap, map);
// read a map
Map<String, Long> loadedMap = datastoreTemplate.findByIdAsMap(keyForMap, Long.class);
```
### Repositories
[Spring Data
Repositories](https://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#repositories)
are an abstraction that can reduce boilerplate code.
For example:
``` java
public interface TraderRepository extends DatastoreRepository<Trader, String> {
}
```
Spring Data generates a working implementation of the specified
interface, which can be autowired into an application.
The `Trader` type parameter to `DatastoreRepository` refers to the
underlying domain type. The second type parameter, `String` in this
case, refers to the type of the key of the domain type.