|
最近项目要和SAP对接,使用JCO方式,所以研究了一下,测试在Windows和Mac下都可以使用,在此记录一下,大家有问题也可以一起交流一下。
SAP Java Connector (SAP JCo) 是JAVA与SAP相互通信的中间件组建,该组建支持双向通讯模式(inbound calls 和 outbound calls )。JCo支持Connection Pools和Direct两种方式的连接。直接连接需要开发者来控制连接的创建和释放,使用连接池方式可以让池来管理连接的分配、管理和释放,可以最大限度的节省系统开销,相比直接方式优势也是明显的。本文将演示的Direct方式的连接。
SAP Java Connector 3.1 运行时环境由两部分组成:
- sapjco3.jar - 包含 JCo 的 Java 运行时类的存档
- {library prefix}sapjco3{shared library extension} - 包含 JCo 原生代码的 JNI 库
运行时初始化逻辑
当应用程序第一次引用 JCo 类时,它会被关联的类加载器(class loader)加载。
在此过程中,将执行 JCo 的静态初始化程序。该例程将使用以下算法搜索并加载 JCo JNI 库:
- 尝试从 sapjco3.jar 文件所在的同一目录加载 sapjco3 JNI 库。
- 尝试从 sapjco3.jar 文件所在的平台相关子目录加载 sapjco3 JNI 库。适当的 JNI 库加载路径是通过考虑当前使用的操作系统和 JVM 运行时环境来确定的
- 沿着 java.library.path 系统属性中定义的目录路径从左到右搜索 sapjco3 JNI 库,如果找到则从那里加载它。
注意:如果不手动设置该属性,JVM 通常会在启动时将 OS 特定的库路径环境变量设置为默认的 java.library.path 系统属性。
- 通过将此任务委托给它的 System.loadLibrary(String libname) 方法,让 JVM 处理 sapjco3 JNI 库的加载。
如果找不到或无法加载 sapjco3 JNI 库,您将收到 UnsatisfiedLinkError 错误,其中包含操作系统提供的详细信息。
此错误消息可能由以下原因之一引起:
- 使用上述算法无法找到 sapjco3 JNI 库,因为您无意中将其安装到了错误的目录,或者忘记在操作系统特定的库路径环境变量或 java.library.path 系统属性中指定其目录。
- 找到的 sapjco3 JNI 库版本太旧。
- 发现的 sapjco3 JNI 库缺少执行权限标志。
- 找到的 sapjco3 JNI 库的位数与 JVM 不同(32 位与 64 位)。
- 找到的 sapjco3 JNI 库来自不同的 JCo 发行版,不适用于您的操作系统和/或硬件处理器。
- 找到的 sapjco3 JNI 库需要更高的操作系统版本。
sapjoc3.jar获取
由于sap官网提供的链接需要合作公司提供账号密码,如果商用请索要正确的用户名密码下载,如果只是想做测试使用,可以自行网上搜索或者找我获取。
测试项目环境准备
- 新建项目
这个就不多谈了,直接上图,我这里新建了一个空的测试项目,新建lib文件夹,准备把需要用到的jar包丢进来

- windows和macOS环境配置
- windows
直接把 sapjco3.jar sapjco3.dll sapjco3.pdb 拷贝至lib下,然后add library把他们都添加进去
- macOS
把 sapjco3.jar 和 libsapjco3.jnilib 拷贝至lib下,同样add libray添加进去,这里只需要add sapjco3.jar 即可。


WARNING: 这里很多博客讲到需要添加classpath在变量里面,这是参照了官方提供的安装方式,这里的话我们只需要将上述文件拷贝至lib文件夹下即可。
3、如果有多个模块,可以在IDEA中打开模块配置【Open Module Settings】
我使用的该方式引入的jar依赖


找到你的jar位置,引入即可。
三、源码编写及测试
首先编写用来放置sap连接信息的SapConn实体类
SapConn.java
/**
* SapConn
*/
public class SapConn {
// SAP服务器
private String JCO_ASHOST;
// SAP系统编号
private String JCO_SYSNR;
// SAP集团
private String JCO_CLIENT;
// SAP用户名
private String JCO_USER;
// SAP密码
private String JCO_PASSWD;
// SAP登录语言
private String JCO_LANG;
// 最大连接数
private String JCO_POOL_CAPACITY;
// 最大连接线程
private String JCO_PEAK_LIMIT;
// SAP ROUTER
private String JCO_SAPROUTER;
public SapConn(String JCO_ASHOST, String JCO_SYSNR, String JCO_CLIENT, String JCO_USER,
String JCO_PASSWD, String JCO_LANG, String JCO_POOL_CAPACITY, String JCO_PEAK_LIMIT,
String JCO_SAPROUTER) {
this.JCO_ASHOST = JCO_ASHOST;
this.JCO_SYSNR = JCO_SYSNR;
this.JCO_CLIENT = JCO_CLIENT;
this.JCO_USER = JCO_USER;
this.JCO_PASSWD = JCO_PASSWD;
this.JCO_LANG = JCO_LANG;
this.JCO_POOL_CAPACITY = JCO_POOL_CAPACITY;
this.JCO_PEAK_LIMIT = JCO_PEAK_LIMIT;
this.JCO_SAPROUTER = JCO_SAPROUTER;
}
public SapConn() {}
public String getJCO_ASHOST() {
return JCO_ASHOST;
}
public void setJCO_ASHOST(String JCO_ASHOST) {
this.JCO_ASHOST = JCO_ASHOST;
}
public String getJCO_SYSNR() {
return JCO_SYSNR;
}
public void setJCO_SYSNR(String JCO_SYSNR) {
this.JCO_SYSNR = JCO_SYSNR;
}
public String getJCO_CLIENT() {
return JCO_CLIENT;
}
public void setJCO_CLIENT(String JCO_CLIENT) {
this.JCO_CLIENT = JCO_CLIENT;
}
public String getJCO_USER() {
return JCO_USER;
}
public void setJCO_USER(String JCO_USER) {
this.JCO_USER = JCO_USER;
}
public String getJCO_PASSWD() {
return JCO_PASSWD;
}
public void setJCO_PASSWD(String JCO_PASSWD) {
this.JCO_PASSWD = JCO_PASSWD;
}
public String getJCO_LANG() {
return JCO_LANG;
}
public void setJCO_LANG(String JCO_LANG) {
this.JCO_LANG = JCO_LANG;
}
public String getJCO_POOL_CAPACITY() {
return JCO_POOL_CAPACITY;
}
public void setJCO_POOL_CAPACITY(String JCO_POOL_CAPACITY) {
this.JCO_POOL_CAPACITY = JCO_POOL_CAPACITY;
}
public String getJCO_PEAK_LIMIT() {
return JCO_PEAK_LIMIT;
}
public void setJCO_PEAK_LIMIT(String JCO_PEAK_LIMIT) {
this.JCO_PEAK_LIMIT = JCO_PEAK_LIMIT;
}
public String getJCO_SAPROUTER() {
return JCO_SAPROUTER;
}
public void setJCO_SAPROUTER(String JCO_SAPROUTER) {
this.JCO_SAPROUTER = JCO_SAPROUTER;
}
@Override
public String toString() {
return "SapConn{" +
"JCO_ASHOST='" + JCO_ASHOST + '\'' +
", JCO_SYSNR='" + JCO_SYSNR + '\'' +
", JCO_CLIENT='" + JCO_CLIENT + '\'' +
", JCO_USER='" + JCO_USER + '\'' +
", JCO_PASSWD='" + JCO_PASSWD + '\'' +
", JCO_LANG='" + JCO_LANG + '\'' +
", JCO_POOL_CAPACITY='" + JCO_POOL_CAPACITY + '\'' +
", JCO_PEAK_LIMIT='" + JCO_PEAK_LIMIT + '\'' +
", JCO_SAPROUTER='" + JCO_SAPROUTER + '\'' +
'}';
}
}
然后编写用来建立sap连接的SapConn作为连接类
编写连接类SAPConnUtils 下面是源码
/**
* SAPConnUtils
*/
public class SAPConnUtils {
private static final String ABAP_AS_POOLED = "ABAP_AS_WITH_POOL";
/**
* 创建SAP接口属性文件。
* @param name ABAP管道名称
* @param suffix 属性文件后缀
* @param properties 属性文件内容
*/
private static void createDataFile(String name, String suffix, Properties properties){
File cfg = new File(name+"."+suffix);
if(cfg.exists()){
cfg.deleteOnExit();
}
try{
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
}catch (Exception e){
System.out.println("Create Data file fault, error msg: " + e.toString());
throw new RuntimeException("Unable to create the destination file " + cfg.getName(), e);
}
}
/**
* 初始化SAP连接
*/
private static void initProperties(SapConn sapConn) {
Properties connectProperties = new Properties();
// SAP服务器
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, sapConn.getJCO_ASHOST());
// SAP系统编号
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, sapConn.getJCO_SYSNR());
// SAP集团
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, sapConn.getJCO_CLIENT());
// SAP用户名
connectProperties.setProperty(DestinationDataProvider.JCO_USER, sapConn.getJCO_USER());
// SAP密码
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, sapConn.getJCO_PASSWD());
// SAP登录语言
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, sapConn.getJCO_LANG());
// 最大连接数
connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, sapConn.getJCO_POOL_CAPACITY());
// 最大连接线程
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, sapConn.getJCO_PEAK_LIMIT());
// SAP ROUTER
//connectProperties.setProperty(DestinationDataProvider.JCO_SAPROUTER, sapConn.getJCO_SAPROUTER());
createDataFile(ABAP_AS_POOLED, "jcoDestination", connectProperties);
}
/**
* 获取SAP连接
* @return SAP连接对象
*/
public static JCoDestination connect(SapConn sapConn){
System.out.println("正在连接至SAP...");
JCoDestination destination = null;
initProperties(sapConn);
try {
destination = JCoDestinationManager.getDestination(ABAP_AS_POOLED);
destination.ping();
System.out.println("已成功建立sap的连接");
} catch (JCoException e) {
System.out.println("Connect SAP fault, error msg: " + e.toString());
}
return destination;
}
}
接下来编写一个简单的测试类test
/**
* testCon
*/
public class testCon {
public static void main(String[] args) {
// 配置信息,需要替换为SAP提供的测试配置
SapConn con = new SapConn(
"127.0.0.1",
"123",
"456",
"abc",
"abc",
"abc",
"123",
"321",
"123456"
);
// 获取连接
SAPConnUtils.connect(con);
// 后续测试需要结合SAP提供的参数格式进行测试
// 1、获取功能
JCoFunction function = destination.getRepository().getFunction("YX_MD_MATERIAL_CREATE");
// 2、获取 MAT_INPUT 结构信息,设置调用单号信息
JCoStructure structure = function.getImportParameterList().getStructure("MAT_INPUT");
structure.setValue("OUTBLNR", orderNo);
structure.setValue("OUSRNAM", "rfc_test");
// 3、组装请求报文需要的数据
// 3.1 获取 MAT_INPUT 结构下边的 HEADERS 表格,设置扩充主体信息
JCoTable headers = structure.getTable("HEADERS");
headers.appendRow(); // 添加一行
headers.setValue("SERNO", "0001");
headers.setValue("MTART", "ROH1"); // 物料类型
headers.setValue("MEINS", "个");
headers.setValue("MATKL", "Z99"); // 物料组
headers.setValue("MAKTX", "测试0001");
headers.setValue("BISMT", "20221206001");
headers.setValue("GROES", "20221206001");
headers.setValue("TXLINE1", "这个是测试数据,测试120601");
headers.setValue("STPRS", "12");
headers.setValue("PEINH", "1");
headers.setValue("BUKRS1", "1234"); // 公司代码1
// 3.2 获取 HEADERS 表格下边的 PLANTSTAB 表格,添加工厂信息
JCoTable plantstab = headers.getTable("PLANTSTAB");
plantstab.appendRow(); // 添加一行
plantstab.setValue("WERKS", "");
// 4、执行接口
function.execute(destination);
// 5、处理返回结果信息
// 遍历接口返回结果
JCoTable returnStructure = function.getTableParameterList().getTable("RETURN_TAB");
for (int i = 0; i < returnStructure.getNumRows(); i++) {
returnStructure.getRow();
System.out.println(&#34;外部调用单号-OUTBLNR:&#34; + returnStructure.getString(&#34;OUTBLNR&#34;));
System.out.println(&#34;外部调用项次-SERNO:&#34; + returnStructure.getString(&#34;SERNO&#34;));
System.out.println(&#34;SAP物料号-MATNR:&#34; + returnStructure.getString(&#34;MATNR&#34;));
System.out.println(&#34;消息类型-STATU:&#34; + returnStructure.getString(&#34;STATU&#34;));
System.out.println(&#34;消息描述-MSGTXT:&#34; + returnStructure.getString(&#34;MSGTXT&#34;));
}
// 调用接口返回状态
String result = function.getExportParameterList().getString(&#34;O_SUBRC&#34;);
// 调用接口返回信息
String message = function.getExportParameterList().getString(&#34;O_MSGTXT&#34;);
System.out.println(&#34;调用返回状态--->&#34; + result + &#34;;调用返回信息--->&#34; + message);
// 将返回结果转换为xml
JCoParameterList tblexport = function.getTableParameterList();
String msg = tblexport.toXML();
System.out.println(&#34;调用返回表XML--->&#34; + msg);
}
}如果出现如下结果表明连接失败,请检查连接类。

此时如果调用结果如下则表明建立连接成功。

以上方式就可以通过JCO实现SAP的交互,如果需要资料的也可以直接获取。
注意:各版本的jar仅测试使用,生产的需要联系对接的SAP获取。
JCO各版本jar及使用示例代码
四、常见报错

这个错误是因为缺少了上述提到的文件,请按照文中提到的顺序将其加入项目中即可。
- 第一种解决方式:配置虚拟机参数:-Djava.library.path=之前环境变量路径
- Intellj IDEA 配置VM options,如图:

- 第二种解决方式:把jnilib文件放到 jdk目录的【jre/lib】目录下面去
参考文章:
SAP Java Connector 组件介绍
JAVA使用JCo连接SAP
JAVA调用SAP端RFC接口 |
|