mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-15 04:35:29 +02:00
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>
163 lines
7.2 KiB
Markdown
163 lines
7.2 KiB
Markdown
# 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](#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`:
|
|
```properties
|
|
driver=com.mysql.cj.jdbc.Driver
|
|
database.url=jdbc:mysql://localhost:3306/mydb
|
|
database.user=root
|
|
database.password=secret
|
|
```
|
|
|
|
#### Command
|
|
```bash
|
|
# 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
|
|
```java
|
|
// 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
|
|
```java
|
|
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`:
|
|
```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):
|
|
```java
|
|
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:
|
|
```properties
|
|
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.
|