Skip to content

dordor12/hbase-orm

Repository files navigation

hbase-orm

A compile-time annotation processor that generates type-safe HBase mappers with zero runtime reflection.

Features

  • Zero reflection — all mapping code generated at compile time via annotation processing
  • Type-safe — generics for row key (R) and entity (T) types
  • Simple & composite row keys — single-field (@RowKey) or multi-field (@RowKeyComponent)
  • Multi-version columnsNavigableMap<Long, T> for time-series data
  • Inheritance@MappedSuperclass for shared fields across entities
  • Pluggable serializationCodec interface with a built-in BestSuitCodec
  • Async supportAsyncHBaseDAO with CompletableFuture-based API

Installation

Gradle

dependencies {
    implementation("io.github.dordor12:hbase-orm-api:0.1.0")
    annotationProcessor("io.github.dordor12:hbase-orm-processor:0.1.0")
    annotationProcessor("io.github.dordor12:hbase-orm-api:0.1.0")
}

Maven

<dependency>
    <groupId>io.github.dordor12</groupId>
    <artifactId>hbase-orm-api</artifactId>
    <version>0.1.0</version>
</dependency>

Configure the annotation processor in maven-compiler-plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>io.github.dordor12</groupId>
                <artifactId>hbase-orm-processor</artifactId>
                <version>0.1.0</version>
            </path>
            <path>
                <groupId>io.github.dordor12</groupId>
                <artifactId>hbase-orm-api</artifactId>
                <version>0.1.0</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Quick Start

1. Define an entity

@Table(name = "citizens", namespace = "govt", families = {
    @ColumnFamily(name = "main"),
    @ColumnFamily(name = "optional", versions = 10)
})
public class Citizen {

    @RowKeyComponent(order = 0)
    private String countryCode;

    @RowKeyComponent(order = 1, delimiter = "#")
    private Integer uid;

    @Column(family = "main", qualifier = "name")
    private String name;

    @Column(family = "main", qualifier = "age")
    private Short age;

    @MultiVersion(family = "optional", qualifier = "phone_number")
    private NavigableMap<Long, Integer> phoneNumber;

    // getters and setters
}

2. Use the generated mapper

The annotation processor generates a CitizenHBMapper class at compile time:

BestSuitCodec codec = new BestSuitCodec();
CitizenHBMapper mapper = new CitizenHBMapper(codec);

// With synchronous DAO
HBaseDAO<String, Citizen> dao = new HBaseDAO<>(connection, mapper);

Citizen citizen = new Citizen();
citizen.setCountryCode("US");
citizen.setUid(123);
citizen.setName("John");
citizen.setAge((short) 30);

// Persist
dao.persist(citizen);

// Retrieve
Citizen result = dao.get("US#123");

// Range scan
List<Citizen> range = dao.get("US#100", "US#200");

// Delete
dao.delete("US#123");

3. Async operations

AsyncHBaseDAO<String, Citizen> asyncDao = new AsyncHBaseDAO<>(asyncConnection, mapper);

asyncDao.persist(citizen)
    .thenCompose(key -> asyncDao.get(key))
    .thenAccept(c -> System.out.println(c.getName()))
    .join();

Annotations

Annotation Target Description
@Table Class Marks an HBase entity. Defines table name, namespace, and column families.
@RowKey Field Single-field row key. The field type becomes the row key type R.
@RowKeyComponent Field Part of a composite row key. Specify order and optional delimiter.
@Column Field Maps a field to a single-versioned HBase column.
@MultiVersion Field Maps a NavigableMap<Long, T> field to a multi-versioned column.
@ColumnFamily (used in @Table) Defines a column family with optional versions count.
@MappedSuperclass Class Marks a superclass whose annotated fields are inherited by @Table subclasses.
@CodecFlag (used in @Column/@MultiVersion) Key-value flag to control serialization behavior.

Row Keys

Simple row key — annotate a single field with @RowKey. The field type (e.g., Long, String) determines the generic type R:

@RowKey
private Long empid;

Composite row key — annotate multiple fields with @RowKeyComponent. Components are joined by their delimiters and the row key type is always String:

@RowKeyComponent(order = 0)
private String countryCode;

@RowKeyComponent(order = 1, delimiter = "#")
private Integer uid;
// Row key: "US#123"

Inheritance

Use @MappedSuperclass to share column definitions across entities:

@MappedSuperclass
public abstract class AbstractRecord {
    @Column(family = "a", qualifier = "created_at")
    private LocalDateTime createdAt;
}

@Table(name = "employees", families = {@ColumnFamily(name = "a")})
public class Employee extends AbstractRecord {
    @RowKey
    private Long empid;

    @Column(family = "a", qualifier = "name")
    private String empName;
}

Codec

BestSuitCodec uses native HBase Bytes utilities for primitives (String, Integer, Long, Short, Float, Double, BigDecimal, Boolean) and falls back to Jackson for complex types (including LocalDateTime via JavaTimeModule).

Force string serialization with a codec flag:

@Column(family = "optional", qualifier = "pincode",
    codecFlags = {@CodecFlag(name = BestSuitCodec.SERIALIZE_AS_STRING, value = "true")})
private Integer pincode;

Provide a custom ObjectMapper:

BestSuitCodec codec = new BestSuitCodec(customObjectMapper);

Or implement the Codec interface for fully custom serialization.

DAO Methods

HBaseDAO (synchronous)

Category Methods
Read get(rowKey), get(rowKeys), get(start, end), getByPrefix(prefix), get(Scan)
Write persist(entity), persist(entities)
Delete delete(rowKey), deleteEntity(entity), deleteByKeys(rowKeys...), deleteEntities(entities)
Atomic increment(rowKey, field, amount), append(Append)
Check exists(rowKey), exists(rowKeys...)

AsyncHBaseDAO (asynchronous)

All methods return CompletableFuture. Supports per-call Executor override to offload deserialization from Netty I/O threads:

asyncDao.get(rowKey, numVersions, customExecutor);

Batch helpers that collect all futures:

CompletableFuture<List<T>> results = asyncDao.getAll(rowKeys);
CompletableFuture<List<R>> keys = asyncDao.persistAll(entities);

Project Structure

hbase-orm/
├── hbase-orm-api/          # Annotations, Codec, DAO, Mapper interface (published)
├── hbase-orm-processor/    # Annotation processor + code generator (published)
└── hbase-orm-test/         # Example entities, unit & integration tests

Performance

Performance benchmarks run on every PR via CI. Interactive trend charts are available on GitHub Pages.

Microbenchmarks (JMH)

The ORM layer adds sub-microsecond overhead — all mapping is compile-time generated with zero reflection.

Category Operation Latency (ns/op)
Codec Native types (Integer, Long, etc.) 1–10
Codec BigDecimal ~18
Codec Jackson fallback (LocalDateTime) 60–107
Mapper Citizen full (11 fields + composite key) 320–553
Mapper Citizen minimal (key + 1 field) 37–136
Mapper Employee (inheritance + LocalDateTime) 134–225
Row Key Composite (String#Integer) 13–31
Row Key Simple (Long/String) 2–8

Load Tests (HBase Docker)

End-to-end operations against a real HBase instance. Trend charts.

Operation Sync p50 (us) Async p50 (us) Winner
Single put+get 1,729 1,291 Async (~25% faster)
Bulk put 100 1,754 8,650 Sync (~5x faster)
Prefix scan 100 1,131 973 ~Equal
Bulk get 100 2,155 1,960 ~Equal

Key insight: Use sync DAO for batch writes (single batched RPC), async DAO for single operations and concurrent workloads.

Running Locally

# JMH microbenchmarks (no Docker needed)
./gradlew :hbase-orm-test:jmh

# Load tests (requires Docker)
./gradlew :hbase-orm-test:perfTest

Building

./gradlew build

Run integration tests (requires Docker):

./gradlew intTest

Requirements

  • Java 17+
  • HBase 2.4.x

License

Apache-2.0

About

HBase ORM: compile-time annotation processor for type-safe HBase mappers

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors