mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-16 21:25:42 +02:00
165 lines
9.3 KiB
Markdown
165 lines
9.3 KiB
Markdown
# tinystruct データベース永続化
|
||
|
||
## 使用する場面
|
||
|
||
データベース操作にはフレームワーク組み込みのORM的なデータ層を使用します。これは `AbstractData` を拡張したPOJOを、アノテーションまたはXMLマッピングファイルでテーブルにマッピングし、JPA/Hibernateの軽量な代替として機能します。
|
||
|
||
## 動作の仕組み
|
||
|
||
### アーキテクチャ
|
||
|
||
各テーブルは以下によって表されます:
|
||
1. **Java POJO**:`AbstractData` を拡張し、getter/setter と `setData(Row)` を提供する。
|
||
2. **マッピング**、いずれか一方:
|
||
- **アノテーション**:クラスに `@Table`、フィールドに `@Column`([アノテーションマッピング](#アノテーションマッピング)を参照)。
|
||
- **マッピングXML**:リソースに置かれた `ClassName.map.xml` で、Javaフィールドとデータベースカラムを結びつける。
|
||
|
||
マッピングはクラスごとに一度だけ読み込まれてキャッシュされるため、インスタンスの生成で再読み込みや再解析は行われません。クラスに `@Table` が存在する場合、同名の `.map.xml` より優先されます。
|
||
|
||
#### 主要な基底クラス:`AbstractData`
|
||
CRUDメソッドを提供します:
|
||
- `append()` / `appendAndGetId()`
|
||
- `update()`
|
||
- `delete()`
|
||
- `findAll()` / `findOneById()` / `findOneByKey(key, value)`
|
||
- `findWith(where, params)`
|
||
- `find(SQL, params)`
|
||
|
||
### POJOの生成(CLI)
|
||
|
||
ライブのデータベーステーブルを内省してPOJOとそのマッピングを生成します。デフォルトでは `.map.xml` ファイルが、`annotation` オプションを指定するとPOJO自体にアノテーションが付与されます。
|
||
|
||
#### 設定
|
||
`application.properties`:
|
||
```properties
|
||
driver=com.mysql.cj.jdbc.Driver
|
||
database.url=jdbc:mysql://localhost:3306/mydb
|
||
database.user=root
|
||
database.password=secret
|
||
```
|
||
|
||
#### コマンド
|
||
```bash
|
||
# インタラクティブモード
|
||
bin/dispatcher generate
|
||
|
||
# テーブルを指定
|
||
bin/dispatcher generate --tables users
|
||
|
||
# .map.xml ではなくPOJOにアノテーションを付与
|
||
bin/dispatcher generate --tables users --mapping annotation
|
||
```
|
||
|
||
`--mapping` は `xml`(デフォルト)または `annotation` を受け付けます。アノテーションモードでは `.map.xml` は書き出されず、リソースディレクトリも作成されません。不明な値を指定するとプロンプトの前にエラーで拒否されます。
|
||
|
||
## 例
|
||
|
||
### CRUD操作
|
||
```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();
|
||
```
|
||
|
||
### 条件を使ったクエリ
|
||
```java
|
||
User user = new User();
|
||
Table results = user.findWith("username LIKE ?", new Object[]{"%jam%"});
|
||
|
||
// 流暢なConditionビルダー
|
||
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"}
|
||
);
|
||
```
|
||
|
||
### マッピングXMLの構造
|
||
`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>
|
||
```
|
||
|
||
### アノテーションマッピング
|
||
アノテーションを使った同じ `User`(`generate --mapping annotation` が生成するもの):
|
||
```java
|
||
import org.tinystruct.data.annotation.Column;
|
||
import org.tinystruct.data.annotation.Id;
|
||
import org.tinystruct.data.annotation.Table;
|
||
import org.tinystruct.data.component.AbstractData;
|
||
import java.io.Serializable;
|
||
|
||
@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;
|
||
|
||
// getter、setter(setFieldAsXxx を使用)、および setData(Row) を通常通り実装
|
||
}
|
||
```
|
||
|
||
| アノテーション | 対象 | 属性 |
|
||
|---|---|---|
|
||
| `@Table` | クラス | `name`(必須)、`schema`(デフォルト:なし)、`id`(デフォルト:識別子なし) |
|
||
| `@Id` | `@Table(id = ...)` 内 | `name`(デフォルト `Id`)、`column`、`type`、`length`、`autoIncrement`、`autoGenerated`。`column` が空の場合、テーブルに識別子がないことを意味する。 |
|
||
| `@Column` | フィールド | `type`(必須)、`name`(DBカラム名。デフォルトはJavaフィールド名)、`length` |
|
||
|
||
- 識別子は `AbstractData` から継承されるため、独自のフィールドに `@Id` でアノテーションするのではなく `@Table` 内で記述する。
|
||
- `type` はXMLの `type` 属性と同じ名前(`FieldType` の名前、大文字小文字を無視してマッチング)を取る:`INT`、`BIGINT`、`VARCHAR`、`TEXT`、`DATE`、`TIMESTAMP` など。
|
||
- `@Column` が付与されたフィールドのみマッピングされる。`@Table` は継承され、スーパークラスのアノテーション付きフィールドもマッピングされる。
|
||
- 同じプロパティを二重にマッピング(例:スーパークラスのアノテーション付きフィールドを隠すサブクラスフィールド)すると、そのクラスが最初に使われる際にエラーになる。
|
||
- `autoGenerated = true` は整数でないIDに新規インスタンスごとにUUIDを付与し、`autoIncrement = true` はその値をデータベースに委ねる。
|
||
- `.map.xml` の `<id>` 要素も同じ名前 `autoIncrement`・`autoGenerated` を使う。旧来の `increment`・`generate` 属性で書かれたファイルも引き続き動作する。両方の綴りが存在する場合、新しい方が優先される。
|
||
|
||
### テーブルの自動作成
|
||
`application.properties` に1行追加することで、存在しないテーブルを自動的に作成できます:
|
||
```properties
|
||
database.autocreate=true
|
||
```
|
||
|
||
- **デフォルトはオフ。** これをオンにしない限り、スキーマは何も変更されません。オンにする場合、データベースユーザーは `CREATE` 権限が必要です。
|
||
- マッピングされたクラスが最初にインスタンス化される際に、テーブルが存在しなければ作成されます。これはプロセスごと・クラスごとに一度だけ発生し、アノテーションとXMLマッピングの両方で機能し、以降のインスタンスには何も追加されません。
|
||
- `CREATE TABLE IF NOT EXISTS` という単一ステートメントが使用され、複数のインスタンスが同時に起動しても安全です(SQL Server では `OBJECT_ID` ガードが使われます)。
|
||
- `@Id` カラムが主キーになり、`autoIncrement = true` の場合はデータベース固有のオートインクリメントまたはIDENTITY構文が使われます。
|
||
- カラム型はターゲットデータベース固有の型にレンダリングされます(例:`TIMESTAMP` はMySQLでは `DATETIME`、SQL Serverでは `DATETIME2`、`TEXT` はSQL Serverでは `VARCHAR(MAX)`、`BLOB` はPostgreSQLでは `BYTEA`)。`length` なしの `VARCHAR` は255になります。
|
||
- 失敗(例:権限不足)は警告としてログに記録され、オブジェクトの生成は停止しません。最初の実際のステートメントでデータベース固有のエラーが報告されます。
|
||
|
||
注意点:
|
||
- `DECIMAL` カラムはマッピングが単一の `length` のみを持つため、精度やスケールなしで作成されます。
|
||
- `FieldType` の名前に含まれないカラム型は拒否されます。
|
||
- テーブル、カラム、主キーのみが作成されます。インデックス、外部キー、デフォルト値、他カラムへの `NOT NULL` は作成されません。
|
||
- 既存のテーブルはマッピングと異なっていても変更・マイグレーションされません。
|
||
- Redisはテーブルを持たず、サポートされていません。
|
||
|
||
## 重要なルール
|
||
|
||
1. **ファイルの配置**(XMLマッピングのみ):マッピングXMLは **`src/main/resources/`** 以下にPOJOのパッケージパスを反映した場所に置く必要があります。
|
||
2. **命名規則**:テーブル名はクラス名に単数化されます(`users` → `User`)。アンダースコアのカラムはキャメルケースのフィールドになります(`created_at` → `createdAt`)。
|
||
3. **セッター**:セッターでは(例:`setFieldAsString`)`setFieldAsXxx` メソッドを使用して内部フィールドマップと状態を同期させてください。
|
||
4. **Idフィールド**:Javaでの主キーフィールドは常に `Id` という名前です(`AbstractData` から継承)。
|
||
5. **マッピングソースは1つ**:クラスに対してアノテーションかXMLのどちらかを使用してください。両方が存在する場合、アノテーションが優先されてXMLは無視されます。
|