FluxaORM v2: Code-Generation-Based Go ORM for MySQL, Redis, and ClickHouse
Guide
GitHub
Guide
GitHub
    • Introduction
    • Registry
    • Data Pools
    • Entities
    • Entity Fields
    • MySQL Indexes
    • Code Generation
    • Engine
    • Context
    • Entity Schema
    • Schema Update
    • CRUD Operations
    • Async Flush
    • Search
    • Redis Search
    • MySQL Queries
    • ClickHouse Queries
    • ClickHouse Schema Management
    • Kafka
    • Debezium CDC
    • Local Cache
    • Context Cache
    • Fake Delete
    • Entity Lifecycle Callbacks
    • Metrics
    • Redis Operations
    • Distributed Lock
    • Queries Log
    • Testing

Entities

In FluxaORM v2, an Entity is a Go struct that represents data stored in a MySQL database table. You define the struct, register it with a Registry, validate the configuration, and then run code generation to produce typed Provider and Entity code with getters, setters, and query methods.

Defining an Entity

To define an entity, create a Go struct with an ID field of type uint64 as the first field:

package entity

import "github.com/latolukasz/fluxaorm/v2"

type UserEntity struct {
    ID    uint64
    Name  string `orm:"required"`
    Email string `orm:"required"`
    Age   uint8
}

Every entity must have an ID uint64 field. This field maps to the primary key in the corresponding MySQL table.

Registering and Validating Entities

Before you can use entities, you must register them with a Registry and validate the configuration:

package main

import "github.com/latolukasz/fluxaorm/v2"

func main() {
    registry := fluxaorm.NewRegistry()

    // Register data pools
    registry.RegisterMySQL("user:password@tcp(localhost:3306)/mydb", fluxaorm.DefaultPoolCode, nil)
    registry.RegisterRedis("localhost:6379", 0, fluxaorm.DefaultPoolCode, nil)

    // Register entities
    registry.RegisterEntity(UserEntity{}, ProductEntity{}, CategoryEntity{})

    // Validate configuration and create engine
    engine, err := registry.Validate()
    if err != nil {
        panic(err)
    }

    // Generate typed code
    err = fluxaorm.Generate(engine, "./entities")
    if err != nil {
        panic(err)
    }
}

The workflow is:

  1. Create a Registry with fluxaorm.NewRegistry()
  2. Register MySQL and Redis pools
  3. Register entity structs with registry.RegisterEntity(...)
  4. Call registry.Validate() to verify configuration and create an Engine
  5. Call fluxaorm.Generate(engine, outputDir) to generate typed code

After Generate() runs, you get a typed Provider and Entity for each registered struct. For example, registering UserEntity produces:

  • entities.UserEntityProvider -- a Provider variable with methods like New(), GetByID(), SearchMany(), SearchOne(), etc.
  • entities.UserEntity -- a generated Entity type with GetName(), SetName(), GetEmail(), SetEmail(), etc.

MySQL Pool

By default, every entity is connected to the default MySQL pool. You can specify a different pool using the orm:"mysql=pool_name" tag on the ID field:

type UserEntity struct {
    ID uint64 // uses the "default" pool
}

type OrderEntity struct {
    ID uint64 `orm:"mysql=sales"` // uses the "sales" pool
}

Make sure to register the pool in the Registry:

registry := fluxaorm.NewRegistry()
registry.RegisterMySQL("user:password@tcp(localhost:3306)/users", fluxaorm.DefaultPoolCode, nil)
registry.RegisterMySQL("user:password@tcp(localhost:3307)/sales", "sales", nil)
registry.RegisterEntity(UserEntity{}, OrderEntity{})

Redis Cache

To protect MySQL from unnecessary queries, entities can be automatically cached in Redis. Enable Redis caching with the orm:"redisCache" tag on the ID field:

type UserEntity struct {
    ID uint64 `orm:"redisCache"` // cache in the "default" Redis pool
}

type OrderEntity struct {
    ID uint64 `orm:"redisCache=orders"` // cache in the "orders" Redis pool
}

When Redis caching is enabled, FluxaORM automatically stores entity data in Redis after loading it from MySQL. Subsequent reads are served from Redis, significantly reducing MySQL load.

Tips

To optimize Redis as a cache, set the maxmemory configuration to a value below the machine's memory size and enable the allkeys-lru eviction policy. Consider disabling persistence -- if data is lost, FluxaORM automatically refills it from MySQL.

Cache TTL

By default, Redis cache entries never expire. You can set a TTL (in seconds) using the ttl tag:

type UserEntity struct {
    ID uint64 `orm:"redisCache;ttl=30"` // Cache for 30 seconds
}

The TTL is reset every time the data is updated.

Local In-Memory Cache

To cache entity data in local memory, use the localCache tag. You can optionally specify a maximum cache size:

type CategoryEntity struct {
    ID uint64 `orm:"localCache"` // unlimited local cache
}

type ProductEntity struct {
    ID uint64 `orm:"localCache=1000"` // cache up to 1000 entities
}

Using Both Redis and Local Cache

You can enable both local and Redis caching on the same entity:

type CategoryEntity struct {
    ID uint64 `orm:"localCache;redisCache"`
}

Tips

Enabling both local and Redis caching is highly recommended for frequently accessed entities. When data is requested:

  1. FluxaORM first checks the local cache
  2. If not found locally, it checks Redis
  3. If not in Redis either, it queries MySQL, then populates both caches

This multi-layer caching greatly reduces MySQL load, especially when running on multiple servers or with autoscaling.

Custom Table Name

By default, FluxaORM uses the struct name as the MySQL table name. Override this with the table tag:

type UserEntity struct {
    ID uint64 `orm:"table=users"`
}

Soft Deletes (Fake Delete)

To enable soft deletes, add a FakeDelete bool field to your entity:

type ProductEntity struct {
    ID         uint64
    Name       string `orm:"required"`
    FakeDelete bool
}

When FakeDelete is present, calling entity.Delete() sets the FakeDelete column to 1 instead of removing the row from MySQL. All generated search methods (SearchMany, SearchOne, SearchManyWithTotal) automatically filter out soft-deleted rows unless you explicitly include them using FilterWhere(fluxaorm.NewWhere("1").WithFakeDeletes()).

Automatic Timestamps

FluxaORM automatically manages CreatedAt and UpdatedAt fields if they are defined as time.Time:

type UserEntity struct {
    ID        uint64
    Name      string    `orm:"required"`
    CreatedAt time.Time
    UpdatedAt time.Time
}
  • CreatedAt is automatically set to the current UTC time on insert (unless you set it explicitly before flushing)
  • UpdatedAt is automatically set to the current UTC time on both insert and update

References (Foreign Keys)

Use fluxaorm.Reference[T] to define a foreign key relationship to another entity:

type CategoryEntity struct {
    ID   uint64
    Name string `orm:"required"`
}

type ProductEntity struct {
    ID       uint64
    Name     string                                 `orm:"required"`
    Category fluxaorm.Reference[CategoryEntity]      `orm:"required"`
}

The Category field creates a Category bigint NOT NULL column in the ProductEntity table. After code generation, you access the reference ID via entity.GetCategoryID() and set it via entity.SetCategory(id).

To make a reference optional (nullable), omit the orm:"required" tag:

type ProductEntity struct {
    ID       uint64
    Name     string                                 `orm:"required"`
    Category fluxaorm.Reference[CategoryEntity]      // nullable reference
}

For an optional reference, the getter returns uint64: entity.GetCategoryID() returns 0 when no reference is set.

Enums and Sets

Define inline enums and sets directly in the struct tag without implementing any interface:

type OrderEntity struct {
    ID     uint64
    Status string `orm:"enum=pending,processing,shipped,delivered;required"`
    Tags   string `orm:"set=sale,featured,new;required"`
}

The enum tag creates a MySQL ENUM column, and the set tag creates a MySQL SET column.

Shared Enum Types

When multiple fields use the same set of values, use enumName to share a single generated type:

type OrderEntity struct {
    ID             uint64
    Status         string `orm:"enum=pending,processing,shipped,delivered;required"`
    PreviousStatus string `orm:"enum=pending,processing,shipped,delivered;enumName=Status"`
}

You can also share enums across entities by defining the values in one entity and referencing them from others:

type OrderEntity struct {
    ID     uint64
    Status string `orm:"enum=pending,processing,shipped,delivered;required;enumName=OrderStatus"`
}

type OrderLogEntity struct {
    ID     uint64
    Status string `orm:"enum;enumName=OrderStatus"` // no values needed — references OrderEntity
}

This works the same way for sets: orm:"set;enumName=TypeName" references a definition from another entity. Values only need to be defined once — when you add a new value, only the defining entity needs to change.

Both fields share the generated enums.Status type. The code generator creates enum types in an enums/ subdirectory with typed constants:

// Generated in enums/Status.go
package enums

type Status string
var StatusList = struct {
    Pending    Status
    Processing Status
    Shipped    Status
    Delivered  Status
}{
    Pending:    "pending",
    Processing: "processing",
    Shipped:    "shipped",
    Delivered:  "delivered",
}

After generation, you use the typed enum values:

order := entities.OrderEntityProvider.New(ctx)
order.SetStatus(enums.StatusList.Pending)

// For sets, pass variadic values
order.SetTags(enums.StatusList.Sale, enums.StatusList.Featured)

Redis Search

To enable full-text and numeric search via Redis Search, mark individual fields as searchable and optionally sortable. FluxaORM will automatically use the default Redis pool. To use a different pool, add orm:"redisSearch=pool" on the ID field:

type ProductEntity struct {
    ID    uint64
    Name  string  `orm:"required;searchable"`
    Price float64 `orm:"searchable;sortable"`
    Stock uint32  `orm:"searchable;sortable"`
}

After code generation, the Provider includes Redis Search methods like SearchManyInRedis(), SearchOneInRedis(), and SearchManyInRedisWithTotal(), along with a FieldsRedisSearch struct for type-safe query building.

Unique Indexes

Define unique indexes by implementing the EntityUniqueIndexes interface on your entity struct. The method returns a map where each key is the index name and the value is an ordered slice of column names:

type UserEntity struct {
    ID    uint64
    Name  string `orm:"required"`
    Age   uint8
    Email string `orm:"required"`
}

func (e UserEntity) UniqueIndexes() map[string][]string {
    return map[string][]string{
        "NameAge": {"Name", "Age"},
        "Email":   {"Email"},
    }
}

This creates two unique indexes: a composite NameAge index on (Name, Age) and a single-column Email index. After code generation, you can look up entities by unique indexes using SearchOne() -- it automatically detects cached unique indexes and uses the optimized lookup path:

user, found, err := entities.UserEntityProvider.SearchOne(ctx,
    fluxaorm.NewQuery().Filter(
        entities.UserEntityProvider.Fields.Name.Is("Alice"),
        entities.UserEntityProvider.Fields.Age.Eq(30),
    ),
)

user, found, err = entities.UserEntityProvider.SearchOne(ctx,
    fluxaorm.NewQuery().Filter(
        entities.UserEntityProvider.Fields.Email.Is("alice@example.com"),
    ),
)

See the MySQL Indexes page for full details on unique indexes, non-unique indexes, and caching.

Complete Example

Here is a complete example showing multiple entities with various features:

package entity

import (
    "time"

    "github.com/latolukasz/fluxaorm/v2"
)

type CategoryEntity struct {
    ID   uint64 `orm:"localCache;redisCache"`
    Name string `orm:"required"`
}

type UserEntity struct {
    ID        uint64 `orm:"redisCache"`
    Name      string `orm:"required"`
    Email     string `orm:"required"`
    Age       uint8
    Active    bool
    CreatedAt time.Time
    UpdatedAt time.Time
}

type ProductEntity struct {
    ID         uint64    `orm:"redisCache"`
    Name       string    `orm:"required"`
    Price      float64   `orm:"decimal=10,2;unsigned"`
    Status     string    `orm:"enum=draft,active,archived;required"`
    Category   fluxaorm.Reference[CategoryEntity] `orm:"required"`
    FakeDelete bool
    CreatedAt  time.Time
    UpdatedAt  time.Time
}

Struct Tags Reference

All orm struct tags available in v2:

TagDescription
mysql=poolUse a specific MySQL pool (on ID field)
table=nameCustom MySQL table name (on ID field)
redisCache / redisCache=poolEnable Redis entity cache (on ID field)
localCache / localCache=sizeEnable local in-memory cache (on ID field)
ttl=secondsRedis cache TTL in seconds (on ID field)
redisSearch=poolOverride Redis pool for Search indexing (on ID field); defaults to default when searchable fields exist
requiredNOT NULL in MySQL; for strings, prevents empty default
enum=a,b,cMySQL ENUM column with specified values
enumReference a shared enum defined in another entity (requires enumName)
set=a,b,cMySQL SET column with specified values
setReference a shared set defined in another entity (requires enumName)
enumName=TypeNameShare a generated enum type across fields or entities
timeMap time.Time to datetime instead of date
length=NSet varchar length (default 255)
length=maxUse mediumtext instead of varchar
decimal=X,YUse MySQL decimal(X,Y) for floats
unsignedUnsigned float column
mediumintUse MySQL mediumint for int32/uint32
debezium / debezium=poolEnable Debezium CDC streaming to Kafka (on ID field); defaults to default Kafka pool
searchableInclude field in Redis Search index
sortableMake field sortable in Redis Search
ignoreDo not store this field in MySQL
Edit this page
Last Updated: 3/26/26, 3:20 PM
Prev
Data Pools
Next
Entity Fields