Databases & Entities Entity Engine Static mapping

Entity Engine

The Klyn Entity Engine provides one statically typed model for entities, identities, queries, relationships, schema management, and persistence. Relational databases are its most complete writable provider, but the same entity concepts also apply to XML, JSON, and in-memory sources.

More Than an ORM

An object-relational mapper associates application objects with relational tables and rows. The Entity Engine includes that role for SQL databases, then extends the same typed entity and query model to sources that have no table at all. An entity is therefore a mapped domain object, not a synonym for a database row.

ProviderEntity Engine role
SQLTracked entities, transactions, CRUD, schema management, relationships, inheritance, and translated KQL.
XMLRead-only typed entities selected and mapped from one atomic XML document generation.
JSONRead-only typed entities selected and mapped with JSONPath expressions.
MemoryTyped repositories over existing collections, with key validation and KQL execution.

Provider capabilities remain explicit. A mapping or operation that the selected provider cannot honor raises a mapping, translation, or capability exception; Klyn never silently changes the execution model.

Programming Model
TypeResponsibility
ProviderConfigurationImmutable source-specific connection or document settings.
EntityManagerFactoryThread-safe owner of the validated entity model, provider metadata, and schema manager.
EntityManagerShort-lived, non-thread-safe persistence context with identity tracking and one unit of work.
Repository<T, TId>Typed query root. The identity type can be inferred from the effective mapping.
Query<T>Immutable lazy query executed by an explicit terminal operation.
SchemaManagerPhysical schema creation, removal, validation, comparison, and migration-plan application.
One manager per unit of work

A factory can be shared safely. An EntityManager must stay inside one request, command, or worker and must never be shared between threads.

Entity Mapping

Provider-neutral annotations live in klyn.data.mapping. SQL bindings live in klyn.data.sql. Matching property and column names are inferred, so annotations should describe only identity, constraints, relationships, generation, or physical names that differ.

import klyn.data.mapping
import klyn.data.sql

@Entity
@Table(name="T_Articles")
class Article:

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public property id as Int = 0

    @Column(nullable=false, length=120)
    public property name as String

    @Column(nullable=false, precision=12, scale=2)
    public property price as Double

    public Article():
        pass

    public Article(name as String, price as Double):
        this.name = name
        this.price = price

The parameterless constructor lets the materializer instantiate the entity. The effective key comes from @Id, @EmbeddedId, or @IdClass. Every mapped property is validated against the selected provider before data access begins.

Configuration and Allowed Entities

The configuration selects the provider. The entities argument separately defines the complete and authoritative model visible to the factory. This explicit boundary prevents an unrelated annotated class on the classpath from changing a schema or query unexpectedly.

import klyn.data
import klyn.data.sql

configuration = SQLConfiguration(
    url="mariadb://localhost:3306/klyn",
    login="klyn",
    password="secret",
    showSQL=false,
    formatSQL=false
)

try factory = EntityManagerFactory(
    configuration,
    entities=[Blog, Post]
):
    assert Blog.type in factory.mappedTypes
    assert Post.type in factory.mappedTypes

A class name used as a value is its reflection Type, so [Blog, Post] is the concise form of [Blog.type, Post.type]. The list is copied, validated, ordered deterministically, and then kept immutable. Every relationship target must belong to this model.

SQLConfiguration argumentMeaning
urlDatabase URL. MariaDB, PostgreSQL, and SQL Server select their dialect automatically.
login / passwordCredentials passed to the SQL driver. Never embed production secrets in source.
dialectOptional explicit SQL dialect; required when an ODBC URL cannot identify server semantics.
showSQLLogs generated SQL when enabled. Defaults to false.
formatSQLFormats logged SQL for readability. Defaults to false.
queryTimeoutSecondsProvider statement timeout in seconds; 0 means no explicit deadline.
odbcConfiguration = SQLConfiguration(
    url="odbc:DSN=Reporting",
    login="reporter",
    password="secret",
    dialect=PostgreSQLDialect()
)
Schema Management

The factory-owned schema manager uses the same validated mappings as queries and persistence. No-argument operations address the complete entity model; overloads receiving a Type address one entity.

schema = factory.schema

if not schema.exists(Article.type):
    schema.create(Article.type)

schema.validate(Article.type)

plan = schema.diff()
if not plan.empty:
    print(plan.preview)
    schema.apply(plan)

Existing structures are validated rather than altered silently. Inspect a schema plan before applying it, especially when plan.destructive is true.

Transactions and Entity Lifecycle

Entity mutations belong to the manager. Use try-with-resources, keep transactions short, flush with saveChanges(), and roll back every exceptional path.

try factory = EntityManagerFactory(configuration, entities=[Article]),
    em = factory.createEntityManager():

    transaction = em.beginTransaction()
    try:
        article = Article("Mechanical keyboard", 129.90)
        em.persist(article)
        assert em.saveChanges() == 1
        transaction.commit()
    catch error as Exception:
        if transaction.active:
            transaction.rollback()
        throw error

    loaded = em.find<Article>(article.id)
    loaded.price = 119.90
    em.saveChanges()

    em.remove(loaded)
    em.saveChanges()

find<T>(id) first checks the identity map. persist() registers a new entity, merge() copies detached state into a managed instance, remove() schedules deletion, and refresh() reloads provider state.

Preferred Typed Query

For manager-backed entity work, prefer query(em):. The compiler resolves the mapped entity, infers the exact result type, validates every member, and lowers the query to the provider-neutral query representation before the SQL provider emits parameterized SQL.

minimum = 50.0
maximum = 150.0

selected = query(em):
    from Article
    where price >= minimum and price <= maximum
    order by price asc

for article in selected.toList():
    print(f"{article.name}: {article.price}")

The repository form remains useful for provider-independent functions and direct document repositories. See Entity Engine KQL for the complete query syntax and execution model.

Other Entity Sources

Relational examples dominate this introduction because SQL currently exposes the complete writable lifecycle. The mapping model also supports XML entities, JSON entities, and in-memory entities. These pages document their source-specific annotations, validation, and capability boundaries.

Complete Sample

samples/sql/Entities.kn builds a MariaDB article catalog, creates its schema, inserts ten entities transactionally, and executes a typed price query from a desktop GUI.

klyn samples/sql/Entities.kn