Skip to content

Commit fc19218

Browse files
authored
Update AI content. (#93) (#105)
1 parent cf070a5 commit fc19218

4 files changed

Lines changed: 358 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
Help the user work with JSON columns in Storm entities using Java.
2+
3+
Fetch https://orm.st/llms-full.txt for complete reference.
4+
5+
Ask: what data they want to store as JSON and whether they need JSON aggregation.
6+
7+
## JSON Columns
8+
9+
Annotate a field with `@Json` to store it as a JSON column. Storm auto-detects Jackson at runtime.
10+
11+
```java
12+
record User(@PK Integer id,
13+
String email,
14+
@Json Map<String, String> preferences
15+
) implements Entity<Integer> {}
16+
```
17+
18+
## Complex Types
19+
20+
JSON columns can store structured domain objects, not just maps and primitives. Jackson handles records automatically without additional annotations.
21+
22+
```java
23+
record Address(String street, String city, String postalCode) {}
24+
25+
record User(@PK Integer id,
26+
@Json Address address
27+
) implements Entity<Integer> {}
28+
```
29+
30+
## JSON Aggregation
31+
32+
Use JSON aggregation functions to load one-to-many relationships in a single query:
33+
34+
```java
35+
record RolesByUser(User user, @Json List<Role> roles) implements Data {}
36+
37+
var results = orm.entity(User.class)
38+
.select(RolesByUser.class, RAW."\{User.class}, JSON_OBJECTAGG(\{Role.class})")
39+
.innerJoin(UserRole.class).on(User.class)
40+
.groupBy(User_.id)
41+
.getResultList();
42+
```
43+
44+
## Dependencies
45+
46+
Storm supports two Jackson modules for Java (add one):
47+
- `storm-jackson2` - Jackson 2.17+ (Spring Boot 3.x)
48+
- `storm-jackson3` - Jackson 3.0+ (Spring Boot 4+)
49+
50+
Storm auto-detects Jackson at runtime. Just add the dependency.
51+
52+
## Database Support
53+
54+
### Column types
55+
56+
When writing migrations, use the correct JSON column type for the target database:
57+
58+
| Database | Column Type | Notes |
59+
|----------|-------------|-------|
60+
| PostgreSQL | `JSONB` | Binary format, indexable |
61+
| MySQL | `JSON` | Native JSON type |
62+
| MariaDB | `JSON` | Alias for LONGTEXT with validation |
63+
| Oracle | `JSON` | Native JSON (21c+) |
64+
| MS SQL Server | `NVARCHAR(MAX)` | Stored as text |
65+
| H2 | `CLOB` | Stored as text (test databases) |
66+
67+
### JSON aggregation functions
68+
69+
JSON aggregation syntax differs by database. Always ask or detect which dialect the user is targeting:
70+
71+
| Database | Object aggregation | Array aggregation |
72+
|----------|-------------------|-------------------|
73+
| PostgreSQL | `JSON_OBJECT_AGG(key, value)` | `JSON_AGG(value)` |
74+
| MySQL | `JSON_OBJECTAGG(key, value)` | `JSON_ARRAYAGG(value)` |
75+
| MariaDB | `JSON_OBJECTAGG(key, value)` | `JSON_ARRAYAGG(value)` |
76+
| Oracle | `JSON_OBJECTAGG(KEY key VALUE value)` | `JSON_ARRAYAGG(value)` |
77+
| MS SQL Server | Manual via `FOR JSON` | Manual via `FOR JSON` |
78+
| H2 | Not supported | Not supported |
79+
80+
H2 does not support JSON aggregation functions. Tests that use JSON aggregation need a real database or should verify only the generated SQL using `SqlCapture` without executing the query.
81+
82+
## Rules
83+
84+
- Use JSON for truly dynamic or denormalized data, not to avoid proper schema design.
85+
- JSON aggregation is suitable for moderate-size collections (< 100 items, < 1MB). For large or unbounded collections, use separate queries.
86+
- `@Json` fields are harder to filter and index than normalized columns. Consider query patterns before choosing JSON.
87+
- Always check the target database dialect before writing JSON aggregation queries. The function names and syntax vary.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Help the user work with JSON columns in Storm entities using Kotlin.
2+
3+
Fetch https://orm.st/llms-full.txt for complete reference.
4+
5+
Ask: what data they want to store as JSON, which serialization library (Jackson or kotlinx.serialization), and whether they need JSON aggregation.
6+
7+
## JSON Columns
8+
9+
Annotate a field with `@Json` to store it as a JSON column. Storm auto-detects the serialization library at runtime.
10+
11+
```kotlin
12+
data class User(
13+
@PK val id: Int = 0,
14+
val email: String,
15+
@Json val preferences: Map<String, String>
16+
) : Entity<Int>
17+
```
18+
19+
## Complex Types
20+
21+
JSON columns can store structured domain objects, not just maps and primitives.
22+
23+
With kotlinx.serialization, annotate the nested type with `@Serializable`. Jackson discovers types automatically through reflection.
24+
25+
```kotlin
26+
@Serializable // For kotlinx.serialization
27+
data class Address(val street: String, val city: String, val postalCode: String)
28+
29+
data class User(
30+
@PK val id: Int = 0,
31+
@Json val address: Address
32+
) : Entity<Int>
33+
```
34+
35+
## JSON Aggregation
36+
37+
Use JSON aggregation functions to load one-to-many relationships in a single query:
38+
39+
```kotlin
40+
data class RolesByUser(val user: User, @Json val roles: List<Role>) : Data
41+
42+
val results = orm.entity(User::class)
43+
.select(RolesByUser::class) { "${User::class}, JSON_OBJECTAGG(${Role::class})" }
44+
.innerJoin(UserRole::class).on(User::class)
45+
.groupBy(User_.id)
46+
.resultList
47+
```
48+
49+
## Dependencies
50+
51+
Storm supports two JSON libraries for Kotlin (add one):
52+
- `storm-jackson2` - Jackson 2.17+ (Spring Boot 3.x)
53+
- `storm-jackson3` - Jackson 3.0+ (Spring Boot 4+)
54+
- `storm-kotlinx-serialization` - Kotlinx Serialization (requires `plugin.serialization` Gradle plugin)
55+
56+
Storm auto-detects the library at runtime. Just add the dependency.
57+
58+
## Database Support
59+
60+
### Column types
61+
62+
When writing migrations, use the correct JSON column type for the target database:
63+
64+
| Database | Column Type | Notes |
65+
|----------|-------------|-------|
66+
| PostgreSQL | `JSONB` | Binary format, indexable |
67+
| MySQL | `JSON` | Native JSON type |
68+
| MariaDB | `JSON` | Alias for LONGTEXT with validation |
69+
| Oracle | `JSON` | Native JSON (21c+) |
70+
| MS SQL Server | `NVARCHAR(MAX)` | Stored as text |
71+
| H2 | `CLOB` | Stored as text (test databases) |
72+
73+
### JSON aggregation functions
74+
75+
JSON aggregation syntax differs by database. Always ask or detect which dialect the user is targeting:
76+
77+
| Database | Object aggregation | Array aggregation |
78+
|----------|-------------------|-------------------|
79+
| PostgreSQL | `JSON_OBJECT_AGG(key, value)` | `JSON_AGG(value)` |
80+
| MySQL | `JSON_OBJECTAGG(key, value)` | `JSON_ARRAYAGG(value)` |
81+
| MariaDB | `JSON_OBJECTAGG(key, value)` | `JSON_ARRAYAGG(value)` |
82+
| Oracle | `JSON_OBJECTAGG(KEY key VALUE value)` | `JSON_ARRAYAGG(value)` |
83+
| MS SQL Server | Manual via `FOR JSON` | Manual via `FOR JSON` |
84+
| H2 | Not supported | Not supported |
85+
86+
H2 does not support JSON aggregation functions. Tests that use JSON aggregation need a real database or should verify only the generated SQL using `SqlCapture` without executing the query.
87+
88+
## Rules
89+
90+
- Use JSON for truly dynamic or denormalized data, not to avoid proper schema design.
91+
- JSON aggregation is suitable for moderate-size collections (< 100 items, < 1MB). For large or unbounded collections, use separate queries.
92+
- `@Json` fields are harder to filter and index than normalized columns. Consider query patterns before choosing JSON.
93+
- Always check the target database dialect before writing JSON aggregation queries. The function names and syntax vary.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
Help the user serialize Storm entities to JSON for REST APIs using Java.
2+
3+
Fetch https://orm.st/llms-full.txt for complete reference.
4+
5+
This is about serializing entities for API responses (Jackson), not about JSON database columns (use /storm-json-java for that).
6+
7+
Ask: whether entities have `Ref<T>` fields and whether they use Spring Boot.
8+
9+
## When You Need the Storm Module
10+
11+
Entities without `Ref` fields serialize with plain Jackson. No Storm module needed.
12+
13+
Entities with `Ref<T>` fields require the Storm serialization module, because `Ref` can be unloaded (only the FK ID) or loaded (full entity/projection). Jackson cannot handle this distinction without help.
14+
15+
## Setup
16+
17+
Register `StormModule` on the `ObjectMapper`:
18+
```java
19+
ObjectMapper mapper = new ObjectMapper();
20+
mapper.registerModule(new StormModule());
21+
```
22+
23+
Spring Boot auto-detects Module beans:
24+
```java
25+
@Configuration
26+
public class JacksonConfig {
27+
@Bean
28+
public StormModule stormModule() {
29+
return new StormModule();
30+
}
31+
}
32+
```
33+
34+
`StormModule` is in `st.orm.jackson`, available in both `storm-jackson2` (Jackson 2.17+, Spring Boot 3.x) and `storm-jackson3` (Jackson 3.0+, Spring Boot 4+). Choose the module that matches your Jackson version.
35+
36+
## Ref Serialization Format
37+
38+
| Ref state | JSON output | Example |
39+
|-----------|-------------|---------|
40+
| Unloaded | Raw primary key | `1` |
41+
| Loaded entity | `{"@entity": {...}}` | `{"@entity": {"id": 1, "name": "Betty"}}` |
42+
| Loaded projection | `{"@id": ..., "@projection": {...}}` | `{"@id": 1, "@projection": {"id": 1, "name": "Betty"}}` |
43+
| Null | `null` | `null` |
44+
45+
The format is fully round-trippable.
46+
47+
## Rules
48+
49+
- Refs deserialized from JSON are **detached**: they carry the ID but have no database connection. Calling `fetch()` on a deserialized ref throws `PersistenceException`. Use the deserialized ID to query the database directly.
50+
- Entities without `Ref` fields need no Storm module registration.
51+
- Both Jackson modules (`storm-jackson2`, `storm-jackson3`) provide the same `StormModule` API.
52+
- Jackson supports `java.time` natively via the `jackson-datatype-jsr310` module (included by Spring Boot).
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
Help the user serialize Storm entities to JSON for REST APIs using Kotlin.
2+
3+
Fetch https://orm.st/llms-full.txt for complete reference.
4+
5+
This is about serializing entities for API responses or caching (Jackson, kotlinx.serialization), not about JSON database columns (use /storm-json-kotlin for that).
6+
7+
Ask: which serialization library (Jackson or kotlinx.serialization), whether entities have `Ref<T>` fields, and whether they use Spring Boot or Redis caching.
8+
9+
## When You Need the Storm Module
10+
11+
Entities without `Ref` fields serialize with plain Jackson or kotlinx.serialization. No Storm module needed.
12+
13+
Entities with `Ref<T>` fields require the Storm serialization module, because `Ref` can be unloaded (only the FK ID) or loaded (full entity/projection). Standard libraries cannot handle this distinction without help.
14+
15+
## Jackson Setup
16+
17+
Register `StormModule` on the `ObjectMapper`:
18+
```kotlin
19+
val mapper = ObjectMapper()
20+
mapper.registerModule(StormModule())
21+
```
22+
23+
Spring Boot auto-detects Module beans:
24+
```kotlin
25+
@Configuration
26+
class JacksonConfig {
27+
@Bean
28+
fun stormModule(): StormModule = StormModule()
29+
}
30+
```
31+
32+
`StormModule` is in `st.orm.jackson`, available in both `storm-jackson2` and `storm-jackson3`.
33+
34+
## Kotlinx Serialization Setup
35+
36+
```kotlin
37+
val json = Json {
38+
serializersModule = StormSerializersModule()
39+
}
40+
```
41+
42+
Or use the pre-built convenience instance:
43+
```kotlin
44+
val json = Json {
45+
serializersModule = StormSerializers
46+
}
47+
```
48+
49+
**Critical**: Every `Ref` field in a `@Serializable` class must be annotated with `@Contextual`:
50+
```kotlin
51+
@Serializable
52+
data class Pet(
53+
@PK val id: Int = 0,
54+
val name: String,
55+
@FK @Contextual val owner: Ref<Owner>?
56+
) : Entity<Int>
57+
```
58+
59+
For collections of refs, annotate both the field and the type argument:
60+
```kotlin
61+
@Serializable
62+
data class TeamMembers(
63+
@Contextual val members: List<@Contextual Ref<User>>
64+
)
65+
```
66+
67+
Without `@Contextual`, the kotlinx compiler plugin tries to serialize `Ref` directly and fails at runtime with "RefImpl is not found in the polymorphic scope".
68+
69+
## Kotlinx Serialization Cascade Rule
70+
71+
When an entity is annotated with `@Serializable`, all entities reachable from it must also be `@Serializable`. This includes entities referenced via `@FK` fields (both direct entity fields and `Ref<T>` fields). For `Ref<T>`, the target type `T` must be `@Serializable`; `StormSerializers` handles the `Ref` wrapper itself.
72+
73+
```kotlin
74+
@Serializable
75+
data class Order(
76+
@PK val id: Int = 0,
77+
@FK val customer: Customer, // Customer must be @Serializable
78+
@FK @Contextual val product: Ref<Product>?, // Product must be @Serializable
79+
) : Entity<Int>
80+
```
81+
82+
### Java time types
83+
84+
Kotlinx.serialization does not support `java.time` types out of the box. If your entities contain `Instant`, `LocalDate`, `LocalTime`, or similar types, you need custom serializers:
85+
86+
```kotlin
87+
@Serializable
88+
data class Event(
89+
@PK val id: Int = 0,
90+
@Serializable(with = InstantAsStringSerializer::class) val timestamp: Instant,
91+
@Serializable(with = LocalDateAsStringSerializer::class) val eventDate: LocalDate,
92+
) : Entity<Int>
93+
```
94+
95+
This does not apply to Jackson, which supports `java.time` natively (with the `jackson-datatype-jsr310` module, included by Spring Boot).
96+
97+
## Caching (Redis, etc.)
98+
99+
Entities cached in external stores like Redis need full serialization support. When using kotlinx.serialization with Redis (e.g., `KotlinxSerializationRedisSerializer`), configure the serializer with `StormSerializers` to handle `Ref<T>`:
100+
101+
```kotlin
102+
val serializer = KotlinxSerializationRedisSerializer(
103+
Json { serializersModule = StormSerializers }
104+
)
105+
```
106+
107+
The same cascade rule applies: all entities reachable from a cached entity must be `@Serializable`, and all `Ref<T>` fields must have `@Contextual`.
108+
109+
## Ref Serialization Format
110+
111+
| Ref state | JSON output | Example |
112+
|-----------|-------------|---------|
113+
| Unloaded | Raw primary key | `1` |
114+
| Loaded entity | `{"@entity": {...}}` | `{"@entity": {"id": 1, "name": "Betty"}}` |
115+
| Loaded projection | `{"@id": ..., "@projection": {...}}` | `{"@id": 1, "@projection": {"id": 1, "name": "Betty"}}` |
116+
| Null | `null` | `null` |
117+
118+
The format is fully round-trippable. Jackson and kotlinx.serialization produce identical JSON.
119+
120+
## Rules
121+
122+
- Refs deserialized from JSON are **detached**: they carry the ID but have no database connection. Calling `fetch()` on a deserialized ref throws `PersistenceException`. Use the deserialized ID to query the database directly.
123+
- Entities without `Ref` fields need no Storm module registration.
124+
- Both Jackson modules (`storm-jackson2`, `storm-jackson3`) provide the same `StormModule` API.
125+
- **Kotlinx cascade**: if an entity is `@Serializable`, every entity it references (directly or via `Ref<T>`) must also be `@Serializable`.
126+
- **`@Contextual` on `Ref<T>`**: without it, the kotlinx compiler plugin tries to serialize `Ref` directly and fails at runtime with "RefImpl is not found in the polymorphic scope".

0 commit comments

Comments
 (0)