Entities and Typed Queries
The Klyn Entity Framework unifies typed entity mapping, persistence, schema management, and
KQL queries under klyn.data. SQL, memory, JSON, and XML sources expose the same
provider-neutral entity and query concepts without silently evaluating unsupported operations
on the client.
EntityManagerFactory owns immutable mapping metadata and shared provider state.
Each short-lived EntityManager owns an identity map, a transaction, and one unit of
work. A Repository<T, TId> is deliberately query-only: entity lifecycle operations
belong to the manager so every read and write observes one persistence context.
| Type | Responsibility |
|---|---|
EntityManagerFactory | Validated entity model, provider configuration, schema manager, and manager creation. |
EntityManager | Identity tracking, find, persist, merge, remove, transactions, and flushing. |
Repository<T, TId> | Typed source used by KQL. The key type can be inferred from the mapping. |
Query<T> | Immutable lazy query executed explicitly with toList(), first(), count(), or stream(). |
A query: block is parsed and type-checked by the compiler. Unsupported expressions
raise a translation diagnostic; Klyn never hides a provider limitation by downloading rows
and evaluating an arbitrary method locally.
Import klyn.data.mapping for provider-neutral entity semantics and
klyn.data.sql for relational bindings. Matching class, property, table, and column
names are inferred; annotate only constraints or 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 is required by the current materializer. The same
@Entity, @Id, embedding, inheritance, version, and relationship
annotations can be shared by providers; physical bindings such as @Table,
@Column, and @JoinColumn remain provider-specific.
SQLConfiguration selects an official direct SQL driver and its matching dialect.
Klyn currently accepts mariadb, postgresql, and sqlserver.
ODBC uses the separate ODBCConfiguration type and requires an explicit dialect.
import klyn.data
import klyn.data.sql
configuration = SQLConfiguration(
driver="mariadb",
host="localhost",
port=3306,
database="klyn",
username="klyn",
password="secret"
)
try factory = EntityManagerFactory(configuration):
print(factory.mappedTypes)
The factory discovers linked @Entity classes once and keeps that model immutable.
It is thread-safe; an EntityManager is intentionally not thread-safe and must stay
inside one request, command, or worker.
The factory-owned SchemaManager creates, drops, validates, and compares provider
structures from the same mapping metadata used by queries and persistence.
try factory = EntityManagerFactory(configuration):
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)
create(), drop(), exists(), and validate() also
have no-argument forms that operate on every entity known to the factory. Existing structures
are validated rather than altered silently.
Use try-with-resources for both factory and manager. Register mutations on the manager, flush
with saveChanges(), and commit or roll back the manager-owned transaction.
try factory = EntityManagerFactory(configuration),
manager = factory.createEntityManager():
transaction = manager.transaction
transaction.begin()
try:
manager.persist(Article("Mechanical keyboard", 129.90))
manager.persist(Article("Desk lamp", 42.00))
manager.saveChanges()
transaction.commit()
catch error as Exception:
if transaction.active:
transaction.rollback()
throw error
Use manager.find<Article>(id) for identity lookup,
manager.merge(detached) for detached state, and manager.remove(entity)
for deletion. Repeated loads of the same identity within one manager return its managed
instance.
Obtain a repository from the manager, then build an immutable query with the native
query: expression. repository<Article>() infers the key type from the
unique @Id; repository<Article, Int>() is the mandatory explicit form.
minimum = 50.0
maximum = 150.0
try factory = EntityManagerFactory(configuration),
manager = factory.createEntityManager():
articles = manager.repository<Article>()
selected as Query<Article> = query:
from articles article
where article.price >= minimum and article.price <= maximum
order by article.price asc
for article in selected.toList():
print(article.name + ": " + String(article.price))
Query variables and result types are checked at compile time. Execution remains lazy until a
terminal operation is called. Scalar and immutable captures are copied when the query is
created; use Parameter<T> for an explicitly late-bound value.
| Provider | 0.1.5 role |
|---|---|
| SQL | Typed KQL, identity tracking, CRUD, transactions, and schema management. |
| Memory | Typed repositories and in-memory KQL over mapped entities. |
| JSON | Read-only typed repository with JSON field/path mapping. |
| XML | Read-only typed repository with XML element/attribute mapping. |
Provider capabilities are explicit. Applying an annotation or operation that the active source cannot honor raises a mapping, translation, or capability exception as early as possible.
The unified mapping package includes @OneToOne, @OneToMany,
@ManyToOne, @ManyToMany, cascades, fetch strategies, join metadata,
ordering, embedded values, inheritance, and optimistic versioning. Their defaults follow JPA
conventions. A provider must reject relationship behavior it cannot implement; it must never
return a silently incomplete graph.
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="categoryId", nullable=false)
public property category as Category
samples/sql/Entities.kn is a complete MariaDB desktop application. It creates an
article table through SchemaManager, inserts ten entities in one transaction, and
executes a typed KQL price query when the user presses Search.
klyn samples/sql/Entities.kn
- Keep one manager per logical unit of work and never share it between threads.
- Use repositories for typed queries and the manager for every entity mutation.
- Keep transactions short and always roll back the exceptional path.
- Never log credentials or construct raw SQL from untrusted input.
- Use
explain()ortoProviderText()for redacted query diagnostics.