Getting Started
This guide will help you get up and running with DB plugin quickly. You'll learn how to create your first domain objects, and perform basic operations.
Creating Your First Record Class
A Record is your domain object that maps to database storage. Here's a simple example:
import com.psddev.dari.db.Record;
public class Article extends Record {
@Indexed
private String title;
@Indexed
@Required
private Author author;
@Indexed
private Date publishDate;
private String content;
private List<String> tags;
// Getters and setters
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public Date getPublishDate() {
return publishDate;
}
public void setPublishDate(Date publishDate) {
this.publishDate = publishDate;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<String> getTags() {
if (tags == null) {
tags = new ArrayList<>();
}
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
// Optional: Override getLabel for display purposes
@Override
public String getLabel() {
return title != null ? title : "Untitled Article";
}
}
import com.psddev.dari.db.Record;
public class Author extends Record {
@Indexed
@Required
private String name;
@Indexed
private String email;
private String bio;
// Getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBio() {
return bio;
}
public void setBio(String bio) {
this.bio = bio;
}
@Override
public String getLabel() {
return name;
}
}
Important Annotations
@Indexed- Makes a field queryable. Required for any field you want to filter/sort by.@Required- Field must have a value before saving.@Embedded- Always stores the object inline (no separate table/document).
Basic CRUD Operations
Creating and Saving Objects
// Create a new author
Author author = new Author();
author.setName("John Doe");
author.setEmail("john@example.com");
author.setBio("Experienced writer and developer");
author.save();
// Create an article
Article article = new Article();
article.setTitle("Getting Started with DB plugin");
article.setAuthor(author); // Reference to author
article.setPublishDate(new Date());
article.setContent("This is the content of the article...");
article.setTags(Arrays.asList("database", "tutorial", "java"));
article.save();
Reading Objects by ID
Every Record has a unique ID (UUID):
// Get the ID
UUID articleId = article.getId();
// Later, load by ID
Article loaded = Query.from(Article.class)
.where("_id = ?", articleId)
.first();
Querying Objects
// Find all articles
List<Article> allArticles = Query.from(Article.class)
.selectAll();
// Find articles by author
List<Article> johnArticles = Query.from(Article.class)
.where("author = ?", author)
.selectAll();
// Find articles by author name (path-based query)
List<Article> johnArticles = Query.from(Article.class)
.where("author/name = ?", "John Doe")
.selectAll();
// Find recent articles
Date cutoffDate = DateUtils.addDays(new Date(), -7);
List<Article> recentArticles = Query.from(Article.class)
.where("publishDate > ?", cutoffDate)
.sortDescending("publishDate")
.selectAll();
// Paginated results
List<Article> firstPage = Query.from(Article.class)
.sortDescending("publishDate")
.select(0, 10); // Skip 0, limit 10
// Count articles
long totalArticles = Query.from(Article.class)
.count();
Updating Objects
// Load the object
Article article = Query.from(Article.class)
.where("_id = ?", articleId)
.first();
// Modify fields
article.setTitle("Updated Title");
article.getTags().add("updated");
// Save changes
article.save();
Deleting Objects
// Delete a single object
article.delete();
// Delete by query
Query.from(Article.class)
.where("publishDate < ?", cutoffDate)
.deleteAll();
Working with References
DB plugin automatically handles relationships between objects:
// Save author first
Author author = new Author();
author.setName("Jane Smith");
author.save();
// Create article with reference
Article article = new Article();
article.setTitle("My Article");
article.setAuthor(author); // This stores a reference, not a copy
article.save();
// Later, when you load the article
Article loaded = Query.from(Article.class)
.where("_id = ?", articleId)
.first();
// The author is lazy-loaded
Author loadedAuthor = loaded.getAuthor(); // Database query happens here
System.out.println(loadedAuthor.getName()); // "Jane Smith"
Reference Resolution Options
// Reference-only query (don't load full objects)
List<Article> articleRefs = Query.from(Article.class)
.resolveToReferenceOnly()
.selectAll();
// articleRefs contains lightweight reference objects
Using Transactions
For multiple operations that should succeed or fail together:
Database db = Database.Static.getDefault();
try {
db.beginWrites();
// Create multiple objects
Author author = new Author();
author.setName("John Doe");
author.save();
Article article1 = new Article();
article1.setTitle("First Article");
article1.setAuthor(author);
article1.save();
Article article2 = new Article();
article2.setTitle("Second Article");
article2.setAuthor(author);
article2.save();
// Commit all changes
db.commitWrites();
} finally {
// Always clean up (rolls back on exception)
db.endWrites();
}
Validation
DB plugin validates objects before saving:
public class Article extends Record {
@Required
@Maximum(200)
private String title;
@Required
private Author author;
// Custom validation
@Override
protected void onValidate() {
if (title != null && title.contains("spam")) {
getState().addError(
getState().getField("title"),
new IllegalArgumentException("Title contains spam")
);
}
}
}
// Attempting to save an invalid object throws ValidationException
Article article = new Article();
// article.setTitle("Required field missing");
// article.setAuthor(null); // Also required
try {
article.save();
} catch (ValidationException e) {
// Handle validation errors
for (ObjectField field : e.getErrors().keySet()) {
System.out.println("Error in " + field.getInternalName() + ": "
+ e.getErrors().get(field));
}
}
Lifecycle Hooks
Override lifecycle methods to add custom behavior:
public class Article extends Record {
@Override
protected void beforeSave() {
// Called before validation and save
if (publishDate == null) {
publishDate = new Date();
}
}
@Override
protected void afterSave() {
// Called after successful save
System.out.println("Article saved: " + getLabel());
}
@Override
protected void beforeDelete() {
// Called before delete
System.out.println("Deleting article: " + getLabel());
}
@Override
protected void afterDelete() {
// Called after successful delete
System.out.println("Article deleted");
}
}
Common Query Patterns
Search by Multiple Tags
// Find articles with any of the specified tags
List<Article> articles = Query.from(Article.class)
.where("tags = ?", Arrays.asList("java", "database", "tutorial"))
.selectAll();
// Find articles with all specified tags (requires all)
// This requires querying differently - use repeated predicates
Query<Article> query = Query.from(Article.class);
for (String tag : Arrays.asList("java", "database")) {
query.and("tags = ?", tag);
}
List<Article> articlesWithAllTags = query.selectAll();
Counting and Statistics
// Total count
long totalArticles = Query.from(Article.class).count();
// Conditional count
long publishedCount = Query.from(Article.class)
.where("publishDate != missing")
.count();
// Count by author
long johnCount = Query.from(Article.class)
.where("author/name = ?", "John Doe")
.count();
Best Practices
1. Always Index Queryable Fields
// Good
@Indexed
private String title;
// Bad - can't query this field
private String title;
2. Use Transactions for Multiple Writes
// Good - atomic
db.beginWrites();
try {
author.save();
article.save();
db.commitWrites();
} finally {
db.endWrites();
}
// Bad - non-atomic, could partially succeed
author.save();
article.save();
3. Use Pagination for Large Result Sets
// Good - memory efficient
List<Article> page = Query.from(Article.class)
.select(offset, limit);
// Bad - loads everything into memory
List<Article> all = Query.from(Article.class)
.selectAll();
4. Prefer Path Queries Over Manual Joins
// Good - simple and efficient
Query.from(Article.class)
.where("author/name = ?", "John Doe")
.selectAll();
// Avoid - more complex
Author author = Query.from(Author.class)
.where("name = ?", "John Doe")
.first();
Query.from(Article.class)
.where("author = ?", author)
.selectAll();
5. Initialize Collections in Getters
// Good
public List<String> getTags() {
if (tags == null) {
tags = new ArrayList<>();
}
return tags;
}
// Bad - can return null
public List<String> getTags() {
return tags;
}