Files
ECC/skills/tinystruct-patterns/references/database.md
T
@JamesandClaude Sonnet 5 fb1f81e0d7 docs: sync tinystruct-patterns skill with latest tinystruct framework (1.7.34)
Sync skills/tinystruct-patterns against tinystruct's own canonical copy of
this skill (.agents/skills/tinystruct-patterns/), which the framework repo
keeps up to date with each release:

- Document the bin/dispatcher / bin/dispatcher.cmd launcher generation
  workflow (never hand-write or copy it; the framework generates it via
  ApplicationManager.init()).
- Add native MCP server/tool integration (org.tinystruct.mcp.*), including
  the prompt-injection warning for tool return values and overloaded-tool
  schema merging.
- Add annotation-based DB mapping (@Table/@Id/@Column) alongside XML
  mapping, the --mapping annotation generator flag, and table
  auto-creation via database.autocreate.
- Add the programmatic logging/diagnostics subsystem (ANSI console colors,
  StackWalker-based caller tracing, per-package level overrides).
- Update the architecture package map accordingly.

Also update the Japanese translation (docs/ja-JP/skills/tinystruct-patterns)
to match, which had drifted to an older, condensed version of this skill.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 21:10:57 +08:00

7.2 KiB

tinystruct Database Persistence

When to Use

Use the built-in ORM-like data layer for database operations. It provides a lightweight alternative to JPA/Hibernate using POJOs extending AbstractData, mapped to tables with annotations or XML mapping files.

How It Works

Architecture

Each table is represented by:

  1. Java POJO: Extends AbstractData, provides getters/setters and setData(Row).
  2. Mapping, either of:
    • Annotations: @Table on the class and @Column on its fields (see Annotation Mapping).
    • Mapping XML: ClassName.map.xml in resources, binding Java fields to DB columns.

The mapping is read once per class and cached, so creating an instance never re-reads or re-parses it, whichever form you use. If a class has @Table, it wins over a .map.xml of the same name.

Key Base Class: AbstractData

Provides CRUD methods:

  • append() / appendAndGetId()
  • update()
  • delete()
  • findAll() / findOneById() / findOneByKey(key, value)
  • findWith(where, params)
  • find(SQL, params)

POJO Generation (CLI)

Introspect a live database table to produce the POJO and its mapping: a .map.xml file by default, or annotations on the POJO itself.

Configuration

application.properties:

driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mydb
database.user=root
database.password=secret

Command

# Interactive mode
bin/dispatcher generate

# Specify table
bin/dispatcher generate --tables users

# Annotate the POJO instead of writing a .map.xml file
bin/dispatcher generate --tables users --mapping annotation

--mapping accepts xml (the default) or annotation. In annotation mode no .map.xml is written and no resources directory is created. An unknown value is rejected before any prompt.

Examples

CRUD Operations

// CREATE
User user = new User();
user.setUsername("james");
user.append();

// READ
User user = new User();
user.setId(42);
user.findOneById();

// UPDATE
user.setEmail("new@example.com");
user.update();

// DELETE
user.delete();

Querying with Conditions

User user = new User();
Table results = user.findWith("username LIKE ?", new Object[]{"%jam%"});

// Fluent Condition Builder
Condition condition = new Condition();
condition.setRequestFields("id,username");
Table filtered = user.find(
    condition.select("`users`").and("email LIKE ?").orderBy("id DESC"),
    new Object[]{"%@example.com"}
);

Mapping XML Structure

User.map.xml:

<mapping>
  <class name="User" table="users">
    <id name="Id" column="id" autoIncrement="true" autoGenerated="false" length="11" type="int"/>
    <property name="username" column="username" length="50" type="varchar"/>
    <property name="email" column="email" length="100" type="varchar"/>
  </class>
</mapping>

Annotation Mapping

The same User mapped with annotations (this is what generate --mapping annotation produces):

import org.tinystruct.data.annotation.Column;
import org.tinystruct.data.annotation.Id;
import org.tinystruct.data.annotation.Table;

@Table(name = "users",
        id = @Id(name = "Id", column = "id", type = "int", length = 11, autoIncrement = true))
public class User extends AbstractData implements Serializable {
    @Column(name = "username", type = "varchar", length = 50)
    private String username;

    @Column(name = "email", type = "varchar", length = 100)
    private String email;

    // getters, setters (using setFieldAsXxx) and setData(Row) as usual
}
Annotation On Attributes
@Table class name (required), schema (default none), id (default: no identifier)
@Id inside @Table(id = ...) name (default Id), column, type, length, autoIncrement, autoGenerated. An empty column means the table has no identifier.
@Column field type (required), name (the DB column; defaults to the Java field name), length
  • The identifier is described inside @Table because it is inherited from AbstractData; there is no field of your own to annotate.
  • type takes the same names as the XML type attribute (the FieldType names, matched case-insensitively): INT, BIGINT, VARCHAR, TEXT, DATE, TIMESTAMP and so on.
  • Only fields with @Column are mapped. @Table is inherited, and the annotated fields of superclasses are mapped too.
  • Mapping the same property twice, for example a subclass field that hides an annotated superclass field, fails with an error the first time the class is used.
  • autoGenerated = true gives a non-integer id a fresh UUID for every new instance; autoIncrement = true leaves the value to the database.
  • The <id> element of a .map.xml file uses the same names, autoIncrement and autoGenerated. Files written with the original increment and generate attributes keep working; if both spellings are present, the new one wins.

Auto-Creating Tables

Add one line to application.properties to have missing tables created for you:

database.autocreate=true
  • Off by default. Nothing touches your schema unless you turn this on, and the database user then needs the CREATE privilege.
  • The first time a mapped class is instantiated, its table is created if it does not exist. This happens once per class per process, works for annotation and XML mappings alike, and adds nothing to later instances.
  • It is a single statement that is safe when several instances start together: CREATE TABLE IF NOT EXISTS (SQL Server uses an OBJECT_ID guard instead).
  • The @Id column becomes the primary key, with the database's own auto-increment or identity syntax when autoIncrement = true.
  • Column types are rendered as the target database's native type, for example TIMESTAMP becomes DATETIME on MySQL and DATETIME2 on SQL Server, TEXT becomes VARCHAR(MAX) on SQL Server, and BLOB becomes BYTEA on PostgreSQL. A VARCHAR without a length gets 255.
  • A failure (for example missing privileges) is logged as a warning and never stops the object from being created; the first real statement then reports the database's own error.

Limits to keep in mind:

  • DECIMAL columns are created without precision or scale, because the mapping carries only a single length.
  • A column type that is not one of the FieldType names is rejected.
  • Only tables, columns and the primary key are created. There are no indexes, foreign keys, defaults or NOT NULL on other columns.
  • An existing table is never altered or migrated, even if it differs from the mapping.
  • Redis has no tables and is not supported.

Important Rules

  1. File Placement (XML mapping only): The mapping XML must mirror the POJO's package path under src/main/resources/.
  2. Naming: Table names are singularized for class names (users → User). Underscored columns become camelCase fields (created_at → createdAt).
  3. Setters: Use setFieldAsXxx methods (e.g., setFieldAsString) in setters to sync state with the internal field map.
  4. Id Field: The primary key field in Java is always named Id (inherited from AbstractData).
  5. One Mapping Source: Use annotations or XML for a class, not both. If both exist the annotation wins and the XML is ignored.