|
1 | | -[[type-safe-property-references]] |
2 | | -= Type-safe Property References |
| 1 | +[[property-paths]] |
| 2 | += Property Paths |
3 | 3 |
|
4 | | -Type-safe property references address a common source of friction in data access code: The reliance on literal, dot-separated property path strings. |
5 | | -Such stringly-typed references are fragile during refactoring difficult to identify as they often lack context. |
6 | | -Type-safe property paths favor explicitness and compiler validation by deriving property paths from Java method references. |
| 4 | +This chapter covers the concept of property paths. |
| 5 | +Property paths are a form of navigation through domain classes to apply certain aspects in the context of interacting with the model. |
| 6 | +Application code provides property paths to data access components to express intents such as selection of properties within a query, forming predicates, or applying sorting. |
| 7 | +A property path originates from its owning type and can consist of one to many segments. |
7 | 8 |
|
8 | | -A property path is a simple, transportable representation of object navigation. |
9 | | -When expressed as a method-reference the compiler participates in validation and IDEs provide meaningful refactoring support. |
10 | | -The result is code that reads naturally, fails fast on renames, and integrates cleanly with existing query and sorting abstractions, for example: |
| 9 | +[TIP] |
| 10 | +==== |
| 11 | +Following domain-driven design principles the classes that form the backbone of your persistent domain model and that are accessed through Spring Data are called entities. |
| 12 | +An entry point to the object graph is called aggregate root. |
11 | 13 |
|
12 | | -[source,java] |
| 14 | +Understanding how to navigate and reference these properties is essential for working with repositories and query operations. |
| 15 | +==== |
| 16 | + |
| 17 | +[[property-path-overview]] |
| 18 | +== Property Path Overview |
| 19 | + |
| 20 | +Property paths provide a simple, text-based mechanism to navigate domain model properties. |
| 21 | +This section introduces the fundamentals of property path navigation and demonstrates trade-offs between string-based and type-safe approaches. |
| 22 | + |
| 23 | +.Domain model example |
| 24 | +[tabs] |
| 25 | +====== |
| 26 | +Java:: |
| 27 | ++ |
| 28 | +[source,java,role="primary"] |
13 | 29 | ---- |
14 | | -TypedPropertyPath.of(Person::getAddress) |
15 | | - .then(Address::getCity); |
| 30 | +class Person { |
| 31 | + String firstname, lastname; |
| 32 | + int age; |
| 33 | + Address address; |
| 34 | + List<Address> previousAddresses; |
| 35 | + |
| 36 | + String getFirstname() { … }; // other property accessors omitted for brevity |
| 37 | +
|
| 38 | +} |
| 39 | +
|
| 40 | +class Address { |
| 41 | + String city, street; |
| 42 | + |
| 43 | + // accessors omitted for brevity |
| 44 | +
|
| 45 | +} |
16 | 46 | ---- |
17 | 47 |
|
18 | | -The expression above constructs a path equivalent to `address.city` while remaining resilient to refactoring. |
19 | | -Property resolution is performed by inspecting the supplied method references; any mismatch becomes visible at compile time. |
| 48 | +Kotlin:: |
| 49 | ++ |
| 50 | +[source,kotlin,role="secondary"] |
| 51 | +---- |
| 52 | +class Person { |
| 53 | + var firstname: String? = null |
| 54 | + var lastname: String? = null |
| 55 | + var age: Int = 0 |
| 56 | + var address: Address? = null |
| 57 | + var previousAddresses: List<Address> = emptyList() |
| 58 | +} |
| 59 | +
|
| 60 | +class Address { |
| 61 | + var city: String? = null |
| 62 | + var street: String? = null |
| 63 | +} |
| 64 | +---- |
| 65 | +====== |
20 | 66 |
|
21 | | -By comparing a literal-based approach as the following example you can immediately spot the same intent while the mechanism of using strings removes any type context: |
| 67 | +Property paths use dot-notation to express property references throughout Spring Data operations, such as sorting and filtering: |
22 | 68 |
|
23 | | -.Stringly-typed programming |
| 69 | +.Dot-notation property references |
24 | 70 | [source,java] |
25 | 71 | ---- |
26 | | -Sort.by("address.city", "address.street") |
| 72 | +Sort.by("firstname", "address.city") |
27 | 73 | ---- |
28 | 74 |
|
29 | | -You can also use it inline for operations like sorting: |
| 75 | +A property path consists of one or more segments separated by a dot (`.`). |
| 76 | +Methods accepting property paths support single-segment references (top-level properties) and multi-segment navigation unless otherwise indicated. |
30 | 77 |
|
31 | | -.Type-safe Property Path |
32 | | -[source,java] |
| 78 | +Collection and array properties support transparent traversal to their component type, enabling direct reference to nested properties: |
| 79 | + |
| 80 | +---- |
| 81 | +Sort.by("address.city") <1> |
| 82 | +
|
| 83 | +Sort.by("previousAddresses") <2> |
| 84 | +
|
| 85 | +Sort.by("previousAddresses.city") <3> |
| 86 | +---- |
| 87 | + |
| 88 | +<1> Navigate from the top-level `address` property to the `city` field. |
| 89 | +<2> Reference the entire `previousAddresses` collection (supported by certain technologies for collection-based sorting). |
| 90 | +<3> Navigate through the collection to sort by the `city` field of each address. |
| 91 | + |
| 92 | +String-based property paths offer simplicity and can be broadly applied but there are tradeoffs to consider: |
| 93 | + |
| 94 | +* **Flexibility**: Property paths are flexible and can be constructed from constant string, configuration or as result of user input. |
| 95 | +* **Untyped**: String paths do not carry compile-time type information. |
| 96 | +Typed as textual content they do not have a dependency on the underlying domain type. |
| 97 | +* **Refactoring risk**: Renaming domain properties requires often manual updates to string literals; IDEs cannot reliably track these references. |
| 98 | + |
| 99 | +To improve refactoring safety and type consistency, prefer type-safe property references using method references. |
| 100 | +This approach associates property paths with compile-time type information and enables compiler validation and IDE-driven refactoring. |
| 101 | +See <<type-safe-property-references>> for details. |
| 102 | + |
| 103 | +NOTE: For implementation details, refer to <<property-path-internals>> for more information. |
| 104 | + |
| 105 | +[[property-path-internals]] |
| 106 | +=== Property Path Internals |
| 107 | + |
| 108 | +The `org.springframework.data.core` package is the basis for Spring Data's navigation across domain classes. |
| 109 | +The javadoc:org.springframework.data.core.TypeInformation[] inteface provides type introspection capable of resolving the type of a property. javadoc:org.springframework.data.core.PropertyPath[] represents a textual navigation path through a domain class. |
| 110 | + |
| 111 | +Together they provide: |
| 112 | + |
| 113 | +* Generic type resolution and introspection |
| 114 | +* Property path creation and validation |
| 115 | +* Actual type resolution for complex properties such as collections and maps |
| 116 | + |
| 117 | +[[type-safe-property-references]] |
| 118 | +== Type-safe Property-References |
| 119 | + |
| 120 | +Type-safe property-references eliminate a common source of errors in data access code: Brittle, string-based property references. |
| 121 | +This section explains how method references can be used to express refactoring-safe property paths. |
| 122 | + |
| 123 | +While a property path is a simple representation of object navigation, String-based property paths are inherently fragile during refactoring as they can be easily missed with an increasing distance between the property definition and its usage. |
| 124 | +Type-safe alternatives derive property paths from method references, enabling the compiler to validate property names and IDEs to support refactoring operations. |
| 125 | + |
| 126 | +Consider the practical difference: The following examples express the same intent - sorting by `address.city` - but only the type-safe version benefits from compiler validation and IDE support: |
| 127 | + |
| 128 | +[tabs] |
| 129 | +====== |
| 130 | +Java:: |
| 131 | ++ |
| 132 | +[source,java,role="primary"] |
| 133 | +---- |
| 134 | +TypedPropertyPath.of(Person::getAddress) |
| 135 | + .then(Address::getCity); |
| 136 | +---- |
| 137 | +
|
| 138 | +Kotlin:: |
| 139 | ++ |
| 140 | +[source,kotlin,role="secondary"] |
33 | 141 | ---- |
| 142 | +TypedPropertyPath.of<Person, Address>(Person::address) |
| 143 | + .then(Address::city) |
| 144 | +
|
| 145 | +// Kotlin Exension |
| 146 | +KTypedPropertyPath.of(Person::address).then(Address::city) |
| 147 | +---- |
| 148 | +====== |
| 149 | + |
| 150 | +=== Building Type-safe Property Paths |
| 151 | + |
| 152 | +Type-safe property paths compose method references to construct navigation expressions. |
| 153 | +The following examples demonstrate inline usage and composition: |
| 154 | + |
| 155 | +.Type-safe Property Path Construction |
| 156 | +[tabs] |
| 157 | +====== |
| 158 | +Java:: |
| 159 | ++ |
| 160 | +[source,java,role="primary"] |
| 161 | +---- |
| 162 | +// Inline usage with Sort |
34 | 163 | Sort.by(Person::getFirstName, Person::getLastName); |
| 164 | +
|
| 165 | +// Composed navigation |
| 166 | +TypedPropertyPath.of(Person::getAddress) |
| 167 | + .then(Address::getCity); |
| 168 | +---- |
| 169 | +
|
| 170 | +Kotlin:: |
| 171 | ++ |
| 172 | +[source,kotlin,role="secondary"] |
| 173 | +---- |
| 174 | +// Inline usage with Sort |
| 175 | +Sort.by(Person::firstName, Person::lastName); |
| 176 | +
|
| 177 | +// Kotlin extension with composed navigation |
| 178 | +KTypedPropertyPath.of(Person::address) |
| 179 | + .then(Address::city) |
35 | 180 | ---- |
| 181 | +====== |
36 | 182 |
|
37 | | -`TypedPropertyPath` can integrate seamlessly with query abstractions or criteria builders: |
| 183 | +Type-safe property paths integrate seamlessly with query abstractions and criteria builders, enabling declarative query construction without string-based property references: |
38 | 184 |
|
39 | | -.Type-safe Property Path |
| 185 | +.Integration with Criteria API |
40 | 186 | [source,java] |
41 | 187 | ---- |
42 | 188 | Criteria.where(Person::getAddress) |
|
0 commit comments