9
10-August-2017 How to execute an Oracle Stored Procedure with Nested Table as a parameter from Mule Flow Mule ESB 3.8.3 Priyobroto Ghosh INDEPENDENT IT CONSULTANT +91-9836733341

How to execute an oracle stored procedure with nested table as a parameter from mule flow

Embed Size (px)

Citation preview

10-August-2017

How to execute an Oracle Stored Procedure with Nested Table as a parameter from Mule Flow Mule ESB 3.8.3

Priyobroto Ghosh INDEPENDENT IT CONSULTANT +91-9836733341

How to execute an Oracle Stored Procedure with Nested Table as a parameter from Mule Flow

Objective The objective of this tutorial is to demonstrate the steps required to execute an Oracle Stored Procedure with a Nested Table as a parameter from Mule Flow.

Use Case To demonstrate the application, I will be using a simple use case of inserting employee records by calling an Oracle Stored Procedure with a Nested Table as one of the parameters. Each employee record has two columns where the first column is the employee’s department and the second column is the nested table of a data structure with employee name and employee number as attributes.

Pre-requisites JDK 1.8.x

Anypoint Studio 6.2+

Mule Runtime 3.8.3 (Embedded)

Oracle 11.2 XE

Oracle Backend Tasks In order to implement and demonstrate the use case with the mule application, you need to execute the following Oracle scripts in the same order defined below:

Create an Oracle object type EMPLOYEE with the following PL/SQL statement.

CREATE TYPE EMPLOYEE AS OBJECT

(EMPNAME VARCHAR2 (50),

EMPNO INTEGER

);

Create an Oracle nested table EMPLOYEE_LIST of type EMPLOYEE with the following PL/SQL statement.

CREATE TYPE EMPLOYEE_LIST AS TABLE OF EMPLOYEE;

Create an Oracle database table EMPLOYEE_TABLE with the second column type as nested table.

CREATE TABLE EMPLOYEE_TABLE

(DEPTNAME VARCHAR2 (20),

EMPLOYEES EMPLOYEE_LIST

) NESTED TABLE EMPLOYEES STORE AS NTABLE;

Create an Oracle stored procedure INSERT_EMPLOYEE with the second parameter type as nested table.

CREATE OR REPLACE PROCEDURE INSERT_EMPLOYEE (DEPT VARCHAR2, EMP_LIST EMPLOYEE_LIST) AS

BEGIN INSERT INTO EMPLOYEE_TABLE VALUES (DEPT, EMP_LIST);

END; /

Implementation This application is created using Mule flows developed in Anypoint Studio that exposes endpoints to execute Oracle stored procedure by passing a nested table as one the parameters and simultaneously queries the nested table.

There are two main flows in the application:

First Flow: Executes the Oracle stored procedure with a nested table of employee details as one of the

parameters using the Database connector.

This flow takes the input as CSV file through the File inbound endpoint.

<file:inbound-endpoint path="${file.path}"

moveToPattern="#[header:INBOUND:originalFilename].backup"

moveToDirectory="${move.to.dir}"

responseTimeout="10000"

doc:name="File: Reads employee data from CSV file">

<file:filename-regex-filter pattern="^.*\.([cC][sS][vV]??)$"

caseSensitive="false" />

</file:inbound-endpoint>

Next, the input is transformed into string.

<object-to-string-transformer

doc:name="Object to String: Transforms InputStream into String"/>

Next, the Splitter component splits the transformed string based on LF/CR characters. Here, each string represents an employee record containing Employee Number, Employee Name, and Department separated with a comma.

<splitter expression =

"#[org.apache.commons.lang3.StringUtils.split(payload,'\n\r')]"

doc:name = "Splitter: Splits the payload based on line feed &amp;

carriage return characters" />

Next, the Expression component splits each string based on comma and returns an array containing the employee record fields defined in the above step.

<expression-transformer Expression =

"#[org.apache.commons.lang3.StringUtils.split(payload,',')]"

doc:name="Expression: Splits the string payload based on comma

character" />

Next, the Variable component assigns the employee’s department from the array defined in the above step into a variable.

<set-variable variableName="deptName" value="#[payload[2]]"

doc:name="Variable: Stores the department name" />

Next, the Transform Message component transforms Employee Name and Employee Number array elements into a POJO.

<dw:transform-message metadata:id="b3a30f33-9f45-4d34-b4bd-76be3d4a19f"

doc:name="Transform Message: Transforms the splitted fields into

a POJO">

<dw:set-payload><![CDATA[%dw 1.0

%output application/java

---

{

empName: payload[1],

empNo: payload[0]

} as :object {class:

"com.oracle.nested.table.example.EmployeeObj"}]]></dw

:set-payload>

</dw:transform-message>

Next, the Collection Aggregator returns a collection of POJOs.

<collection-aggregator failOnTimeout="true"

doc:name="Collection Aggregator: Aggregates the POJOs into a

List" />

Next, the Groovy Script component creates an object of type oracle.sql.ARRAY from the collection of POJOs. Here, the Oracle PL/SQL nested table is mapped with oracle.sql.ARRAY (Oracle JDBC Mapping).

<scripting:component doc:name="Script: Creates SQL ARRAY from POJO

List">

<scripting:script engine = "Groovy">

<![CDATA[oracle.jdbc.pool.OracleDataSource ds =

(oracle.jdbc.pool.OracleDataSource)

muleContext.getRegistry().get("employeeDataSource");

oracle.jdbc.OracleConnection conn =

(oracle.jdbc.OracleConnection) ds.getConnection();

Object [] emp = payload.toArray();

oracle.sql.ARRAY array =

conn.createARRAY("SCHEMA_NAME.EMPLOYEE_LIST",emp);

payload = array;]]>

</scripting:script>

</scripting:component>

Finally, the Database connector executes the stored procedure with parameters department and an object of type oracle.sql.ARRAY.

<db:stored-procedure config-ref="Oracle_Configuration"

doc:name="Database">

<db:parameterized-query><![CDATA[CALL

INSERT_EMPLOYEE(:dept,:emp_list)]]></db:parameterized-query>

<db:in-param name="dept" type="VARCHAR"

value="#[flowVars.deptName]" />

<db:in-param name="emp_list" type="ARRAY"

value="#[payload]" />

</db:stored-procedure>

Second Flow: Queries the Oracle nested table using Database connector.

The flow takes the employee’s department name as a query parameter through the HTTP Listener source.

<http:listener config-ref="HTTP_Listener_Configuration"

path="/nestedTableRetrieve" doc:name="HTTP" />

Next, the department name is assigned to a variable using Variable component.

<set-variable variableName="deptName"

value="#[message.inboundProperties.'http.query.params'.dept]"

doc:name="Department Name" />

Next, a variable is created to store the result of the query using Variable component.

<set-variable variableName="result" value="#['']"

doc:name="Result" />

Next, the SQL query is executed using Database connector to retrieve Oracle nested table.

<db:select config-ref="Oracle_Configuration" streaming="true"

doc:name="Database">

<db:parameterized-query><![CDATA[SELECT e.EMPLOYEES FROM

employee_table e where DEPTNAME = #[flowVars.deptName]]]>

</db:parameterized-query>

</db:select>

Next, the Groovy component iterates the resultset using JDBC API and Collection API and subsequently, compiles the result.

<scripting:component doc:name="Script">

<scripting:script engine="Groovy"><![CDATA[

java.util.Map empMap = new java.util.HashMap();

empMap.put("SCHEMA_NAME.EMPLOYEE",

Class.forName("com.oracle.nested.table.example.EmployeeObj"));

resultSetIterator = payload;

while (resultSetIterator.hasNext()) {

java.util.Map map=resultSetIterator.next();

oracle.sql.ARRAY employeeArray =

(oracle.sql.ARRAY)map.get('EMPLOYEES');

Object[] employees =

(Object[])employeeArray.getArray(empMap);

for(emp in employees) {

com.oracle.nested.table.example.EmployeeObj employee =

(com.oracle.nested.table.example.EmployeeObj)emp;

flowVars.result = flowVars.result + '\nEmployee No: ' +

employee.getEmpNo() + '\t' + 'Employee Name: ' +

employee.getEmpName() + '\n';

}}]]>

</scripting:script>

</scripting:component>

Next, we log the result using the Logger component.

<logger message="#[flowVars.result]" level="INFO" doc:name="Logger" />

Finally, the result is transformed to payload using Set Payload transformer.

<set-payload value="#[flowVars.result]" doc:name="Set Payload" />

In the global configuration of the Database connector, I have to provide the Oracle configuration via spring-bean because the spring beans are registered in Mule Registry, they can be referred from Java Objects or Scripts, and in our case, the OracleDataSource is used in Groovy script of first flow to obtain the Oracle Connection object. The global spring bean configuration details for the Database connector is as follows:

<spring:beans>

<spring:bean id="employeeDataSource" name="employeeDataSource"

class="oracle.jdbc.pool.OracleDataSource">

<spring:property name="password" value="${password}" />

<spring:property name="URL" value="${url}" />

<spring:property name="user" value="${user}" />

<spring:property name="dataSourceName" value="ds" />

</spring:bean>

</spring:beans>

<db:oracle-config name="Oracle_Configuration"

doc:name="Oracle Configuration" dataSource-ref="employeeDataSource">

</db:oracle-config>

The property ‘url’ should be defined as ‘jdbc:oracle:thin:@server-host:1521:database-name’.

The global configuration for the HTTP Listener are left with the default values.

<http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration" />

In this application, I have used an Oracle nested table of a user-defined type or Oracle object type named EMPLOYEE that has two attributes EmpName and EmpNo respectively. I will be using the option of explicitly creating a custom Java class for SQL-Java mapping for the Oracle object EMPLOYEE. In this case, the custom Java class EmployeeObj will implement the JDBC standard java.sql.SQLData interface. The following Java class is used in the Groovy script encapsulated within the Script component of both flows. The Transform Message component in first flow transforms the input source into an object of the following Java class.

package com.oracle.nested.table.example;

import java.sql.SQLData;

import java.sql.SQLException;

import java.sql.SQLInput;

import java.sql.SQLOutput;

public class EmployeeObj implements SQLData {

private int empNo;

private String empName;

private String sql_type;

public EmployeeObj() throws SQLException {

// TODO Auto-generated constructor stub

}

public String getEmpName() {

return empName;

}

public EmployeeObj(int empNo, String empName, String sQLTypeName)

{

super();

this.empName = empName;

this.empNo = empNo;

sql_type = "SCHEMA_NAME.EMPLOYEE";

}

public void setEmpName(String empName) {

this.empName = empName;

}

public int getEmpNo() {

return empNo;

}

public void setEmpNo(int empNo) {

this.empNo = empNo;

}

public void setSQLTypeName(String sQLTypeName) {

sql_type = sQLTypeName;

}

@Override

public String getSQLTypeName() throws SQLException {

// TODO Auto-generated method stub

return "SCHEMA_NAME.EMPLOYEE";

}

@Override

public void readSQL(SQLInput stream, String typeName) throws

SQLException {

// TODO Auto-generated method stub

sql_type=typeName;

empName=stream.readString();

empNo=stream.readInt();

}

@Override

public void writeSQL(SQLOutput stream) throws SQLException {

// TODO Auto-generated method stub

stream.writeString(empName);

stream.writeInt(empNo);

}

}

Now, let us run and test the application.

Now, we will run the first flow by placing a CSV file containing the following employee records in the path polled by the File inbound endpoint and execute the stored procedure.

EMPNO EMPNAME DEPTNAME

100 Ashok Kumar SALES

200 Pankaj Shah SALES

300 Manoj Kumar SALES

400 Puneet Manjhi SALES

500 Radhika Apte SALES

600 Archana Iyer SALES

To test if the values are inserted in the underlying Oracle database table EMPLOYEE_TABLE, we can query using the second flow. Let us query for the department ‘SALES’ using the URL http://localhost:8081/nestedTableRetrieve?dept=SALES and you should see the following response:

The query result shown in the above figure ensures that the Oracle nested table column of the underlying database table EMPLOYEE is successfully inserted with values by executing the stored procedure with a nested table as one the parameters.

Conclusion The CSV file must have a set of employee records pertaining to only one department at a time at the time of flow execution. If you want to put the employee records with multiple departments then the flow logic for the first flow must be changed accordingly.