Unreleased version v6.4.0-SNAPSHOT. This page describes the alpha branch and may change at any time; it is not part of any released version.

commit ee1447a · 2026-11-08 11:36 UTC

Skip to content

Query DSL ​

Since v6.2.0

The fluent Query DSL is available starting from UltiTools-API v6.2.0.

UltiTools provides a fluent Query DSL that lets you build data queries using a readable, chainable API. Instead of manually constructing WhereCondition objects, you can write expressive queries directly.

Basic Usage ​

Get a Query builder from any DataOperator:

java
DataOperator<HomeEntity> dataOperator = plugin.getDataOperator(HomeEntity.class);

// Find all homes belonging to a player
List<HomeEntity> homes = dataOperator.query()
    .where("player_id").eq(playerUuid)
    .list();

The snippets on this page are fragments. The HomeEntity of the complete example maps only player_id and name; the snippets that query world assume it also maps a world column, and PlayerEntity stands for an entity with balance and level columns. Every column you name must be an @Column of the entity: the MySQL and SQLite backends reject an unknown column in eq with DataAccessException, and an in-memory operator such as in matches no row on an unknown column.

Conditions ​

where / and ​

Use where() to start your first condition, and and() to add additional conditions:

java
// Find a specific home by player and name
HomeEntity home = dataOperator.query()
    .where("player_id").eq(playerUuid)
    .and("name").eq("base")
    .first();

Comparison Operators ​

After calling where() or and(), chain one of these operators:

OperatorDescriptionExample
eq(value)Equal to.where("name").eq("base")
ne(value)Not equal to.where("status").ne("disabled")
gt(value)Greater than.where("level").gt(10)
lt(value)Less than.where("balance").lt(1000.0)
gte(value)Greater than or equal.where("level").gte(5)
lte(value)Less than or equal.where("balance").lte(500.0)
like(pattern)% matches any run of characters, _ one character; case-insensitive (see below).where("name").like("%base%")
in(collection)Value in collection.where("world").in(worldList)
java
// Find players with balance between 100 and 1000
List<PlayerEntity> players = dataOperator.query()
    .where("balance").gte(100.0)
    .and("balance").lte(1000.0)
    .list();

// Find homes in specific worlds
List<String> worlds = Arrays.asList("world", "world_nether");
List<HomeEntity> homes = dataOperator.query()
    .where("world").in(worlds)
    .list();

like is not passed to the database. It is evaluated in memory, case-insensitively, on every backend: % and _ are turned into a Java regular expression and every other character is interpreted as regex, so escape ., (, [ and the like yourself.

Ordering ​

Sort results using orderBy() (ascending) or orderByDesc() (descending):

java
// Get players sorted by balance, highest first
List<PlayerEntity> richest = dataOperator.query()
    .orderByDesc("balance")
    .list();

// Sort by level ascending
List<PlayerEntity> sorted = dataOperator.query()
    .orderBy("level")
    .list();

Pagination ​

Use limit() and offset() to paginate results:

java
// Get page 2 with 10 items per page
int pageSize = 10;
int page = 2;
List<PlayerEntity> pageResults = dataOperator.query()
    .limit(pageSize)
    .offset((page - 1) * pageSize)
    .list();

Only eq conditions reach the storage backend. The other operators, ordering, offset and limit are applied in memory to every row that matches the eq conditions, so this still loads the whole matching set. For large tables use DataOperator.page(...).

Terminal Operations ​

Every query chain must end with a terminal operation:

MethodReturn TypeDescription
list()List<T>Returns all matching results
first()T (nullable)Returns the first match, or null
exists()booleanReturns true if any match exists
count()longCounts matching results
delete()intDeletes matching results, returns the number of rows removed
java
// Check if a player has any homes
boolean hasHomes = dataOperator.query()
    .where("player_id").eq(playerUuid)
    .exists();

// Count homes for a player
long homeCount = dataOperator.query()
    .where("player_id").eq(playerUuid)
    .count();

// Delete all homes in a specific world
int deleted = dataOperator.query()
    .where("world").eq("old_world")
    .delete();

As of v6.3.0, delete() returns the number of rows the backend actually removed, so a matched row that another writer removed first is not counted. If a matched row has a null id, delete() throws DataAccessException before deleting anything, because no delete can address that row.

Complete Example ​

Here is a complete example of a service using the Query DSL:

java
package com.ultikits.docs.query;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.abstracts.data.AuditableDataEntity;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.Player;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
public class HomeServiceImpl implements HomeService {

    private final UltiToolsPlugin plugin;

    @Autowired
    public HomeServiceImpl(UltiToolsPlugin plugin) {
        this.plugin = plugin;
    }

    @Override
    public HomeEntity getHome(String playerUuid, String homeName) {
        return plugin.getDataOperator(HomeEntity.class).query()
            .where("player_id").eq(playerUuid)
            .and("name").eq(homeName)
            .first();
    }

    @Override
    public List<HomeEntity> getAllHomes(String playerUuid) {
        return plugin.getDataOperator(HomeEntity.class).query()
            .where("player_id").eq(playerUuid)
            .orderBy("name")
            .list();
    }

    @Override
    public boolean homeExists(String playerUuid, String homeName) {
        return plugin.getDataOperator(HomeEntity.class).query()
            .where("player_id").eq(playerUuid)
            .and("name").eq(homeName)
            .exists();
    }

    @Override
    public void deleteHome(String playerUuid, String homeName) {
        plugin.getDataOperator(HomeEntity.class).query()
            .where("player_id").eq(playerUuid)
            .and("name").eq(homeName)
            .delete();
    }
}

Legacy API

The WhereCondition API still works and is not deprecated. The Query DSL is a higher-level alternative that is more readable for complex queries. You can mix both approaches freely.

Contributors

No contributors

Released under the MIT License.