查看: 83|回复: 2

JAVA使用JCO方式调用SAP接口

[复制链接]

4

主题

8

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2023-3-30 17:27:23 | 显示全部楼层 |阅读模式
最近项目要和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("外部调用单号-OUTBLNR:" + returnStructure.getString("OUTBLNR"));
            System.out.println("外部调用项次-SERNO:" + returnStructure.getString("SERNO"));
            System.out.println("SAP物料号-MATNR:" + returnStructure.getString("MATNR"));
            System.out.println("消息类型-STATU:" + returnStructure.getString("STATU"));
            System.out.println("消息描述-MSGTXT:" + returnStructure.getString("MSGTXT"));
        }
        // 调用接口返回状态
        String result = function.getExportParameterList().getString("O_SUBRC");
        // 调用接口返回信息
        String message = function.getExportParameterList().getString("O_MSGTXT");
        System.out.println("调用返回状态--->" + result + ";调用返回信息--->" + message);
        // 将返回结果转换为xml
        JCoParameterList tblexport = function.getTableParameterList();
        String msg = tblexport.toXML();
        System.out.println("调用返回表XML--->" + msg);
        
    }
}如果出现如下结果表明连接失败,请检查连接类。


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


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


  • 缺少build path


这个错误是因为缺少了上述提到的文件,请按照文中提到的顺序将其加入项目中即可。

  • MAC配置JCO,与找不到sapjco3异常

  • 第一种解决方式:配置虚拟机参数:-Djava.library.path=之前环境变量路径

    • Intellj IDEA 配置VM options,如图:




    • 第二种解决方式:把jnilib文件放到 jdk目录的【jre/lib】目录下面去



参考文章:
SAP Java Connector 组件介绍
JAVA使用JCo连接SAP
JAVA调用SAP端RFC接口
回复

使用道具 举报

2

主题

12

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2023-3-30 17:27:35 | 显示全部楼层
超详细
回复

使用道具 举报

4

主题

12

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2023-3-30 17:28:10 | 显示全部楼层
很有用
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表