Files
gh-giuseppe-trisciuoglio-de…/skills/spring-boot/spring-data-neo4j/references/reference.md
2025-11-29 18:28:30 +08:00

19 KiB

Spring Data Neo4j - Reference Guide

This document provides detailed reference information for Spring Data Neo4j, including annotations, query language syntax, configuration options, and API documentation.

Table of Contents

  1. Annotations Reference
  2. Cypher Query Language
  3. Configuration Properties
  4. Repository Methods
  5. Projections and DTOs
  6. Transaction Management
  7. Performance Tuning

Annotations Reference

Entity Annotations

@Node

Marks a class as a Neo4j node entity.

@Node                    // Label defaults to class name
@Node("CustomLabel")     // Explicit label
@Node({"Label1", "Label2"})  // Multiple labels
public class MyEntity {
    // ...
}

Properties:

  • value or labels: String or String array for node labels
  • primaryLabel: Specify which label is primary (when using multiple labels)

@Id

Marks a field as the entity identifier.

@Id
private String businessKey;  // Custom business key

@Id @GeneratedValue
private Long id;  // Auto-generated internal ID

Important:

  • Required on every @Node entity
  • Can be used with business keys or generated values
  • Must be unique within the node type

@GeneratedValue

Configures ID generation strategy.

@Id @GeneratedValue
private Long id;  // Uses Neo4j internal ID

@Id @GeneratedValue(generatorClass = UUIDStringGenerator.class)
private String uuid;  // Custom UUID generator

@Id @GeneratedValue(generatorClass = MyCustomGenerator.class)
private String customId;

Built-in Generators:

  • InternalIdGenerator (default for Long): Uses Neo4j's internal ID
  • UUIDStringGenerator: Generates UUID strings

@Property

Maps a field to a different property name in Neo4j.

@Property("graph_property_name")
private String javaFieldName;

When to use:

  • Field name differs from graph property name
  • Property names contain special characters
  • Following different naming conventions

@Relationship

Defines relationships between nodes.

@Relationship(type = "RELATIONSHIP_TYPE", direction = Direction.OUTGOING)
private RelatedEntity related;

@Relationship(type = "RELATED_TO", direction = Direction.INCOMING)
private List<RelatedEntity> incoming;

@Relationship(type = "CONNECTED", direction = Direction.UNDIRECTED)
private Set<RelatedEntity> connections;

Properties:

  • type (required): Relationship type in Neo4j
  • direction: OUTGOING, INCOMING, or UNDIRECTED
  • Default direction is OUTGOING if not specified

Direction Guidelines:

  • OUTGOING: This node → target node
  • INCOMING: Target node → this node
  • UNDIRECTED: Ignores direction when querying

@RelationshipProperties

Marks a class as relationship properties container.

@RelationshipProperties
public class ActedIn {
    
    @Id @GeneratedValue
    private Long id;
    
    @TargetNode
    private Movie movie;
    
    private List<String> roles;
    private Integer screenTime;
}

Required Fields:

  • @Id field (can be generated)
  • @TargetNode field pointing to target entity

Repository Annotations

@Query

Defines custom Cypher query for a repository method.

@Query("MATCH (n:Node) WHERE n.property = $param RETURN n")
List<Node> customQuery(@Param("param") String param);

@Query("MATCH (n:Node) WHERE n.id = $0 RETURN n")
Node findById(String id);  // Positional parameter

Parameter Binding:

  • Use $paramName for named parameters with @Param
  • Use $0, $1, etc. for positional parameters
  • SpEL expressions supported: #{#entityName}

@Param

Binds method parameter to query parameter.

@Query("MATCH (n) WHERE n.name = $customName RETURN n")
List<Node> find(@Param("customName") String name);

When required:

  • Parameter name in query differs from method parameter
  • Making intent explicit and clear

Configuration Annotations

@EnableNeo4jRepositories

Enables Neo4j repository support.

@Configuration
@EnableNeo4jRepositories(basePackages = "com.example.repositories")
public class Neo4jConfiguration {
    // ...
}

Properties:

  • basePackages: Packages to scan for repositories
  • basePackageClasses: Type-safe package specification
  • repositoryImplementationPostfix: Custom implementation suffix (default: "Impl")

Note: Auto-enabled by Spring Boot starter, manual configuration rarely needed.

@DataNeo4jTest

Test slice annotation for Neo4j tests.

@DataNeo4jTest
class MyRepositoryTest {
    @Autowired
    private MyRepository repository;
}

What it does:

  • Configures test slice for Spring Data Neo4j
  • Loads only Neo4j-related beans
  • Configures embedded test database when available
  • Enables transaction rollback for tests

Cypher Query Language

Basic Patterns

MATCH - Find Patterns

// Find all nodes with label
MATCH (n:Label) RETURN n

// Find node with property
MATCH (n:Label {property: 'value'}) RETURN n

// Find nodes with WHERE clause
MATCH (n:Label) WHERE n.property > 100 RETURN n

// Multiple labels
MATCH (n:Label1:Label2) RETURN n

Relationship Patterns

// Outgoing relationship
MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a, b

// Incoming relationship
MATCH (a:Person)<-[:KNOWS]-(b:Person) RETURN a, b

// Undirected relationship
MATCH (a:Person)-[:KNOWS]-(b:Person) RETURN a, b

// Relationship with properties
MATCH (a)-[r:KNOWS {since: 2020}]->(b) RETURN a, r, b

// Variable length relationships
MATCH (a)-[:KNOWS*1..3]->(b) RETURN a, b

CREATE - Create Patterns

// Create single node
CREATE (n:Person {name: 'John', age: 30})

// Create node and relationship
CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})

// Create relationship between existing nodes
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS {since: 2020}]->(b)

MERGE - Find or Create

// Find or create node
MERGE (n:Person {email: 'john@example.com'})
ON CREATE SET n.created = timestamp()
ON MATCH SET n.accessed = timestamp()

// Find or create relationship
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
MERGE (a)-[r:KNOWS]->(b)
ON CREATE SET r.since = 2020

SET - Update Properties

// Set single property
MATCH (n:Person {name: 'John'})
SET n.age = 31

// Set multiple properties
MATCH (n:Person {name: 'John'})
SET n.age = 31, n.city = 'London'

// Set from map
MATCH (n:Person {name: 'John'})
SET n += {age: 31, city: 'London'}

// Add label
MATCH (n:Person {name: 'John'})
SET n:Premium

DELETE and REMOVE

// Delete node (must have no relationships)
MATCH (n:Person {name: 'John'})
DELETE n

// Delete node and relationships
MATCH (n:Person {name: 'John'})
DETACH DELETE n

// Delete relationship
MATCH (a)-[r:KNOWS]->(b)
WHERE a.name = 'Alice' AND b.name = 'Bob'
DELETE r

// Remove property
MATCH (n:Person {name: 'John'})
REMOVE n.age

// Remove label
MATCH (n:Person {name: 'John'})
REMOVE n:Premium

Advanced Patterns

Collections and List Functions

// Collect results
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN p.name, collect(m.title) AS movies

// Unwind collection
UNWIND [1, 2, 3] AS number
RETURN number

// List comprehension
MATCH (p:Person)
RETURN [x IN p.skills WHERE x STARTS WITH 'Java'] AS javaSkills

// Size of collection
MATCH (p:Person)
RETURN p.name, size(p.skills) AS skillCount

Aggregation Functions

// Count
MATCH (p:Person) RETURN count(p)

// Sum
MATCH (p:Product) RETURN sum(p.price)

// Average
MATCH (p:Product) RETURN avg(p.price)

// Min/Max
MATCH (p:Product) RETURN min(p.price), max(p.price)

// Group by with aggregation
MATCH (p:Person)-[:LIVES_IN]->(c:City)
RETURN c.name, count(p) AS population
ORDER BY population DESC

Conditional Logic

// CASE expression
MATCH (p:Person)
RETURN p.name,
  CASE
    WHEN p.age < 18 THEN 'Minor'
    WHEN p.age < 65 THEN 'Adult'
    ELSE 'Senior'
  END AS category

// COALESCE - first non-null value
MATCH (p:Person)
RETURN coalesce(p.nickname, p.name) AS displayName

Pattern Comprehension

// Pattern comprehension
MATCH (p:Person)
RETURN p.name,
  [(p)-[:KNOWS]->(friend) | friend.name] AS friends

// With filtering
MATCH (p:Person)
RETURN p.name,
  [(p)-[:KNOWS]->(friend) WHERE friend.age > 30 | friend.name] AS olderFriends

Query Optimization

Using Indexes

// Create index (admin query, not in @Query)
CREATE INDEX person_name FOR (n:Person) ON (n.name)

// Composite index
CREATE INDEX person_name_age FOR (n:Person) ON (n.name, n.age)

// Use index hint
MATCH (p:Person)
USING INDEX p:Person(name)
WHERE p.name = 'John'
RETURN p

PROFILE and EXPLAIN

// Analyze query performance
PROFILE
MATCH (p:Person)-[:KNOWS*1..3]->(friend)
WHERE p.name = 'Alice'
RETURN friend.name

// Dry run without execution
EXPLAIN
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p, friend

Limiting Results

// Limit results
MATCH (n:Person) RETURN n LIMIT 10

// Skip and limit (pagination)
MATCH (n:Person)
RETURN n
ORDER BY n.name
SKIP 20 LIMIT 10

Configuration Properties

Connection Properties

# Neo4j URI
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.uri=neo4j://localhost:7687
spring.neo4j.uri=neo4j+s://production.server:7687

# Authentication
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
spring.neo4j.authentication.realm=native
spring.neo4j.authentication.kerberos-ticket=...

# Connection pool
spring.neo4j.pool.max-connection-pool-size=50
spring.neo4j.pool.idle-time-before-connection-test=PT30S
spring.neo4j.pool.max-connection-lifetime=PT1H
spring.neo4j.pool.connection-acquisition-timeout=PT60S
spring.neo4j.pool.metrics-enabled=true

Advanced Configuration

# Logging
spring.neo4j.logging.level=WARN
spring.neo4j.logging.log-leaked-sessions=true

# Connection timeout
spring.neo4j.connection-timeout=PT30S

# Max transaction retry time
spring.neo4j.max-transaction-retry-time=PT30S

# Encrypted connection
spring.neo4j.security.encrypted=true
spring.neo4j.security.trust-strategy=TRUST_ALL_CERTIFICATES
spring.neo4j.security.hostname-verification-enabled=true

Neo4j Driver Configuration Bean

@Configuration
public class Neo4jConfiguration {
    
    @Bean
    org.neo4j.driver.Config neo4jDriverConfig() {
        return org.neo4j.driver.Config.builder()
            .withMaxConnectionPoolSize(50)
            .withConnectionAcquisitionTimeout(60, TimeUnit.SECONDS)
            .withConnectionLivenessCheckTimeout(30, TimeUnit.SECONDS)
            .withMaxConnectionLifetime(1, TimeUnit.HOURS)
            .withLogging(Logging.slf4j())
            .withEncryption()
            .build();
    }
    
    @Bean
    Configuration cypherDslConfiguration() {
        return Configuration.newConfig()
            .withDialect(Dialect.NEO4J_5)
            .build();
    }
}

Repository Methods

Query Derivation Keywords

Keyword Cypher Equivalent
findBy MATCH ... RETURN
existsBy MATCH ... RETURN count(*) > 0
countBy MATCH ... RETURN count(*)
deleteBy MATCH ... DETACH DELETE
And AND
Or OR
Between >= $lower AND <= $upper
LessThan <
LessThanEqual <=
GreaterThan >
GreaterThanEqual >=
Before < (for dates)
After > (for dates)
IsNull IS NULL
IsNotNull IS NOT NULL
Like =~ '.*pattern.*'
NotLike NOT =~ '.*pattern.*'
StartingWith STARTS WITH
EndingWith ENDS WITH
Containing CONTAINS
In IN
NotIn NOT IN
True = true
False = false
OrderBy...Asc ORDER BY ... ASC
OrderBy...Desc ORDER BY ... DESC

Method Return Types

Return Type Description
Entity Single result or null
Optional<Entity> Single result wrapped in Optional
List<Entity> Multiple results
Stream<Entity> Results as Java Stream
Page<Entity> Paginated results
Slice<Entity> Slice of results
Mono<Entity> Reactive single result
Flux<Entity> Reactive stream of results
boolean Existence check
long Count query

Examples

public interface UserRepository extends Neo4jRepository<User, String> {
    
    // Simple query derivation
    Optional<User> findByEmail(String email);
    
    List<User> findByAgeGreaterThan(Integer age);
    
    List<User> findByAgeBetween(Integer minAge, Integer maxAge);
    
    List<User> findByNameStartingWith(String prefix);
    
    // Boolean queries
    boolean existsByEmail(String email);
    
    // Count queries
    long countByAgeGreaterThan(Integer age);
    
    // Delete queries
    long deleteByAgeLessThan(Integer age);
    
    // Sorting
    List<User> findByAgeGreaterThanOrderByNameAsc(Integer age);
    
    // Pagination
    Page<User> findByAgeGreaterThan(Integer age, Pageable pageable);
    
    // Stream
    Stream<User> findByAgeBetween(Integer min, Integer max);
    
    // Multiple conditions
    List<User> findByNameAndAge(String name, Integer age);
    
    List<User> findByNameOrEmail(String name, String email);
    
    // Null checks
    List<User> findByNicknameIsNull();
    
    List<User> findByNicknameIsNotNull();
    
    // Collection queries
    List<User> findByRolesContaining(String role);
    
    List<User> findByIdIn(Collection<String> ids);
}

Projections and DTOs

Interface-based Projections

// Closed projection - only declared properties
public interface UserSummary {
    String getUsername();
    String getEmail();
}

// Open projection - with SpEL
public interface UserWithFullName {
    @Value("#{target.firstName + ' ' + target.lastName}")
    String getFullName();
}

// Nested projection
public interface UserWithPosts {
    String getUsername();
    List<PostSummary> getPosts();
    
    interface PostSummary {
        String getTitle();
        LocalDateTime getCreatedAt();
    }
}

// Usage
public interface UserRepository extends Neo4jRepository<User, String> {
    List<UserSummary> findAllBy();
    Optional<UserWithFullName> findByUsername(String username);
}

Class-based DTOs

public record UserDTO(
    String username,
    String email,
    LocalDateTime joinedAt
) {}

// Repository usage
public interface UserRepository extends Neo4jRepository<User, String> {
    List<UserDTO> findAllBy();
}

Dynamic Projections

public interface UserRepository extends Neo4jRepository<User, String> {
    <T> T findByUsername(String username, Class<T> type);
}

// Usage
UserSummary summary = repository.findByUsername("john", UserSummary.class);
UserDTO dto = repository.findByUsername("john", UserDTO.class);
User full = repository.findByUsername("john", User.class);

Transaction Management

Declarative Transactions

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    @Transactional
    public User createUser(CreateUserRequest request) {
        User user = new User(request.username(), request.email());
        return userRepository.save(user);
    }
    
    @Transactional(readOnly = true)
    public Optional<User> getUser(String username) {
        return userRepository.findByUsername(username);
    }
    
    @Transactional(
        propagation = Propagation.REQUIRES_NEW,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30
    )
    public void complexOperation() {
        // Multiple repository calls in single transaction
        // ...
    }
}

Programmatic Transactions

@Service
public class TransactionalService {
    
    private final Neo4jTransactionManager transactionManager;
    
    public void executeInTransaction() {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.execute(status -> {
            try {
                // Your transactional code here
                return someResult;
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }
}

Reactive Transactions

@Service
public class ReactiveUserService {
    
    private final ReactiveUserRepository repository;
    private final ReactiveNeo4jTransactionManager transactionManager;
    
    public Mono<User> createUser(CreateUserRequest request) {
        return transactionManager.getReactiveTransaction()
            .flatMap(status -> {
                User user = new User(request.username(), request.email());
                return repository.save(user)
                    .doOnError(e -> status.setRollbackOnly());
            });
    }
}

Performance Tuning

Index Creation

Create indexes on frequently queried properties:

// Single property index
CREATE INDEX user_email FOR (u:User) ON (u.email);

// Composite index
CREATE INDEX user_name_age FOR (u:User) ON (u.name, u.age);

// Full-text index
CREATE FULLTEXT INDEX user_search FOR (u:User) ON EACH [u.name, u.bio];

// Show indexes
SHOW INDEXES;

// Drop index
DROP INDEX user_email;

Query Optimization Tips

  1. Use specific labels:

    // Good
    MATCH (u:User {email: $email}) RETURN u
    
    // Bad
    MATCH (n {email: $email}) RETURN n
    
  2. Filter early:

    // Good
    MATCH (u:User)
    WHERE u.age > 18
    MATCH (u)-[:POSTED]->(p:Post)
    RETURN p
    
    // Bad
    MATCH (u:User)-[:POSTED]->(p:Post)
    WHERE u.age > 18
    RETURN p
    
  3. Use projections to fetch only needed data:

    // Good
    List<UserSummary> findAllBy();
    
    // Bad (when you only need summary)
    List<User> findAll();
    
  4. Limit result sets:

    // Use pagination
    Page<User> findAll(Pageable pageable);
    
    // Or explicit limits
    @Query("MATCH (u:User) RETURN u LIMIT $limit")
    List<User> findTopUsers(@Param("limit") int limit);
    

Connection Pooling

@Bean
org.neo4j.driver.Config driverConfig() {
    return org.neo4j.driver.Config.builder()
        .withMaxConnectionPoolSize(50)
        .withConnectionAcquisitionTimeout(60, TimeUnit.SECONDS)
        .withIdleTimeBeforeConnectionTest(30, TimeUnit.SECONDS)
        .build();
}

Batch Operations

// Save in batches
@Service
public class BatchService {
    
    private final UserRepository repository;
    
    public void saveUsersInBatches(List<User> users) {
        int batchSize = 1000;
        for (int i = 0; i < users.size(); i += batchSize) {
            int end = Math.min(i + batchSize, users.size());
            List<User> batch = users.subList(i, end);
            repository.saveAll(batch);
        }
    }
}

Monitoring and Metrics

# Enable driver metrics
spring.neo4j.pool.metrics-enabled=true

# Log slow queries (if using Neo4j Enterprise)
# Set in neo4j.conf:
# dbms.logs.query.enabled=true
# dbms.logs.query.threshold=1s

Additional Resources