73 lines
4.0 KiB
Markdown
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']}`
|
|
— 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 →](05-the-diagnostic-endpoint.md)
|