4.0 KiB
4. The writer, the beanMapped trap, and finding the partition's own name
← Previous | README | Next: The diagnostic endpoint →
Not beanMapped(), again
The earlier spring-batch module 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
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 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 already uses for the shard file path.
@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
starts re-executing partitions after a failure.
Going deeper
BeanPropertySqlParameterSourceand why it fails silently rather than throwing: Spring Framework Javadoc (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.