Skip to content

Commit f5cd7a0

Browse files
authored
Merge pull request #6 from FalkorDB/add-query-and-targetnode-annotations
Implement @query and @TargetNode annotations for Spring Data FalkorDB
2 parents 26a6194 + 75d6100 commit f5cd7a0

File tree

11 files changed

+1122
-4
lines changed

11 files changed

+1122
-4
lines changed

ANNOTATIONS.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Spring Data FalkorDB Annotations
2+
3+
This document describes the annotations available in Spring Data FalkorDB for mapping entities and defining custom queries.
4+
5+
## Query Annotations
6+
7+
### @Query
8+
9+
The `@Query` annotation allows you to define custom Cypher queries for repository methods that cannot be expressed as derived queries.
10+
11+
**Location:** `org.springframework.data.falkordb.repository.query.Query`
12+
13+
#### Basic Usage
14+
15+
```java
16+
public interface UserRepository extends FalkorDBRepository<User, Long> {
17+
18+
@Query("MATCH (u:User) WHERE u.age > $age RETURN u")
19+
List<User> findUsersOlderThan(@Param("age") int age);
20+
}
21+
```
22+
23+
#### Parameter Binding
24+
25+
The `@Query` annotation supports multiple parameter binding techniques:
26+
27+
1. **By parameter name using @Param:**
28+
```java
29+
@Query("MATCH (u:User) WHERE u.name = $name RETURN u")
30+
User findByName(@Param("name") String name);
31+
```
32+
33+
2. **By parameter index:**
34+
```java
35+
@Query("MATCH (u:User) WHERE u.age > $0 RETURN u")
36+
List<User> findUsersOlderThan(int age);
37+
```
38+
39+
3. **By entity property:**
40+
```java
41+
@Query("MATCH (u:User {id: $user.__id__})-[:FOLLOWS]->(f) RETURN f")
42+
List<User> findFollowing(@Param("user") User user);
43+
```
44+
45+
#### Query Types
46+
47+
**Count Queries:**
48+
```java
49+
@Query(value = "MATCH (u:User) WHERE u.age > $age RETURN count(u)", count = true)
50+
Long countUsersOlderThan(@Param("age") int age);
51+
```
52+
53+
**Exists Queries:**
54+
```java
55+
@Query(value = "MATCH (u:User {name: $name}) RETURN count(u) > 0", exists = true)
56+
Boolean existsByName(@Param("name") String name);
57+
```
58+
59+
**Write Operations:**
60+
```java
61+
@Query(value = "MATCH (u:User {id: $id}) SET u.lastLogin = timestamp() RETURN u", write = true)
62+
User updateLastLogin(@Param("id") Long id);
63+
```
64+
65+
#### Complex Queries
66+
67+
```java
68+
@Query("MATCH (m:Movie)-[r:ACTED_IN]-(p:Person {name: $actorName}) " +
69+
"RETURN m, collect(r), collect(p)")
70+
List<Movie> findMoviesByActorName(@Param("actorName") String actorName);
71+
```
72+
73+
## Relationship Mapping Annotations
74+
75+
### @TargetNode
76+
77+
The `@TargetNode` annotation marks a field in a relationship properties class as the target node of the relationship.
78+
79+
**Location:** `org.springframework.data.falkordb.core.schema.TargetNode`
80+
81+
#### Usage with @RelationshipProperties
82+
83+
```java
84+
@RelationshipProperties
85+
public class ActedIn {
86+
87+
@RelationshipId
88+
private Long id;
89+
90+
@TargetNode
91+
private Person actor; // The target node of the relationship
92+
93+
private List<String> roles; // Relationship properties
94+
private Integer year; // Relationship properties
95+
96+
// constructors, getters, setters...
97+
}
98+
```
99+
100+
#### Complete Example
101+
102+
**Entity Classes:**
103+
```java
104+
@Node
105+
public class Movie {
106+
@Id
107+
private String title;
108+
109+
@Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
110+
private List<ActedIn> actors = new ArrayList<>();
111+
112+
// other fields...
113+
}
114+
115+
@Node
116+
public class Person {
117+
@Id @GeneratedValue
118+
private Long id;
119+
120+
private String name;
121+
private Integer born;
122+
123+
// other fields...
124+
}
125+
```
126+
127+
**Relationship Properties:**
128+
```java
129+
@RelationshipProperties
130+
public class ActedIn {
131+
132+
@RelationshipId
133+
private Long id;
134+
135+
@TargetNode
136+
private Person actor;
137+
138+
private List<String> roles;
139+
private Integer year;
140+
}
141+
```
142+
143+
### @RelationshipId
144+
145+
The `@RelationshipId` annotation marks a field as the relationship's internal ID.
146+
147+
**Location:** `org.springframework.data.falkordb.core.schema.RelationshipId`
148+
149+
```java
150+
@RelationshipProperties
151+
public class Friendship {
152+
153+
@RelationshipId
154+
private Long id; // Relationship's internal ID
155+
156+
@TargetNode
157+
private Person friend;
158+
159+
private LocalDate since;
160+
}
161+
```
162+
163+
## Repository Examples
164+
165+
### Complete Repository Interface
166+
167+
```java
168+
public interface MovieRepository extends FalkorDBRepository<Movie, String> {
169+
170+
// Derived query methods
171+
List<Movie> findByReleasedGreaterThan(Integer year);
172+
173+
// Custom queries with different parameter binding styles
174+
@Query("MATCH (m:Movie) WHERE m.released > $year RETURN m")
175+
List<Movie> findMoviesReleasedAfter(@Param("year") Integer year);
176+
177+
@Query("MATCH (m:Movie) WHERE m.title CONTAINS $0 RETURN m")
178+
List<Movie> findMoviesByTitleContaining(String titlePart);
179+
180+
// Complex relationship queries
181+
@Query("MATCH (m:Movie {title: $title})-[r:ACTED_IN]-(p:Person) " +
182+
"RETURN m, collect(r), collect(p)")
183+
Optional<Movie> findMovieWithActors(@Param("title") String title);
184+
185+
// Entity parameter queries
186+
@Query("MATCH (m:Movie {title: $movie.__id__})-[:ACTED_IN]-(p:Person) RETURN p")
187+
List<Person> findActorsInMovie(@Param("movie") Movie movie);
188+
189+
// Count and exists queries
190+
@Query(value = "MATCH (m:Movie) WHERE m.released > $year RETURN count(m)", count = true)
191+
Long countMoviesReleasedAfter(@Param("year") Integer year);
192+
193+
@Query(value = "MATCH (m:Movie {title: $title}) RETURN count(m) > 0", exists = true)
194+
Boolean existsByTitle(@Param("title") String title);
195+
196+
// Write operations
197+
@Query(value = "MATCH (m:Movie {title: $title}) SET m.updated = timestamp() RETURN m",
198+
write = true)
199+
Movie updateMovieTimestamp(@Param("title") String title);
200+
}
201+
```
202+
203+
## Best Practices
204+
205+
### Query Annotation Best Practices
206+
207+
1. **Use meaningful parameter names** with `@Param` for better readability
208+
2. **Mark write operations** with `write = true` for proper transaction handling
209+
3. **Use count/exists flags** for performance optimization on aggregate queries
210+
4. **Avoid N+1 queries** by using `collect()` in Cypher for related data
211+
212+
### Relationship Properties Best Practices
213+
214+
1. **Always use @RelationshipId** for the relationship's internal ID field
215+
2. **Use @TargetNode** to clearly identify the target node field
216+
3. **Keep relationship properties simple** and avoid deep nesting
217+
4. **Consider performance implications** of relationship properties in queries
218+
219+
## Migration from Spring Data Neo4j
220+
221+
These annotations are designed to be compatible with Spring Data Neo4j patterns:
222+
223+
- `@Query` works similarly to Neo4j's `@Query` annotation
224+
- `@TargetNode` replaces Neo4j's `@TargetNode` with the same semantics
225+
- `@RelationshipId` provides the same functionality as Neo4j's `@RelationshipId`
226+
227+
The main differences are:
228+
- Package names use `falkordb` instead of `neo4j`
229+
- FalkorDB-specific optimizations and features
230+
- Integration with FalkorDB's graph query language
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2011-2025 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+
package org.springframework.data.falkordb.core.schema;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Inherited;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.apiguardian.api.API;
26+
27+
/**
28+
* Annotation to mark a field in a {@link RelationshipProperties} annotated class as the
29+
* relationship's internal ID.
30+
* <p>
31+
* This annotation is used to identify the field that holds the internal ID of a
32+
* relationship in FalkorDB. The relationship ID is typically a {@code Long} value that is
33+
* automatically generated by the database and represents the unique identifier of the
34+
* relationship edge.
35+
* <p>
36+
* Example usage: <pre>
37+
* &#64;RelationshipProperties
38+
* public class ActedIn {
39+
*
40+
* &#64;RelationshipId
41+
* private Long id;
42+
*
43+
* &#64;TargetNode
44+
* private Person actor;
45+
*
46+
* private List&lt;String&gt; roles;
47+
* private Integer year;
48+
*
49+
* // constructors, getters, setters...
50+
* }
51+
* </pre>
52+
* <p>
53+
* In the above example, the {@code id} field marked with {@code @RelationshipId} will
54+
* hold the internal ID of the relationship as assigned by FalkorDB.
55+
* <p>
56+
* This annotation is specifically designed for use with {@link RelationshipProperties}
57+
* annotated classes and should not be used on regular node entities. For node entities,
58+
* use {@link Id} or {@link org.springframework.data.annotation.Id} instead.
59+
*
60+
* @author Shahar Biron (FalkorDB adaptation)
61+
* @since 1.0
62+
* @see RelationshipProperties
63+
* @see TargetNode
64+
* @see Id
65+
*/
66+
@Retention(RetentionPolicy.RUNTIME)
67+
@Target(ElementType.FIELD)
68+
@Documented
69+
@Inherited
70+
@API(status = API.Status.STABLE, since = "1.0")
71+
public @interface RelationshipId {
72+
73+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2011-2025 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+
package org.springframework.data.falkordb.core.schema;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Inherited;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.apiguardian.api.API;
26+
27+
/**
28+
* Annotation to mark a field in a {@link RelationshipProperties} annotated class as the
29+
* target node of the relationship.
30+
* <p>
31+
* This annotation is used in conjunction with {@link RelationshipProperties} to define
32+
* relationship entities that have properties. The field annotated with
33+
* {@code @TargetNode} represents the target node of the relationship from the perspective
34+
* of the source node.
35+
* <p>
36+
* Example usage: <pre>
37+
* &#64;RelationshipProperties
38+
* public class ActedIn {
39+
*
40+
* &#64;RelationshipId
41+
* private Long id;
42+
*
43+
* &#64;TargetNode
44+
* private Person actor;
45+
*
46+
* private List&lt;String&gt; roles;
47+
* private Integer year;
48+
*
49+
* // constructors, getters, setters...
50+
* }
51+
*
52+
* &#64;Node
53+
* public class Movie {
54+
*
55+
* &#64;Id
56+
* private String title;
57+
*
58+
* &#64;Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
59+
* private List&lt;ActedIn&gt; actors = new ArrayList&lt;&gt;();
60+
*
61+
* // other fields...
62+
* }
63+
* </pre>
64+
* <p>
65+
* In the above example, the {@code ActedIn} class represents a relationship with
66+
* properties between a {@code Movie} and a {@code Person}. The {@code actor} field marked
67+
* with {@code @TargetNode} represents the Person node that is the target of the ACTED_IN
68+
* relationship.
69+
* <p>
70+
* The {@code @TargetNode} annotation helps Spring Data FalkorDB understand the structure
71+
* of the relationship and properly map the relationship properties and target node when
72+
* loading and saving entities.
73+
*
74+
* @author Shahar Biron (FalkorDB adaptation)
75+
* @since 1.0
76+
* @see RelationshipProperties
77+
* @see Relationship
78+
*/
79+
@Retention(RetentionPolicy.RUNTIME)
80+
@Target(ElementType.FIELD)
81+
@Documented
82+
@Inherited
83+
@API(status = API.Status.STABLE, since = "1.0")
84+
public @interface TargetNode {
85+
86+
}

0 commit comments

Comments
 (0)