A Spring Data-like integration framework for LadybugDB, providing familiar Spring patterns for graph database operations.
- Repository Pattern: Spring Data-style
NodeRepositoryfor CRUD operations on graph nodes - Template Support:
LadybugDBTemplatefor executing Cypher queries with connection management - Connection Pooling: Built-in connection pooling via
PooledConnectionFactory - Cypher DSL Integration: Use Neo4j Cypher DSL for type-safe query building
- Entity Mapping: Annotation-based entity mapping with
@NodeEntityand@Id - Custom Queries: Support for
@Queryannotation on repository interfaces
<dependency>
<groupId>com.thecookiezen</groupId>
<artifactId>spring-data-ladybugdb</artifactId>
<version>0.0.1</version>
</dependency>@NodeEntity(label = "Person")
public class Person {
@Id
private String name;
private int age;
// constructors, getters, setters
}LadybugDBTemplate template = new LadybugDBTemplate(connectionFactory);
// Execute raw Cypher
template.execute("CREATE (p:Person {name: 'Alice', age: 30})");
// Query with mapping
List<Person> people = template.query(
"MATCH (p:Person) RETURN p.name AS name, p.age AS age",
(row) -> new Person(
ValueMappers.asString(row.getValue("name")),
ValueMappers.asInteger(row.getValue("age"))
)
);The repository requires RowMapper and EntityWriter to handle entity conversion.
// Define mappers
RowMapper<Person> reader = (row) -> new Person(
ValueMappers.asString(row.getValue("name")),
ValueMappers.asInteger(row.getValue("age"))
);
EntityWriter<Person> writer = (entity) -> Map.of(
"age", entity.getAge() // ID (name) is handled automatically
);
// Create descriptors
EntityDescriptor<Person> personDescriptor = new EntityDescriptor<>(Person.class, reader, writer);
// define relationship descriptor similarly if needed, or pass null if not using relationships
SimpleNodeRepository<Person, Void, String> repository = new SimpleNodeRepository<>(
template,
Person.class,
Void.class,
personDescriptor,
null // relationship descriptor
);
// CRUD operations
Person saved = repository.save(new Person("Bob", 25));
Optional<Person> found = repository.findById("Bob");
repository.deleteById("Bob");You can also map relationships, including their properties and connected nodes.
// Define relationship entity
public class Follows {
String id;
Person from;
Person to;
int since;
// constructors...
}
// Define mapper for relationship
RowMapper<Follows> followsMapper = (row) -> {
// Get relationship data (properties, type, ids)
RelationshipData rel = row.getRelationship("rel");
String id = rel.id().toString(); // Internal ID
int since = ValueMappers.asInteger(rel.properties().get("since"));
// Get connected nodes (if returned by query)
// Query: MATCH (s)-[rel:FOLLOWS]->(t) RETURN s, rel, t
Map<String, Value> sourceProps = row.getNode("s");
Map<String, Value> targetProps = row.getNode("t");
Person from = new Person(
ValueMappers.asString(sourceProps.get("name")),
ValueMappers.asInteger(sourceProps.get("age"))
);
Person to = new Person(
ValueMappers.asString(targetProps.get("name")),
ValueMappers.asInteger(targetProps.get("age"))
);
return new Follows(id, from, to, since);
};You can execute custom Cypher queries using the @Query annotation on your repository interface.
public interface PersonRepository extends NodeRepository<Person, String, Void, Person> {
@Query("MATCH (p:Person) WHERE p.age > $minAge RETURN p")
List<Person> findByAgeGreaterThan(@Param("minAge") int minAge);
@Query("MATCH (p:Person {name: $name}) SET p.age = $newAge RETURN p")
Optional<Person> updateAge(@Param("name") String name, @Param("newAge") int newAge);
}LadybugDB supports dynamic extension loading (e.g., for vector search). You can load extensions both through the template and the repository.
Pass an array of extension names as the first argument to query, stream, or execute:
List<Note> results = template.query(
new String[]{"vector"}, // Extensions to load
"MATCH (n:Note) WHERE vector_search(n.embedding, $embedding) RETURN n",
Map.of("embedding", queryVector),
noteReader
);Use the loadExtensions attribute in the @Query annotation:
public interface NoteRepository extends NodeRepository<Note, String, Void, Note> {
@Query(
value = "MATCH (n:Note) WHERE vector_search(n.embedding, $query, metric := 'cosine') < 0.5 RETURN n",
loadExtensions = {"vector"}
)
List<Note> findSimilarNotes(@Param("query") float[] query);
}You can also execute manual setup commands for extensions:
// Install extension (usually required once)
template.execute("INSTALL vector");
// Configure extension directory if needed
template.execute("CALL home_directory='/path/to/extensions'");| Component | Description |
|---|---|
LadybugDBTemplate |
Central class for executing Cypher queries |
SimpleNodeRepository |
Repository implementation for node entities |
LadybugDBTransactionManager |
Transaction manager (connection binding only, no commit/rollback) |
PooledConnectionFactory |
Connection pool using Apache Commons Pool2 |
SimpleConnectionFactory |
Simple connection factory (no pooling) |
rowMapper / QueryRow |
Interface for mapping query results to domain objects |
Caution
Single Writer Constraint: LadybugDB only allows one write transaction at a time. Concurrent write operations will block waiting for the write lock, which can cause issues in multi-threaded applications.
Per LadybugDB documentation:
"At any point in time, there can be multiple read transactions but only one write transaction"
Implications:
- The transaction manager provides connection binding only - it does not use explicit
BEGIN TRANSACTION/COMMIT/ROLLBACK - Each query auto-commits immediately
- Rollback is not supported - once a command executes, it is committed
- Multiple read-only transactions can run in parallel without blocking
- Keep write operations short to minimize blocking time
- Use connection pooling (
PooledConnectionFactory) for efficient connection reuse - Consider read-only transactions for read-heavy workloads - they don't block writers
<dependency>
<groupId>com.ladybugdb</groupId>
<artifactId>lbug</artifactId>
<version>0.15.1</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher-dsl</artifactId>
<version>2025.2.4</version>
</dependency>