Skip to content

Commit cf425a0

Browse files
This commit adds documentation for the new Typed API, which allows users to work directly with POJOs. It includes examples for writing, reading, and performing lookups, as well as a note on performance considerations.
1 parent 3be2f3d commit cf425a0

File tree

2 files changed

+173
-2
lines changed

2 files changed

+173
-2
lines changed

website/docs/apis/java-client.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ List<User> users = List.of(
170170
);
171171
```
172172

173-
**Note:** Currently data in Fluss is written in the form of `rows`, so we need to convert our POJO to `GenericRow`, while the Fluss community is working to provide
174-
a more user-friendly API for writing data.
173+
**Note:** Currently data in Fluss is written in the form of `rows`, so we need to convert our POJO to `GenericRow`. For a more user-friendly API for writing data, please refer to the [Java Typed API](./java-typed-api.md) documentation.
175174
```java
176175
Table table = connection.getTable(tablePath);
177176

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
---
2+
title: Java Typed API
3+
sidebar_position: 3
4+
---
5+
6+
# Java Typed API
7+
8+
Fluss provides a Typed API that allows you to work directly with Java POJOs (Plain Old Java Objects) instead of `InternalRow` objects. This simplifies development by automatically mapping your Java classes to Fluss table schemas.
9+
10+
:::info
11+
The Typed API provides a more user-friendly experience but comes with a performance cost due to the overhead of converting between POJOs and internal row formats. For high-performance use cases, consider using the lower-level `InternalRow` API.
12+
:::
13+
14+
## Defining POJOs
15+
16+
To use the Typed API, define a Java class where the field names and types match your Fluss table schema.
17+
18+
```java
19+
public class User {
20+
public Integer id;
21+
public String name;
22+
public Integer age;
23+
24+
public User() {}
25+
26+
public User(Integer id, String name, Integer age) {
27+
this.id = id;
28+
this.name = name;
29+
this.age = age;
30+
}
31+
32+
// Getters, setters, equals, hashCode, toString...
33+
}
34+
```
35+
36+
The supported type mappings are:
37+
38+
| Fluss Type | Java Type |
39+
|---|---|
40+
| INT | Integer |
41+
| BIGINT | Long |
42+
| STRING | String |
43+
| BOOLEAN | Boolean |
44+
| FLOAT | Float |
45+
| DOUBLE | Double |
46+
| DECIMAL | BigDecimal |
47+
| DATE | LocalDate |
48+
| TIME | LocalTime |
49+
| TIMESTAMP | LocalDateTime |
50+
| TIMESTAMP_LTZ | Instant |
51+
| BINARY / BYTES | byte[] |
52+
53+
## Writing Data
54+
55+
### Append Writer
56+
57+
For append-only tables (Log tables), use `TypedAppendWriter`.
58+
59+
```java
60+
TablePath path = TablePath.of("my_db", "users_log");
61+
try (Table table = conn.getTable(path)) {
62+
TypedAppendWriter<User> writer = table.newAppend().createTypedWriter(User.class);
63+
64+
writer.append(new User(1, "Alice", 30));
65+
writer.append(new User(2, "Bob", 25));
66+
67+
writer.flush();
68+
}
69+
```
70+
71+
### Upsert Writer
72+
73+
For primary key tables, use `TypedUpsertWriter`.
74+
75+
```java
76+
TablePath path = TablePath.of("my_db", "users_pk");
77+
try (Table table = conn.getTable(path)) {
78+
TypedUpsertWriter<User> writer = table.newUpsert().createTypedWriter(User.class);
79+
80+
// Insert or Update
81+
writer.upsert(new User(1, "Alice", 31));
82+
83+
// Delete
84+
writer.delete(new User(1, null, null)); // Only PK fields are needed for delete
85+
86+
writer.flush();
87+
}
88+
```
89+
90+
### Partial Updates
91+
92+
You can perform partial updates by specifying the columns to update.
93+
94+
```java
95+
// Update only 'name' and 'age' for the user with id 1
96+
Upsert upsert = table.newUpsert().partialUpdate("name", "age");
97+
TypedUpsertWriter<User> writer = upsert.createTypedWriter(User.class);
98+
99+
User partialUser = new User();
100+
partialUser.id = 1;
101+
partialUser.name = "Alice Updated";
102+
partialUser.age = 32;
103+
104+
writer.upsert(partialUser);
105+
writer.flush();
106+
```
107+
108+
## Reading Data
109+
110+
Use `TypedLogScanner` to read data as POJOs.
111+
112+
```java
113+
Scan scan = table.newScan();
114+
TypedLogScanner<User> scanner = scan.createTypedLogScanner(User.class);
115+
116+
try (CloseableIterator<TypedScanRecord<User>> iterator = scanner.subscribeFromBeginning()) {
117+
while (iterator.hasNext()) {
118+
TypedScanRecord<User> record = iterator.next();
119+
ChangeType changeType = record.getChangeType();
120+
User user = record.getValue();
121+
122+
System.out.println(changeType + ": " + user);
123+
}
124+
}
125+
```
126+
127+
### Projections
128+
129+
You can also use projections with the Typed API. The POJO fields that are not part of the projection will be null.
130+
131+
```java
132+
// Only read 'id' and 'name'
133+
TypedLogScanner<User> scanner = table.newScan()
134+
.project("id", "name")
135+
.createTypedLogScanner(User.class);
136+
```
137+
138+
## Lookups
139+
140+
For primary key tables, you can perform lookups using a POJO that represents the primary key.
141+
142+
```java
143+
// Define a POJO for the key
144+
public class UserId {
145+
public Integer id;
146+
147+
public UserId(Integer id) { this.id = id; }
148+
}
149+
150+
// Create a TypedLookuper
151+
TypedLookuper<UserId> lookuper = table.newLookup().createTypedLookuper(UserId.class);
152+
153+
// Perform lookup
154+
CompletableFuture<LookupResult> resultFuture = lookuper.lookup(new UserId(1));
155+
LookupResult result = resultFuture.get();
156+
157+
if (result != null) {
158+
// Convert the result row back to a User POJO if needed
159+
// Note: You might need a RowToPojoConverter for this part if you want the full User object
160+
// or you can access fields from the InternalRow directly.
161+
}
162+
```
163+
164+
## Performance Considerations
165+
166+
While the Typed API offers convenience and type safety, it involves an additional layer of conversion between your POJOs and Fluss's internal binary row format (`InternalRow`). This conversion process (serialization and deserialization) introduces CPU overhead.
167+
168+
Benchmarks indicate that using the Typed API can be roughly **2x slower** than using the `InternalRow` API directly for both writing and reading operations.
169+
170+
**Recommendation:**
171+
* Use the **Typed API** for ease of use, rapid development, and when type safety is preferred over raw performance.
172+
* Use the **InternalRow API** for high-throughput, latency-sensitive applications where performance is critical.

0 commit comments

Comments
 (0)