Files
spring-boot-demo/spring-batch-partitioning/docs/04-the-writer-and-the-beanmapped-trap.md

73 lines
4.0 KiB
Markdown

# 4. The writer, the beanMapped trap, and finding the partition's own name
[← Previous](03-what-gridsize-actually-controls.md) | [README](../README.md) | [Next: The diagnostic endpoint →](05-the-diagnostic-endpoint.md)
## Not beanMapped(), again
[The earlier `spring-batch` module](https://ankurm.com/) already found this once: `Order` and
`RiskScoredOrder` are Java records, and `JdbcBatchItemWriterBuilder.beanMapped()` binds SQL
parameters through `BeanPropertySqlParameterSource`, which looks for JavaBean-style getters
(`getOrderId()`). A record's accessors are `orderId()`, no `get` prefix — `beanMapped()`
would silently bind every column to `NULL` against this exact shape, because
`BeanPropertySqlParameterSource` treats a missing getter as an absent property rather than an
error. This module's [`riskWriter`](../src/main/java/com/ankurm/batchpartition/config/BatchConfig.java)
uses an explicit `itemPreparedStatementSetter` instead, precisely to avoid rediscovering that bug
a second time.
## Finding out which partition wrote which row
`ORDER_RISK_SUMMARY.partition_name` exists so a reader (or a bug report) can tell which worker
wrote a given row. The first attempt to populate it reached for
`StepSynchronizationManager.getContext().getStepExecution().getStepName()` inside the
`itemPreparedStatementSetter` lambda, guessing the class lived at
`org.springframework.batch.core.step.StepSynchronizationManager` by analogy with `StepExecution`
living in `org.springframework.batch.core.step`. It does not compile:
```
error: package org.springframework.batch.core.step does not exist
(well -- StepSynchronizationManager specifically does not exist there)
```
`javap` against the real jar answers where it actually is:
[`docs/output/03-package-repackaging-javap.txt`](output/03-package-repackaging-javap.txt) shows
`org.springframework.batch.core.scope.context.StepSynchronizationManager` — alongside
`JobScope` and `StepScope`'s own scope-context machinery, which is the more sensible package for
it in hindsight; the guess just followed the wrong analogy.
The fix that shipped is simpler than getting the import right would have been: late-bind the step
name directly, the same mechanism [chapter 2](02-anatomy-of-a-partitioned-step.md) already uses
for the shard file path.
```java
@Bean
@StepScope
public ItemWriter<RiskScoredOrder> riskWriter(JdbcTemplate jdbcTemplate,
@Value("#{stepExecution.stepName}") String partitionName) {
return new JdbcBatchItemWriterBuilder<RiskScoredOrder>()
.dataSource(jdbcTemplate.getDataSource())
.sql("MERGE INTO ORDER_RISK_SUMMARY (order_id, customer_id, amount_cents, region, " +
"risk_score, high_risk, partition_name) KEY(order_id) VALUES (?, ?, ?, ?, ?, ?, ?)")
.itemPreparedStatementSetter((item, ps) -> {
// ... ps.setString(7, partitionName);
})
.assertUpdates(true)
.build();
}
```
`#{stepExecution.stepName}` reaches the *current* `StepExecution`'s name (`ordersWorkerStep:partition2`,
for instance) through the same `@StepScope` proxy machinery that resolves `#{stepExecutionContext['fileName']}`
&mdash; no lookup class needed, no package to get wrong. `MERGE ... KEY(order_id)` (H2 upsert
syntax) rather than a plain `INSERT` was a deliberate second choice: it makes re-running a single
partition idempotent, which matters once [chapter 8](08-restart-reruns-only-the-failed-partition.md)
starts re-executing partitions after a failure.
## Going deeper
- `BeanPropertySqlParameterSource` and why it fails silently rather than throwing:
[Spring Framework Javadoc](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/BeanPropertySqlParameterSource.html) (`rel="nofollow"`).
- The full list of classes this article's research found relocated in Spring Batch 6.0:
[`docs/output/03-package-repackaging-javap.txt`](output/03-package-repackaging-javap.txt).
[Next: The diagnostic endpoint &rarr;](05-the-diagnostic-endpoint.md)