ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Mybatis-Spring 官方中文文档:Spring Batch 批处理读写器配置与验证

Mybatis-Spring 官方中文文档:Spring Batch 批处理读写器配置与验证 1. 从一次批处理任务卡住说起MyBatisPagingItemReader 分页读取为什么读不到第二页先说一个我实际遇到的场景。有个朋友做用户行为数据的离线归档用 Spring Batch 配 MyBatis 做读写任务跑起来之后日志显示读取了 200 条就停了数据库里明明有几十万条。他一开始怀疑是 chunk 配置问题改来改去没效果最后发现是分页 SQL 里没写_skiprows和_pagesize这两个参数reader 每次都在读同一页数据Spring Batch 检测到返回结果和上一页重复直接判定读取结束。这个坑很典型。Mybatis-Spring 从 1.1.0 开始提供了三个专门给 Spring Batch 用的 beanMyBatisPagingItemReader、MyBatisCursorItemReader、MyBatisBatchItemWriter。2.0.0 之后又补了对应的 builder 类Java 配置写起来更顺手。但官方文档给的是片段真正拼成一条能跑的读写链路中间有不少细节要对齐。这篇就围绕这三类组件把分页读取、游标读取、批量写入的配置要点拆开讲每个环节都给可复制的 XML 和 Java 配置再配上验证动作。适合已经在用 Spring Batch、想把 MyBatis 接进 Reader/Writer 的同学也适合刚开始搭批处理任务、被分页参数和批量提交绕晕的人。核心检索词先摆出来Mybatis-Spring 集成 Spring Batch 时MyBatisPagingItemReader负责分页读MyBatisCursorItemReader负责游标读MyBatisBatchItemWriter负责批量写。三者配合 chunk 机制才能把「读一批、处理一批、写一批」的流程跑通。下面按「先讲清楚每个组件的行为 → 给配置 → 给验证」的顺序展开。如果你现在正卡在某个报错上可以直接跳到第 5 节的排查对照表。2. 接入前的准备TaoToken 与 Mybatis-Spring 环境怎么摆在动手写配置之前先把两件事理清楚一是 MyBatis 和 Mybatis-Spring 的依赖版本二是如果你打算用大模型辅助生成 Mapper 或排查报错怎么把模型调用接进来。2.1 依赖版本的最低要求MyBatisCursorItemReader依赖 MyBatis 3.4.0 及以上版本因为游标读取走的是selectCursor()方法低版本没有这个 API。MyBatisBatchItemWriter的复合写入技巧要求 MyBatis 3.2再早的版本有已知的 writer 行为问题。Mybatis-Spring 本身建议 2.0.0 以上这样三个 builder 类都能用。Maven 里大致是这样dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.1.2/version /dependency dependency groupIdorg.springframework.batch/groupId artifactIdspring-batch-core/artifactId version4.3.10/version /dependency版本不用完全照抄但 MyBatis 别低于 3.4.0Mybatis-Spring 别低于 2.0.0。2.2 用 TaoToken 辅助生成配置和排查报错写批处理配置的时候经常需要查某个 builder 方法叫什么、某个参数怎么传。我习惯把这类问题丢给模型对话去问比翻文档快。TaoToken 的模型对话入口可以直接用https://taotoken.net/api如果你要长期做编码类任务比如反复生成 Mapper XML、调试 Spring Batch 的 chunk 配置用 Coding Plan 会更划算入口在https://taotoken.net/api/coding-planAPI Key 在控制台生成https://taotoken.net/api/console生成之后在调用模型时把 Base URL 指向https://taotoken.net/apiKey 填进去Model ID 按你选的模型填。这三件套Base URL Key Model ID是接入任何兼容 OpenAI 协议的工具时都要对齐的。注意TaoToken 是模型调用入口不是数据库连接工具也不替代你的 IDE。它帮你生成配置片段、解释报错但最终跑批处理还是在你自己的 Spring Boot 工程里。2.3 两个 SqlSessionFactory 的准备批处理场景里读和写经常要用不同的执行类型。MyBatisBatchItemWriter要求SqlSessionFactory配置成BATCH执行类型而普通的读取用SIMPLE就行。所以工程里通常会有两个 factoryConfiguration public class SessionFactoryConfig { Bean public SqlSessionFactory batchReadingSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setMapperLocations( new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/read/*.xml)); return factoryBean.getObject(); } Bean public SqlSessionFactory batchWritingSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setMapperLocations( new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/write/*.xml)); org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration(); configuration.setDefaultExecutorType(ExecutorType.BATCH); factoryBean.setConfiguration(configuration); return factoryBean.getObject(); } }读的 factory 用默认执行类型写的 factory 显式设成BATCH。这一步不做后面 writer 写入时不会走批量性能上不去还可能报执行类型不匹配的错。3. 三类组件的可复制配置分页读、游标读、批量写这一节是核心把三个组件的 XML 和 Java 配置都给全并且每个配置都说明关键参数怎么填。3.1 MyBatisPagingItemReader 分页读取配置分页读取的原理是reader 执行你指定的queryIdSQL 里用_page、_pagesize、_skiprows三个参数构造分页。_page从 0 开始_pagesize是每页行数_skiprows是_page * _pagesize。Mapper XML 里的查询这样写select idgetEmployee resultMapemployeeBatchResult SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize} /select注意ORDER BY必须有否则分页结果不稳定可能出现重复或漏读。XML 配置 readerbean idreader classorg.mybatis.spring.batch.MyBatisPagingItemReader property namesqlSessionFactory refbatchReadingSessionFactory / property namequeryId valuecom.my.name.space.batch.EmployeeMapper.getEmployee / property namepageSize value200 / /beanJava 配置用 builderBean public MyBatisPagingItemReaderEmployee reader() { return new MyBatisPagingItemReaderBuilderEmployee() .sqlSessionFactory(batchReadingSessionFactory()) .queryId(com.my.name.space.batch.EmployeeMapper.getEmployee) .pageSize(200) .build(); }pageSize要和 step 的 chunk size 对齐。如果 chunk 是 200pageSize 也设 200这样每次读取刚好填满一个 chunk。带参数的复杂场景比如按时间范围读用parameterValues传 mapStepScope Bean public MyBatisPagingItemReaderUser dateBasedCriteriaReader( Value(#{datesParameters}) MapString, Object datesParameters) throws Exception { return new MyBatisPagingItemReaderBuilderUser() .sqlSessionFactory(batchReadingSessionFactory()) .queryId(com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot) .parameterValues(datesParameters) .pageSize(200) .build(); }对应的 XML 配置bean iddateBasedCriteriaReader classorg.mybatis.spring.batch.MyBatisPagingItemReader p:sqlSessionFactory-refbatchReadingSessionFactory p:parameterValues-refdatesParameters p:queryIdcom.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot p:pageSize200 scopestep/ util:map iddatesParameters scopestep entry keyyesterday value#{jobExecutionContext[EXTRACTION_START_DATE]}/ entry keytoday value#{jobExecutionContext[TODAY_DATE]}/ /util:map这里scopestep很关键。reader 和参数 map 都必须是 step 作用域才能在 SpEL 里访问jobExecutionContext。如果写成单例启动时就会报找不到 jobExecutionContext 的错。3.2 MyBatisCursorItemReader 游标读取配置游标读取适合数据量大、不想一次性算分页的场景。它执行selectCursor()每次read()返回游标的下一个元素直到没有为止。用的是单独的数据库连接不参与 step 里创建的事务。Mapper 里写普通查询就行不需要分页参数select idgetEmployee resultMapemployeeBatchResult SELECT id, name, job FROM employees ORDER BY id ASC /selectXML 配置bean idreader classorg.mybatis.spring.batch.MyBatisCursorItemReader property namesqlSessionFactory refbatchReadingSessionFactory / property namequeryId valuecom.my.name.space.batch.EmployeeMapper.getEmployee / /beanJava 配置Bean public MyBatisCursorItemReaderEmployee reader() { return new MyBatisCursorItemReaderBuilderEmployee() .sqlSessionFactory(batchReadingSessionFactory()) .queryId(com.my.name.space.batch.EmployeeMapper.getEmployee) .build(); }游标读取不需要pageSize因为它是流式返回。但要注意游标持有的连接在 step 执行期间一直不释放如果数据库连接池很小同时跑多个游标任务可能把连接占满。3.3 MyBatisBatchItemWriter 批量写入配置writer 走SqlSessionTemplate的批量处理SqlSessionFactory必须是BATCH执行类型。调用write()时执行statementId指定的语句通常要放在事务里。XML 配置bean idwriter classorg.mybatis.spring.batch.MyBatisBatchItemWriter property namesqlSessionFactory refbatchWritingSessionFactory / property namestatementId valuecom.my.name.space.batch.EmployeeMapper.updateEmployee / /beanJava 配置Bean public MyBatisBatchItemWriterUser writer() { return new MyBatisBatchItemWriterBuilderUser() .sqlSessionFactory(batchWritingSessionFactory()) .statementId(com.my.name.space.batch.EmployeeMapper.updateEmployee) .build(); }默认情况下writer 把 reader 读到的对象或 processor 转换后的对象直接作为参数传给 MyBatis。如果你想自定义参数对象用itemToParameterConverterpublic class ItemToParameterMapConverters { public static T ConverterT, MapString, Object createItemToParameterMapConverter( String operationBy, LocalDateTime operationAt) { return item - { MapString, Object parameter new HashMap(); parameter.put(item, item); parameter.put(operationBy, operationBy); parameter.put(operationAt, operationAt); return parameter; }; } }配置 writer 时挂上转换器Bean public MyBatisBatchItemWriterPerson writer() throws Exception { return new MyBatisBatchItemWriterBuilderPerson() .sqlSessionFactory(batchWritingSessionFactory()) .statementId(org.mybatis.spring.sample.mapper.PersonMapper.createPerson) .itemToParameterConverter( createItemToParameterMapConverter(batch_java_config_user, LocalDateTime.now())) .build(); }对应的 insert 语句用#{item.firstName}这种形式取参数insert idcreatePerson insert into persons (first_name, last_name, operation_by, operation_at) values(#{item.firstName}, #{item.lastName}, #{operationBy}, #{operationAt}) /insert3.4 复合 writer 写多表如果要写多个有关联的表用CompositeItemWriter把多个 writer 串起来顺序很重要Bean public CompositeItemWriter? interactionsItemWriter() { CompositeItemWriter compositeItemWriter new CompositeItemWriter(); ListItemWriter? writers new ArrayList(4); writers.add(visitorInteractionsWriter()); writers.add(customerInteractionsWriter()); writers.add(interactionMetadataWriter()); writers.add(interactionWriter()); compositeItemWriter.setDelegates(writers); return compositeItemWriter; }先写InteractionMetadata拿到自增主键后再写Interaction。Mapper 里用useGeneratedKeys和keyProperty把主键回填到对象上insert idinsertInteractionMetadata parameterTypecom.my.batch.interactions.item.InteractionRecordToWriteInMultipleTables useGeneratedKeystrue keyPropertyinteraction.interactionMetadata.id keyColumnid /insert这里有个坑不同 JDBC 驱动对批量模式下返回主键的行为不一致。H2 的 1.3.168 驱动只在 BATCH 模式下返回最后一个索引值MySQL 驱动则正常返回所有 ID。如果发现关联表写入时主键对不上先确认驱动版本和批量模式下的主键返回行为。4. 验证请求与成功结果怎么确认读写链路真的通了配置写完不代表能跑。这一节给几个验证动作从读取到写入逐段确认。4.1 验证分页读取是否翻页最直接的办法是在 Mapper 的 SQL 里临时加日志或者在 reader 外面包一层监听。更简单的是看 Spring Batch 的日志如果分页正常你会看到 reader 反复执行查询每次_skiprows递增。也可以在测试里手动调 readerTest public void testPagingReader() throws Exception { MyBatisPagingItemReaderEmployee reader reader(); reader.open(new ExecutionContext()); Employee first reader.read(); assertNotNull(first); Employee second reader.read(); assertNotNull(second); assertNotEquals(first.getId(), second.getId()); reader.close(); }如果第二次读到的和第一次一样说明分页参数没生效回去检查 SQL 里有没有用_skiprows和_pagesize。4.2 验证游标读取是否流式返回游标读取的验证类似但要注意它用的是独立连接Test public void testCursorReader() throws Exception { MyBatisCursorItemReaderEmployee reader reader(); reader.open(new ExecutionContext()); int count 0; Employee item; while ((item reader.read()) ! null) { count; } reader.close(); assertTrue(count 0); }如果读到一半报连接超时检查连接池配置和游标持有时间。4.3 验证批量写入是否真的批量提交批量写入的验证要看两点一是数据有没有写进去二是是不是批量提交的。可以在 writer 执行后查数据库Test public void testBatchWriter() throws Exception { MyBatisBatchItemWriterEmployee writer writer(); ListEmployee items Arrays.asList(emp1, emp2, emp3); writer.write(items); // 查库确认 Integer count jdbcTemplate.queryForObject( select count(*) from employees where id in (?,?,?), Integer.class, emp1.getId(), emp2.getId(), emp3.getId()); assertEquals(Integer.valueOf(3), count); }批量提交的确认可以看 MyBatis 日志里的JDBC Connection和PreparedStatement执行次数。如果每条都单独提交说明SqlSessionFactory没设成BATCH。4.4 用模型对话核对配置配置多的时候容易漏参数。我试过把 XML 和 Java 配置贴给模型对话让它对照官方文档检查有没有缺项。入口还是https://taotoken.net/api比如问「MyBatisPagingItemReader 的 parameterValues 在 XML 里怎么配」它会给出p:parameterValues-ref的写法。比自己翻文档快但生成的内容还是要自己核对一遍。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 对照这一节把批处理集成里常见的报错和排查方向列出来对照着看。5.1 401 与鉴权类报错如果你在用模型辅助生成配置时遇到 401通常是 API Key 没填对或过期。检查控制台里的 Key 是否还有效https://taotoken.net/api/console批处理本身的 401 一般不会出现因为数据库连接是本地配置的。但如果你的 Mapper 里调了外部服务可能会有鉴权问题那就和批处理无关了。5.2 local proxy failed这个报错通常出现在网络请求走本地代理时。如果你在调用模型接口时看到local proxy failed检查你的 HTTP 客户端有没有配代理以及代理是否可达。TaoToken 的接口地址是https://taotoken.net/api不需要额外配代理。5.3 reading choices 相关报错如果你在解析模型返回时看到reading choices相关的错误通常是返回结构和你预期的格式不一致。检查请求里的model参数和返回的choices数组。这类问题在模型对话里问一下就能定位https://taotoken.net/api5.4 OAuth 与 Claude Code 接入如果你用 Claude Code 做编码辅助接入时可能需要配置 OAuth 或 API Key。Claude Code 的 Anthropic 兼容入口在https://taotoken.net/api/claude-code-anthropic配置时同样要对齐三件套Base URL、Key、Model ID。如果 OAuth 流程卡住先确认回调地址和 Key 的权限范围。5.5 批处理本身的常见错除了上面这些批处理集成里还有几个高频错报错现象可能原因排查方向读取只返回第一页SQL 没用_skiprows/_pagesize检查 Mapper XML游标读取报连接超时连接池太小或游标持有太久调大连接池或改用分页批量写入不生效SqlSessionFactory不是 BATCH检查defaultExecutorType关联表主键对不上驱动批量模式主键返回行为不同换驱动版本或改写入顺序step 作用域报错reader 或参数 map 写成单例加StepScope或scopestep排查的时候先把 Spring Batch 的日志级别调到 DEBUG看 reader 和 writer 的实际执行次数。再对照上面的表逐项排除。6. 把读写链路跑通之后几个实用建议配置跑通只是第一步。实际用的时候有几个点值得注意。分页读取的pageSize和 chunk size 对齐之后内存占用和数据库压力都比较可控。如果数据量特别大分页比游标更稳因为游标长时间持有连接容易在连接池紧张时出问题。批量写入的SqlSessionFactory一定要单独配一个 BATCH 类型的不要和读取共用。共用的话要么读取变慢要么写入不批量两头不讨好。复合 writer 写多表的时候顺序不能乱。先写主表拿到主键再写关联表。如果驱动在批量模式下主键返回有问题可以考虑把关联表的写入拆成单独的 step或者改用非批量模式写关联表。最后如果你在生成 Mapper 或调试配置时需要快速查 API模型对话和 Coding Plan 都能用。API Key 在控制台生成Base URL 指向https://taotoken.net/apiModel ID 按需选。三件套对齐了接入就顺了。
返回列表