安装JCo3
- windows安装
将sapjco3.dll拷贝到c:/windows/system32与C:\Program Files (x86)\Java\jdk1.7.0_51\bin下,将sapjco3.jar加入项目的classpath中 - linux安装
sapjco3.jar放入Java的lib目录下,将libsapjco3.so放入Java/jre/lib/amd64/server目录下,编辑环境变量文件profile 。vim /etc/profile
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-3.b13.el7_5.x86_64 export JRE_HOME=$JAVA_HOME/jre export CLASS_PATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/sapjco3.jar:$JRE_HOME/lib export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH export LD_LIBRARY_PATH=dir:$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/server
使其生效即可
注意:打包运行程序时不需将包命名为sapjco3.jar,否则会报错(命名失败)
kettle使用sap插件
- windows下配置
将sapjco3.jar拷贝至data-intergration/lib下,sapjco3.dll拷贝至data-intergration/libswt/相应系统下 - linux下配置
将sapjco3.jar,libsapjco3.so拷贝至data-integration/libswt/linux/x86_64下即可
创建jco3连接
JCo连接到SAP服务器有两种方法,分别是直连和通过连接池进行连接。其差别在于,打开直连连接后可以一直保持连接;连接池则是在需要时才建立连接,连接暂不需要时,将被释放回连接池,再分配给其他用户使用。在网络服务器应用程序里,一般采用连接池进行连接SAP服务器。
DestinationDataProvider接口(不需连接属性配置文件)
上面直接连接、连接池,两种连接方法都需要先建立一个属性配置文件,然后JCo再从建立好文件里读取连接到SAP服务器所需要的连接属性,这个方法很难在实际的环境中应用,存储SAP连接属性配置信息到一个文件里,是比较不安全的。然而,JCO为我们提供了另外一种连接的方法:DestinationDataProvider,通过它我们就可以将一个连接变量信息存放在内存里
import java.util.HashMap;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
importcom.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
public class CustomSAPDestinationDataProvider {
static class MyDestinationDataProvider implements DestinationDataProvider {
privateDestinationDataEventListenereL;
private HashMap<String, Properties>destinations;
private static MyDestinationDataProvider provider = new MyDestinationDataProvider();
private MyDestinationDataProvider() {// 单例模式
if (provider == null) {
destinations = new HashMap<String, Properties>();
}
}
public static MyDestinationDataProvider getInstance() {
return provider;
}
// 实现接口:获取连接配置属性
public Properties getDestinationProperties(String destinationName) {
if (destinations.containsKey(destinationName)) {
return destinations.get(destinationName);
} else {
throw new RuntimeException("Destination " + destinationName
+ " is not available");
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
/**
* Add new destination 添加连接配置属性
*
* @param properties
* holds all the required data for a destination
**/
void addDestination(String destinationName, Properties properties) {
synchronized (destinations) {
destinations.put(destinationName, properties);
}
}
}
public static void main(String[] args) throws Exception {
// 获取单例
MyDestinationDataProvider myProvider = MyDestinationDataProvider
.getInstance();
// Register the MyDestinationDataProvider 环境注册
Environment.registerDestinationDataProvider(myProvider);
// TEST 01:直接测试
// ABAP_AS is the test destination name :ABAP_AS为目标连接属性名(只是逻辑上的命名)
String destinationName = "ABAP_AS";
System.out.println("Test destination - " + destinationName);
Properties connectProperties = new Properties();
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.123");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
// Add a destination
myProvider.addDestination(destinationName, connectProperties);
// Get a destination with the name of "ABAP_AS"
JCoDestination DES_ABAP_AS = JCoDestinationManager
.getDestination(destinationName);
// Test the destination with the name of "ABAP_AS"
try {
DES_ABAP_AS.ping();
System.out.println("Destination - " + destinationName + " is ok");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Destination - " + destinationName
+ " is invalid");
}
// TEST 02:连接池测试
// Add another destination to test
// ABAP_AS2 is the test destination name
String destinationName2 = "ABAP_AS2";
System.out.println("Test destination - " + destinationName2);
Properties connectProperties2 = new Properties();
connectProperties2.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.123");
connectProperties2.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties2
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties2.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
connectProperties2.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties2.setProperty(DestinationDataProvider.JCO_LANG, "en");
connectProperties2.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
"10");
connectProperties2.setProperty(
DestinationDataProvider.JCO_POOL_CAPACITY, "3");
// Add a destination
myProvider.addDestination(destinationName2, connectProperties2);
// Get a destination with the name of "ABAP_AS2"
JCoDestination DES_ABAP_AS2 = JCoDestinationManager
.getDestination(destinationName2);
// Test the destination with the name of "ABAP_AS2"
try {
DES_ABAP_AS2.ping();
System.out.println("Destination - " + destinationName2 + " is ok");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Destination - " + destinationName2
+ " is invalid");
}
}
}
单链接和组连接配置
//单链接
/* connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "127.0.0.1");//服务器
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "02"); //系统编号
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "666"); //SAP集团客户端号
connectProperties.setProperty(DestinationDataProvider.JCO_USER, "RF_XXX"); //SAP用户名
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "*******"); //密码
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "zh"); //登录语言
connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, "20"); //最大连接数
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "40"); */ //最大连接线程
//组连接
connectProperties.setProperty(DestinationDataProvider.JCO_R3NAME, "CQQ"); // System ID
connectProperties.setProperty(DestinationDataProvider.JCO_MSHOST, "127.0.0.1");//服务器
connectProperties.setProperty(DestinationDataProvider.JCO_MSSERV, "8888");//服务端口
connectProperties.setProperty(DestinationDataProvider.JCO_GROUP, "MC_XXX_ALL"); //组名称
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "888"); //SAP集团客户端号
connectProperties.setProperty(DestinationDataProvider.JCO_USER, "RF_XXX_C101"); //SAP用户名
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "xxxxxx"); //密码
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "zh"); //登录语言
connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, "20"); //最大连接数
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "40"); //最大连接线程
connectProperties.setProperty(DestinationDataProvider.JCO_MAX_GET_TIME, "240"); //最大连接时间
实现调用SAP的RFC函数(整理)(附一篇看起来比较全面的说明)
// 获取RFC返回的字段值
JCoDestination destination = SAPConn.connect();
function = destination.getRepository().getFunction("ZCHENH001");
JCoParameterList exportParam = function.getExportParameterList();
String exParamA = exportParam.getString("field_A");
String exParamB = exportParam.getString("field_B");
function.execute(destination);执行函数
// 遍历RFC返回的表对象
JCoTable tb = function.getTableParameterList().getTable("table_name");
for (int i = 0; i < tb.getNumRows(); i++) {
tb.setRow(i);
System.out.println(tb.getString("field01"));
System.out.println(tb.getString("field02"));
}
}
在执行任何操作之前,必须先建立到SAP系统的连接。本文示例使用的用户名是“DDIC”,登录密码是“minisap”
类JCO是Jco库中最主要的一个入口,它提供了许多静态方法。其中有一系列重载的createClient方法可以用来创建与SAP系统的连接信息。这些信息都保存在返回的JCO.Client类的实例中。常用的两种方式如下:
直接输入参数:
JCO.Client myConnection = JCO.createClient("000", "DDIC", "minisap", "EN", "10.0.0.11", "00");
使用Java Properties:
Properties logonProperties = new Properties(); logonProperties.put("jco.client.ashost","10.0.0.11"); logonProperties.put("jco.client.client","000"); logonProperties.put("jco.client.passwd","minisap"); logonProperties.put("jco.client.sysnr","00"); logonProperties.put("jco.client.user","DDIC");
JCO.Client myConnection = JCO.createClient( logonProperties ).
第一种方式比较简单,所有的参数都直接写在代码中。相比之下,第二种方式使用Java Properties,它好处在于,除了硬编码这种方式之外,用户也可以将连接信息保存在一个单独的.properties文件中。这样即使连接信息改变也无需改变代码,只需要修改.properties文件中的数据即可。关于.properties文件的用法,请参考相关的Java语言教程。
JCO.Client提供方法connect方法来建立从当前Java进程到SAP服务器的连接。
this.myConnection.connect();
可以使用isAlive方法来获取一个连接的状态,还可以使用disconnect方法来关闭一个连接:
if ( myConnection != null && myConnection.isAlive())
很多情况下,频繁创建新的连接可能导致严重的性能问题。典型的情况就是在Web应用程序中,如果每个session创建一个连接,那么用户数量很多的时候系对系统来说就是一场灾难。JCo库支持以连接池的形式重用已创建的连接。只需要调用JCO类的静态方法addClientPool即可创建一个连接池,并且可以在参数中指定连接池的名字和允许同时激活的最大连接数。
如下代码演示了如何创建一个名为“Sample_Pool”的JCo连接池:
public static final String POOL_NAME = "Sample_Pool";
public static final int max_connection = 2;
……
JCO.Pool pool = JCO.getClientPoolManager().getPool(POOL_NAME);
if (pool == null) {
Properties logonProperties = new Properties();
logonProperties.put("jco.client.ashost","10.0.0.11"); logonProperties.put("jco.client.client","000"); logonProperties.put("jco.client.passwd","minisap"); logonProperties.put("jco.client.sysnr","00"); logonProperties.put("jco.client.user","DDIC");
JCO.addClientPool( POOL_NAME, // pool name
max_connection, // max num of connections,
logonProperties); // properties
}
创建好连接池之后,可以通过如下代码来从连接池中获取一个连接:
mConnection = JCO.getClient(POOL_NAME);
在连接使用完毕之后,不要忘记使用releaseClient方法释放当前连接:
JCO.releaseClient( myConnection ).
如果需要移除连接池,则可以使用如下代码:
JCO.removeClientPool(POOL_NAME);
移除连接池将导致其中所有的活动连接被强行关闭,所以必须在确保连接池中所有的连接都不再被使用的时候才能执行该操作。
调用Function Modules
为了演示如何使用JCo库来调用远程的ABAP函数,本文示例中使用NetWeaver ABAP试用版系统中的一个样例函数BAPI_FLIGHT_GETLIST。
JCo库使用RFC的方式来调用ABAP中的函数,所以被调用的函数必须已经勾选“Remote-enabled”属性。
调用一个函数之前,需要知道函数的元数据,比如函数名字,输入输出参数等等。在JCo库中,必须通过类JCO.Repository来获取所有的ABAP函数的元数据,所以第一步是创建一个JCO.Repository类的对象:
JCO.Repository myRepository = new JCO.Repository("Repository", myConnection);
JCO.Repository类的构造函数有两个参数,第一个是可以任意指定的名字,第二个是当前使用的连接。此处也可以直接指定一个连接池的名字,JCo库将自动从该连接池中获取连接。
此时,必须保证该连接使用的用户名在目标SAP服务器上有足够的权限。
获得JCO.Repository类的实例之后,就可以通过该实例来获得函数的信息。如下代码演示了如何获取函数BAPI_FLIGHT_GETLIST的信息以及如何设置简单类型的参数:
String strFunc = "BAPI_FLIGHT_GETLIST";
IFunctionTemplate ft = myRepository.getFunctionTemplate(strFunc.toUpperCase());
JCO.Function funGetList = ft.getFunction();
// set up scalar parameter
JCO.ParameterList input = funGetList.getImportParameterList();
input.setValue(10, "MAX_ROWS");
JCO.Function对象提供了对应的方法来获取ABAP函数的参数列表。例如,上例中的getImportParameterList方法返回该函数的Import参数列表。
在上面的示例代码中,仅仅设置了一个最简单的int类型的参数。事实上,setValue方法有许多重载形式,允许设置各种复杂类型的参数,比如structure类型和table类型的参数。而且,除了通过参数名字引用要设置的参数之外,还可以通过参数的索引来引用一个参数。
在设置structure和table类型的参数之前,需要通过JCO.Function对象的方法获取相应的JCO.Structure和JCO.Table对象,然后才可以使用对每个字段进行赋值。
在我们使用的函数BAPI_FLIGHT_GETLIST中,Import参数中的DESTINATION_FROM是一个structure,其中包含一个CITY字段。如下代码演示了如何将CITY字段赋值为“NEW YORK”:
// set up structure parameter
JCO.Structure sFrom = input.getStructure("DESTINATION_FROM");
sFrom.setValue("NEW YORK", "CITY");
input.setValue(sFrom, "DESTINATION_FROM");
类似地,可以使用JCO.Function对象的getTableParameterList方法拿到Table参数列表。下面的代码演示了如何拿到一个名为DATE_RANGE的Table参数并且为它创建两行:
// set up table parameter
JCO.Table tDateRange = funGetList.getTableParameterList()
.getTable("DATE_RANGE");
tDateRange.appendRow();
tDateRange.setRow(0);
tDateRange.setValue("I", "SIGN");
tDateRange.setValue("EQ", "OPTION");
tDateRange.setValue("20070606", "LOW");
tDateRange.appendRow();
tDateRange.setRow(1);
tDateRange.setValue("I", "SIGN");
tDateRange.setValue("EQ", "OPTION");
tDateRange.setValue("20070704", "LOW");
参数设置完毕之后,可以通过JCO.Client对象的execute方法执行远程调用:
myConnection.execute(funGetList);
获得输出参数的方法与输入参数完全一样。下面的代码演示了如何获取一个包含返回值的Table参数,并且输出它的内容:
// get table results
JCO.Table flights = funGetList.getTableParameterList().getTable(
"FLIGHT_LIST");
for (int i = 0; i < flights.getNumRows(); i++) {
flights.setRow(i);
System.out.println("Airline ["
+ flights.getString("AIRLINE")
+ "] from city "
+ flights.getString("CITYFROM")
+ " to city "
+ flights.getString("CITYTO")
+ ", departure time is "
+ flights.getDate("FLIGHTDATE"));
}
JCO.Structure和JCO.Table都继承自类JCO.Record。JCO.Record对每种类型的参数都提供了对应的get和set方法,并且在运行时自动进行Java数据类型和ABAP数据类型之间的转换。限于篇幅,本文不再详叙,请参考JCo库的JavaDoc文档。
在使用JCo库的过程中,主要有两种类型的异常需要处理:
JCO.AbapException
如果ABAP函数执行过程中出现异常,则在Java进程中会触发该异常。
JCO.ConversionException
当执行参数的get和set方法时,如果在Java类型和ABAP类型之间转换失败,则会触发该异常。
作为一种最佳实践,建议使用try-catch封装使用JCo库进行参数设置和函数调用的代码,处理上述两种异常,并且在finally代码块中,释放当前所使用的连接。
远程调试
一般的情况下,在SAP服务器上通过事务代码SE37可以测试ABAP函数。在保证ABAP函数的正确性之后,Java客户端只需要检查输入输出参数是否正确即可。这时可以利用JCo库为了方便调试而提供的了一个很强大的功能,把所有继承自JCO.Record的类的对象格式化输出到一个指定的HTML文档中。通过这种方式,我们可以检查输入输出参数是否正确。比如如下代码输出前面得到的FLIGHT_LIST参数的内容:
JCO.Table flights = funGetList.getTableParameterList().getTable(
"FLIGHT_LIST");
flights.writeHTML("c:flight_list.html");
更进一步地,通过启动ABAP的远程调试功能,可以像调试普通程序一样调试远程调用的ABAP函数。
要实现远程调试,首先需要在Java代码中,通过调用JCO.Client类或者JCO.Pool类的setAbapDebug方法激活JCo的ABAP调试功能。如下代码演示了如何激活一个连接池的ABAP调试功能:
JCO.Pool pool = JCO.getClientPoolManager().getPool(POOL_NAME);
pool.setAbapDebug(true);
如果一个连接池的ABAP调试功能被激活,那么其中的所有连接的ABAP调试功能都会被激活。使用这样一个连接来调用ABAP函数的时候,SAP系统会自动弹出一个调试器窗口(如下图所示)。当然,前提是客户端机器上已经安装了SAPGUI。
这时候调试器仅仅停留在RFC调用的入口处,而并未进入所调用的ABAP函数。
为了让调试器直接进入ABAP函数,需要在事务代码SE37中为该函数设置一个外部断点(External BreakPoint)。
设置好外部断点之后,还必须通过事务代码SRDEBUG激活远程调试功能。
如果之前没有直接在代码上设置过外部断点的话,也可以直接在SRDEBUG中设置断点所在的Function Module。 要注意的是,在调试结束之前,不要关闭SRDEBUG的窗口,否则系统将会立即关闭远程调试功能。
此时,再执行Java代码,将会发现系统自动打开的DEBUG窗口自动停留在我们所设置的外部断点的位置,而Java进程在调试结束之前将会被挂起。
在ABAP调试器中,可以检查通过RFC协议传过来的参数,以及ABAP程序运行的结果。通过这种方式,可以更清晰地跟踪整个执行过程。
调试结束的时候,不要忘记关闭SRDEBUG的窗口和清除外部断点。