ARTICLE DETAIL

资讯详情

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

SpringBoot集成SAP JCo的RFC调用与连接池实战

SpringBoot集成SAP JCo的RFC调用与连接池实战 简介这是一套基于 Spring Boot 的后台管理系统源码核心是通过 SAPJCo3 调用 SAP 的 RFC 函数完成数据交换适合需要在 Java 系统中集成 SAP 接口、或准备开发企业级后台管理项目的开发者参考。项目整合了 MyBatis/MyBatis-Plus、Druid、Shiro、Thymeleaf、Quartz 等主流组件并带前端单页面模板整体结构清晰可以直接改造使用。压缩包共包含336个文件大小约9.46MB其中98个Java文件覆盖控制层、服务层与SAP交互逻辑25个CSS、28个HTML与41个JS构成页面样式与交互另含3个SQL脚本、13个XML配置及4个YAML/Properties配置文件便于快速搭建运行环境83个GIF动图则可直观展示功能界面效果。该资源已有约428人学习浏览适合有一定Java基础、希望快速理解SAP集成与定时同步逻辑的开发者通过源码可以掌握SAPJCo3调用方式、Shiro权限控制思路和Quartz任务调度配置是一份少见的完整企业集成示例。1. 为什么要在SpringBoot里接SAP先聊清楚这套系统要解决什么问题1.1 我遇到的实际业务场景本地系统与SAP之间的数据交互需求做企业级应用开发的同行应该都有同感SAP在制造、零售、化工这些行业里几乎是绕不开的基建设施。我参与的这个项目客户现有的SAP系统里跑着物料主数据、采购订单、库存台账、财务凭证但业务部门日常做报表、做审批、做排产用的却是另一套自研系统。两边数据对不上靠人工导出Excel再导进来一天可能要重复操作几十次既容易出错又拖慢节奏。当时摆在我面前的核心诉求很直接自研系统要能主动从SAP拉取数据也要能把本地业务产生的结果回写进SAP。具体到场景就是三类查询物料清单和库存、创建和修改采购订单、读取生产订单状态。这些操作放到SAP侧来看正好全部落在RFCRemote Function Call这个接口范畴里。所以项目本质是在做一个桥接层——让SpringBoot应用通过SAP官方提供的连接器以RFC方式访问SAP的函数模块。这个定位决定了后面所有技术选型都围绕一个核心展开如何让RFC交互稳定、可控、能被人看懂和运维。1.2 技术选型的取舍为什么偏偏是SAP JCo这一套SAP的集成方案其实有好几条路SOAP WebService、RESTful OData、RFC、IDoc。很多团队会用SAP PI/PO做中间件把接口转成WebService或者直接用SAP Gateway暴露OData。这套方案的优点是对下游系统友好但坏处也很明显——你手里得有一个能配PI/PO的顾问而且多一层中间件排查问题时链路更长。最终我选择了SAP JCoJava Connector直连RFC理由有三点客户已有的RFC函数模块可以直接复用不需要额外改造SAP侧。JCo是SAP官方维护的Java连接器性能和稳定性有保障比用WebService去包一层更可控。项目里已经有定时任务和权限体系的整合需求JCo天然支持在Java进程中管理连接池和SpringBoot的契合度最高。技术栈落定为SpringBoot MyBatis Thymeleaf Quartz Shiro SAP JCo每层都有明确分工SpringBoot做应用骨架和依赖管理MyBatis负责把SAP拉回来的数据落到本地库以及维护接口调用日志Thymeleaf渲染管理后台页面Quartz跑定时同步任务Shiro管登录和操作权限JCo专门负责与SAP对话。这套组合没有花哨成分但每块都能在一个真实项目里找到不可替代的位置。2. SAP JCo接入不是改个pom就完事环境安装与连接池设计2.1 sapjco3.jar与本地库的安装细节如果你是第一次在项目里引入SAP JCo最容易被坑的就是依赖装好了但启动报错。SAP JCo和普通Java库最大的区别在于它除了一个jar包还带一个与操作系统相关的本地动态库。Windows下是sapjco3.dllLinux下是libsapjco3.so。只引入jar包不加载动态库启动时会直接抛UnsatisfiedLinkError。我当时的做法是从SAP官网下载对应版本的SAP JCo 3.x安装包解压后把sapjco3.jar手动安装到本地Maven仓库。因为公司Maven私服不一定允许上传第三方jar所以这一步用了最稳妥的方式mvn install:install-file -Dfilesapjco3.jar -DgroupIdcom.sap -DartifactIdsapjco3 -Dversion3.1.4 -Dpackagingjar然后在pom.xml里正常声明依赖即可。本地动态库的处理我把它放在了项目的lib目录下并在启动脚本里显式指定java.library.pathjava -Djava.library.path/opt/sapjco/lib -jar sapweb.jar一个很容易被忽略的细节Linux服务器上如果报了libsapjco3.so: cannot open shared object file不一定是路径错了很可能是系统缺32位兼容库。JCo的动态库对glibc版本有要求建议先执行ldd libsapjco3.so查看依赖是否完整。这一步能省下大量排查时间。2.2 连接配置与池化策略理解JCoDestination和连接池的关系JCo的设计里有个核心概念叫JCoDestination它本质上是一个连接配置的载体包含了SAP服务器地址、系统编号、客户端、用户名、密码、语言等参数。所有RFC调用都要通过它来获取连接所以这个配置的质量直接影响整个系统的稳定性。我在application.yml里维护了独立的连接参数段没有和业务配置混在一起sap: jco: ashost: 192.168.1.100 sysnr: 00 client: 800 user: RFC_USER passwd: ENC(****) lang: ZH pool-capacity: 10 peak-limit: 20生产环境里密码绝对不能明文写我用了jasypt做配置加密启动时通过环境变量传入解密密钥。连接池这块pool-capacity决定空闲时保留的连接上限peak-limit决定最多能同时借出的连接数。这里有个经验值peak-limit不要超过SAP侧允许的并行RFC连接数否则SAP会直接拒绝多余连接。当时客户的SAP实例限制是30个并行对话我在设计时就给JCo池设了20的上限留出余量给SAP自身的后台任务和其他接入方。3. RFC调用的正确姿势参数映射、BAPI事务与错误处理3.1 从JCoDestination到JCoFunction的标准调用流程JCo调用RFC的标准路径很清晰我拆成五步走每一步都有对应的方法从JCoDestinationManager获取JCoDestination实例内部从连接池中取连接。通过destination.getRepository().getFunction(函数名)获取函数模板函数名务必与SAP侧RFC函数模块完全一致大小写敏感。设置导入参数通过function.getImportParameterList().setValue(参数名, 值)完成。执行函数function.execute(destination)这一步是同步阻塞调用。读取导出参数和表参数转换成本地对象后交给业务层处理。以查询物料库存为例SAP侧的函数模块通常返回一张物料库存表。JCo中通过function.getTableParameterList().getTable(ET_MAKT)拿到JCoTable对象然后遍历每一行取出物料号、库存数量、库存地点等字段。这段代码我封装成了一个通用的RfcInvoker服务屏蔽了模板获取、参数设置、异常转换等重复逻辑。在实际项目里调用方只需要传函数名和参数Map返回结果统一封装成RfcResponse里面有成功标志、错误信息、业务数据三块。这样封装的直接好处是Controller层和定时任务里维护RFC调用的代码量大幅减少排查问题时也只需要盯着RfcInvoker一个类。3.2 BAPI事务的提交与回滚处理RFC调用里最容易出问题的场景是修改类操作比如创建采购订单、修改物料主数据。SAP侧这类操作通常被封装成BAPIBusiness Application Programming Interface而且遵守一套固定的事务模式先调用BAPI函数然后检查返回值里有没有错误消息最后再决定是调用BAPI_TRANSACTION_COMMIT提交还是BAPI_TRANSACTION_ROLLBACK回滚。我见过很多新手在这里踩坑最典型的是调用完BAPI直接判断成功就完事根本没有检查RETURN表里的错误消息。实际上BAPI函数本身执行时大概率不抛异常而是把错误信息塞到RETURN参数表里如果你不主动检查就会出现界面提示成功、SAP里根本没数据的事故。提交那段逻辑我是这样写的JCoFunction commitFunction destination.getRepository().getFunction(BAPI_TRANSACTION_COMMIT); commitFunction.getImportParameterList().setValue(WAIT, X); commitFunction.execute(destination);WAIT参数设置为X表示同步等待SAP提交完成这样后续操作能立即读到已提交的数据。如果业务中途出错或者RETURN表里出现类型为E错误或A终止的消息就必须执行BAPI_TRANSACTION_ROLLBACK同时把错误细节记录到本地日志表方便业务人员直接看到失败原因。3.3 常见错误码与排查思路JCo交互过程中常见的异常集中在三类我在项目里分别做了处理异常类型典型表现处理方式JCoExceptionSAP连接失败、参数格式错误、函数不存在区分是连接级别还是业务级别错误连接级别做重试和告警业务级别直接返回失败JCoAbapExceptionABAP程序主动抛出异常将异常消息转成业务提示显示给操作人员JCoRuntimeException本地JCo资源耗尽、线程中断检查连接池配置、线程池配置做资源隔离保护排查RFC问题有一个通用思路先看SAP侧有没有报错用ST22事务码查短转储再看JCo日志有没有底层连接异常最后看应用日志里有没有业务层面错误。三层日志对照着看基本能定位绝大部分问题。4. 定时任务里的RFC长调用Quartz调度策略要怎么改4.1 调度并发的陷阱连接池被定时任务耗尽项目里有一个典型的定时同步需求每天早上八点从SAP同步当天的物料需求计划同步完成后再触发本地报表刷新。这个任务本身不复杂但和RFC连在一起就出现了一个隐蔽的问题——定时任务默认的并发策略是并发执行也就是说上一次任务还没跑完下一次触发时间到了Quartz会再起一个线程继续跑。当RFC调用本身要花不少时间比如同步几千行物料数据要跑十几分钟而任务又设置了很短的重试间隔就会发生多个线程同时向JCo连接池借连接直接把连接池打满。更严重的是如果单个RFC函数内部有长时间数据库操作SAP侧的对话进程也会被占满直接影响SAP业务系统正常使用。我的解决方案有三步在Quartz的Job实现类上标注DisallowConcurrentExecution注解强制同一作业不并发执行。给RFC调用单独设一个线程池核心线程数控制在2最大线程数控制在连接池上限的50%以内。任务执行前先检查JCo连接池当前可用连接数低于阈值时告警而不是继续硬调。4.2 任务幂等与异常处理同步任务失败的恢复思路定时同步最怕的不是失败而是失败之后产生脏数据。我当时设计了一个本地同步记录表以同步日期数据类型作为唯一键。每次定时任务启动时先查这张表如果当天已经同步成功并且数据没变化就直接跳过。同步过程中每拉取一批数据就写一条明细记录任务失败时可以通过管理后台手动触发补同步补同步逻辑会自动从上次失败的分页位置继续而不是从头再来。Quartz的触发器配置我也做了特别设计。同步任务的CronTrigger仅仅是一个允许启动的信号真正控制的逻辑放在Job内部用分布式锁保证同一时刻只有一个实例在跑当时用了数据库悲观锁取for update锁超时时间设置为核心业务最大耗时的1.5倍。这样即使Quartz集群部署了多个节点也不会出现双跑的情况。还有一个值得分享的细节JCo连接如果长时间空闲SAP侧可能会主动断开。定时任务如果跨越了空闲期第一次RFC调用大概率会失败。所以我在JCo连接池配置文件里设置了connection-timeout和定期校验机制每次借连接之前做一次destination.ping()无效连接先清掉再新建最大程度避免定时任务首调失败。5. Shiro权限与MyBatis本地业务与SAP交互的辅助层5.1 页面权限的拦截设计不是所有操作都能碰SAP系统里涉及SAP的操作都是重要操作比如创建采购订单、修改物料主数据这种操作做错影响的是整个企业的供应链数据。所以权限设计不能停留在登录就能点的层面而是要细化到按钮。Shiro在这套系统里的职责分成了三层认证层用户名密码校验集成客户现有的LDAP账号体系登录成功后创建Subject并写入会话。权限层基于RBAC模型设计用户关联角色角色关联权限点。权限点用字符串编码比如sap:order:create表示创建采购订单的权限。接口层在Controller方法上使用RequiresPermissions(sap:order:create)注解Shiro拦截器在请求进入业务逻辑前完成校验无权限直接返回403。这个设计的关键在于权限点的命名要有业务语义。当时为了方便权限管理后台做批量配置我把权限点按模块分组sap:开头代表SAP交互类操作local:代表本地数据维护类操作sys:代表系统管理类操作。配置权限时只看前缀就能快速归类运营人员使用起来非常直观。5.2 MyBatis在交互链路中的角色不只是增删改查MyBatis在这套系统里承担了三件事每一件都比单纯的业务CRUD更有实际价值。第一是配置同步表单数据。SAP返回的主数据、单据数据不可能每次都实时调RFC那样性能和稳定性都没法保证。我的方案是定时任务把SAP数据拉到本地MySQL表业务页面直接读本地表这样查询响应时间从秒级降到了毫秒级。MyBatis负责这张本地表的结构映射、批量插入和增量更新。第二是维护RFC调用日志。每次RFC调用都会记录一条日志包含函数名、调用参数、返回码、耗时、操作人、调用时间。这张日志表是对接SAP出现纠纷时最重要的证据。排查问题时我直接通过MyBatis查询日志表筛选出同一时间段内所有失败的调用记录按照函数名和错误码分组统计能快速定位是哪类功能出了问题。第三是实现幂等控制。本地保存SAP返回数据时设置唯一索引重复同步时执行INSERT ... ON DUPLICATE KEY UPDATE保证同一份数据不会产生重复记录。这个设计配合Quartz任务的幂等策略构成了整个数据同步链路的双层保障。6. 实测中的踩坑记录与性能调优建议6.1 字符集与数据截断两个差点上线的bug踩坑记录里最有代表性的两个问题都和数据的隐形损坏有关。第一个是中文乱码。JCo连接配置里lang参数我一开始设置的是ZHRFC函数返回的中文字段在本地显示正常但写入MySQL后通过管理后台页面查看时偶尔出现乱码。排查到最后发现是MyBatis连接串里没加characterEncodingutf8参数数据库连接默认用了latin1中文写入时被截断成乱码。修正后在JDBC连接串上显式配置useUnicodetruecharacterEncodingutf8问题彻底解决。第二个是数字精度丢失。SAP的金额字段通常定义为DECIMAL类型但RFC传输到Java侧时会映射成BigDecimal。如果直接用Double接收经过一次序列化和反序列化就可能出现精度漂移。我当时给所有金额和数量字段统一使用了BigDecimal封装并且在MyBatis的TypeHandler中做了自定义映射确保从SAP JCo读取到数据库存储再到页面展示全程无精度损失。这两个问题的共性在于它们不在正常测试用例的覆盖范围内只有到了数据量大的真实环境下才会暴露。我后来养成了一个习惯凡是跨系统数据交互一律在联调阶段提前准备一份脏数据测试集里面包含超长字符串、特殊字符、大金额、负数等边界情况专门用来验证数据链路是否完好。6.2 连接释放与线程模型别让资源随GC沉没JCo的JCoDestination通过连接池管理物理连接但业务代码里获取的JCoFunction、JCoTable这些对象实际上是引用连接资源的。如果在一个长生命周期对象里保存了对JCoFunction的引用即使连接归还了连接池某些本地资源也有可能被意外保留导致内存泄漏。我在RfcInvoker的参数设计上有一个硬性约定函数对象全部在方法内部创建执行完毕后置为null不对外返回任何JCo类型的对象只返回纯Java的数据结构。这样做从代码层面杜绝了连接资源被外部误持久的风险。线程模型方面由于RFC调用是阻塞式的一个调用就可能占用几分钟。我当时给RFC调用单独配置了一个线程池并设置了CallerRunsPolicy拒绝策略——线程池满时任务由调用线程直接执行而不是直接丢弃任务。同时配合Future.get(timeout)做超时控制超过预设时间就显示调用超时请稍后重试避免页面无限等待。6.3 监控与排查工具出了问题怎么快速定位最后聊聊运维视角。RFC接口对业务的影响是实时的SAP侧一条慢查询可能拖垮整个交互链路。所以这套系统上线时我同步配了一套三层监控JCo连接池监控通过定时任务每隔30秒采样连接池的getPoolCount和getPeakCount超过阈值就推送告警到企业微信。SAP响应耗时监控在RfcInvoker里记录每次调用的耗时分位数当P95耗时超过5秒时自动通知开发人员排查。SAP侧事务日志在SAP侧用事务码STAD和ST05定期分析慢调用和数据库访问和本地调用日志对应起来形成全链路视图。日志组件选型上我用了logback为RFC调用单独配置了一个独立日志文件滚动策略按天保存保留最近90天。这个文件在排查问题时价值极高因为所有RFC进出的参数和异常都按时间顺序完整记录现场条件有限时仅凭这份日志就能还原大部分问题。写在最后整个sapweb项目从开发到上线最让我觉得值得沉淀的并不是某个华丽的技术点而是一套稳定的程序与SAP对话的思维框架连接怎么管、事务怎么控、失败怎么恢复、权限怎么隔离、数据怎么追踪。这套框架离开SAP同样适用换成其他任何外部系统集成思路都是相通的。如果你也在做类似的集成项目我的建议是先把连接资源管理和错误处理想透再谈业务功能的实现。这两件事做扎实了后面的开发都会顺畅很多。本文还有配套的精品资源点击获取
返回列表