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>
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:
- Java POJO: Extends
AbstractData, provides getters/setters andsetData(Row). - Mapping, either of:
- Annotations:
@Tableon the class and@Columnon its fields (see Annotation Mapping). - Mapping XML:
ClassName.map.xmlin resources, binding Java fields to DB columns.
- Annotations:
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
@Tablebecause it is inherited fromAbstractData; there is no field of your own to annotate. typetakes the same names as the XMLtypeattribute (theFieldTypenames, matched case-insensitively):INT,BIGINT,VARCHAR,TEXT,DATE,TIMESTAMPand so on.- Only fields with
@Columnare mapped.@Tableis 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 = truegives a non-integer id a fresh UUID for every new instance;autoIncrement = trueleaves the value to the database.- The
<id>element of a.map.xmlfile uses the same names,autoIncrementandautoGenerated. Files written with the originalincrementandgenerateattributes 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
CREATEprivilege. - 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 anOBJECT_IDguard instead). - The
@Idcolumn becomes the primary key, with the database's own auto-increment or identity syntax whenautoIncrement = true. - Column types are rendered as the target database's native type, for example
TIMESTAMPbecomesDATETIMEon MySQL andDATETIME2on SQL Server,TEXTbecomesVARCHAR(MAX)on SQL Server, andBLOBbecomesBYTEAon PostgreSQL. AVARCHARwithout 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:
DECIMALcolumns are created without precision or scale, because the mapping carries only a singlelength.- A column type that is not one of the
FieldTypenames is rejected. - Only tables, columns and the primary key are created. There are no indexes, foreign keys, defaults or
NOT NULLon 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
- File Placement (XML mapping only): The mapping XML must mirror the POJO's package path under
src/main/resources/. - Naming: Table names are singularized for class names (
users→User). Underscored columns become camelCase fields (created_at→createdAt). - Setters: Use
setFieldAsXxxmethods (e.g.,setFieldAsString) in setters to sync state with the internal field map. - Id Field: The primary key field in Java is always named
Id(inherited fromAbstractData). - One Mapping Source: Use annotations or XML for a class, not both. If both exist the annotation wins and the XML is ignored.