Skip to content

Transactions

Volt provides transaction support for atomic database operations. All operations return a Result<T, VoltError> type for explicit error handling.

Getting a Transaction

java
Transaction tx = volt.beginTransaction();

Transactions implement AutoCloseable, so you can use try-with-resources:

java
try (Transaction tx = volt.beginTransaction()) {
    // operations...
    tx.commit();
}

Result Type

All transaction operations return Result<T, VoltError> instead of throwing exceptions:

java
Result<User, VoltError> result = tx.findById(User.class, 1L);

if (result.isOkay()) {
    User user = result.getValue();
    // use user
} else {
    VoltError error = result.getError();
    System.err.println(error.getMessage());
}

CRUD Operations

Save (Insert or Upsert)

Saves an entity. Inserts if the primary key is null and auto-generated, otherwise performs an upsert:

java
User user = new User();
user.setUsername("john");
user.setEmail("john@example.com");

Result<User, VoltError> result = tx.save(user);

Find by ID

java
Result<User, VoltError> result = tx.findById(User.class, 1L);

Find All

Retrieve all entities of a type:

java
Result<List<User>, VoltError> result = tx.findAll(User.class);

Find by Field

Simple field equality queries:

java
// Find first matching (returns Optional)
Result<Optional<User>, VoltError> result = tx.findFirstBy(User.class, "email", "john@example.com");

// Find exactly one (errors if none or multiple found)
Result<User, VoltError> result = tx.findOneBy(User.class, "username", "john");

// Find all matching
Result<List<User>, VoltError> result = tx.findAllBy(User.class, "status", "active");

Find with Query

For complex queries, pass a Query object:

java
Query query = Query.where("status").eq("active")
    .and("age").gte(18);

// Find first matching
Result<Optional<User>, VoltError> first = tx.findFirstBy(User.class, query);

// Find exactly one
Result<User, VoltError> one = tx.findOneBy(User.class, query);

// Find all matching
Result<List<User>, VoltError> all = tx.findAllBy(User.class, query);

Delete

Delete by entity or by ID:

java
// Delete entity
Result<Void, VoltError> result = tx.delete(user);

// Delete by ID
Result<Void, VoltError> result = tx.deleteById(User.class, 1L);

Commit and Rollback

Commit

Persist all changes made within the transaction:

java
Result<Void, VoltError> result = tx.commit();

if (result.isOkay()) {
    System.out.println("Transaction committed");
} else {
    System.err.println("Commit failed: " + result.getError().getMessage());
}

Rollback

Discard all changes:

java
Result<Void, VoltError> result = tx.rollback();

Complete Examples

java
try (Transaction tx = volt.beginTransaction()) {
    User user = new User();
    user.setUsername("john");
    user.setEmail("john@example.com");

    Result<User, VoltError> userResult = tx.save(user);
    if (!userResult.isOkay()) {
        tx.rollback();
        return;
    }

    Profile profile = new Profile();
    profile.setUserId(userResult.getValue().getId());
    profile.setBio("Software developer");

    Result<Profile, VoltError> profileResult = tx.save(profile);
    if (!profileResult.isOkay()) {
        tx.rollback();
        return;
    }

    tx.commit();
}

Money Transfer

java
public Result<Void, VoltError> transfer(Long fromId, Long toId, BigDecimal amount) {
    try (Transaction tx = volt.beginTransaction()) {
        Result<Account, VoltError> fromResult = tx.findById(Account.class, fromId);
        if (!fromResult.isOkay()) {
            return Result.failure(fromResult.getError());
        }

        Result<Account, VoltError> toResult = tx.findById(Account.class, toId);
        if (!toResult.isOkay()) {
            return Result.failure(toResult.getError());
        }

        Account from = fromResult.getValue();
        Account to = toResult.getValue();

        if (from.getBalance().compareTo(amount) < 0) {
            tx.rollback();
            return Result.failure(new VoltError("Insufficient funds"));
        }

        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));

        tx.save(from);
        tx.save(to);

        return tx.commit();
    }
}

Query and Update

java
try (Transaction tx = volt.beginTransaction()) {
    Query query = Query.where("status").eq("pending")
        .and("createdAt").lt(cutoffDate);

    Result<List<Order>, VoltError> result = tx.findAllBy(Order.class, query);

    if (result.isOkay()) {
        for (Order order : result.getValue()) {
            order.setStatus("expired");
            tx.save(order);
        }
        tx.commit();
    }
}

Method Reference

MethodReturn TypeDescription
save(entity)Result<T, VoltError>Insert or upsert an entity
findById(class, id)Result<T, VoltError>Find by primary key
findAll(class)Result<List<T>, VoltError>Find all entities
findFirstBy(class, field, value)Result<Optional<T>, VoltError>Find first by field
findOneBy(class, field, value)Result<T, VoltError>Find exactly one by field
findAllBy(class, field, value)Result<List<T>, VoltError>Find all by field
findFirstBy(class, query)Result<Optional<T>, VoltError>Find first by query
findOneBy(class, query)Result<T, VoltError>Find exactly one by query
findAllBy(class, query)Result<List<T>, VoltError>Find all by query
delete(entity)Result<Void, VoltError>Delete an entity
deleteById(class, id)Result<Void, VoltError>Delete by primary key
commit()Result<Void, VoltError>Commit the transaction
rollback()Result<Void, VoltError>Rollback the transaction
close()voidRelease the connection

Made with ❤️