Readable output from Oracle procedure that returns a cursor in JMeter - oracle

I have a procedure that takes in one VARCHAR parameter and returns an OUT CURSOR which is a list, in this case 3 rows with 9 columns.
My current JDBC Request:
CALL foo.bar.procedure('123456', ?)
Query type: Callable Statement
Parameter values: OUT
Parameter types: OUT -10
Variable names: outList
Result variable name: resultList (this is always null, does it mean that the result is empty?)
Handle ResultSet: Store as Object
Response data:
-1 updates.
Output variables by position:
[1] oracle.jdbc.driver.OracleResultSetImpl#21512d0b
outList is now oracle.jdbc.driver.OracleResultSetImpl#21512d0b
I've tried getting some information from outList in a BeanShell sampler like is suggested here but I haven't been able to get anything from it except just the ResultSet object id.
I'm new to calling procedures through Jmeter, View Result Tree doesn't seem to show me any result data and I've been going back and forth with the SQL Query and the parameter values, trying to fix it, but I always have the same trouble with the output.
I've also tried something like this with similar settings:
DECLARE
refCursor sys_refcursor;
Type MyRec Is Record (
v1 varchar2(
v2 varchar2(50),
...
v13 varchar2(10));
rec MyRec;
BEGIN
foo.bar.procedure('123456',refCursor);
LOOP
FETCH refCursor INTO rec;
EXIT WHEN refCursor%NOTFOUND;
dbms_output.put_line(
rec.v1||','||
rec.v2||','||
...
rec.v13);
END LOOP;
END;
Am I calling the procedure correctly or is something missing in the JDBC Request settings?

I finally solved this by moving away from JDBC Request sampler and used the BeanShell Sampler instead.
import java.sql.*;
import oracle.jdbc.*;
import org.apache.jmeter.protocol.jdbc.config.DataSourceElement;
ResultSet rs = null;
ResultSetMetaData rsmd = null;
CallableStatement stmt;
// "myConnConfigName" is the 'JDBC Connection Configuration' variable name
Connection conn = DataSourceElement.getConnection("myConnConfigName");
try {
stmt = conn.prepareCall("CALL foo.bar.procedure(?,?)");
stmt.setString(1, "123456");
stmt.registerOutParameter(2, OracleTypes.CURSOR);
stmt.executeUpdate();
rs = (ResultSet) stmt.getObject(2);
while (rs.next()) {
rsmd = rs.getMetaData();
log.info("ColumnCount:" + rsmd.getColumnCount().toString());
log.info("RowNo:" + rs.getRow().toString());
// TODO: Store data.
// Loop through columns with rs.getString(i);
}
}
catch(Throwable ex) {
log.error("Error message: ", ex);
throw ex;
}
finally {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}

Add JSR223 PostProcessor as a child of your request
Extract the "interesting" values from the OracleResultSetImpl as per Retrieving and Modifying Values from Result Sets article, something like:
import java.sql.ResultSet
ResultSet rs = (ResultSet)vars.getObject('outList')
while (rs.next()) {
vars.put('column_1_row_1_value`, rs.getString(0))
///...
}
See JDBC Tutorial and Apache Groovy - Why and How You Should Use It for more details.

Related

In Spring Boot, how to I get the column names from a StoredProcedureQuery result?

I am working on creating a simple utility that allows our users to execute a pre-selected list of stored procedures that return a simple list result set as a JSON string. The result set varies based on the selected procedure. I am able to get the results easily enough (and pass back as JSON as required), but the results don't include the column names.
The most common answer I found online is to use ResultSetMetaData or NativeQuery, but I couldn't figure out how to extract the metadata or transform the query properly using a StoredProcedureQuery object. How do I get the column names from a StoredProcedureQuery result?
Here is my code:
#SuppressWarnings("unchecked")
public String executeProcedure(String procedure, String jsonData) {
//Set up a call to the stored procedure
StoredProcedureQuery query = entityManager.createStoredProcedureQuery(procedure);
//Register and set the parameters
query.registerStoredProcedureParameter(0, String.class, ParameterMode.IN);
query.setParameter(0, jsonData);
String jsonResults = "[{}]";
try {
//Execute the query and store the results
query.execute();
List list = query.getResultList();
jsonResults = new Gson().toJson(list);
} finally {
try {
//Cleanup
query.unwrap(ProcedureOutputs.class).release();
} catch(Exception e) {
e.printStackTrace();
}
}
return jsonResults;
}
The challenge is to get a ResultSet. In order to list the column names you need a ResultSet to do the following to access metadata. (Column names are metadata)
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
System.out.println("Column name: "+resultSetMetaData.getColumnName(1));
System.out.println("Column type: "+resultSetMetaData.getColumnTypeName(1));
You can't get ResultSet (or metadata) from javax.persistence.StoredProcedureQuery or from spring-jpa Support JPA 2.1 stored procedures returning result sets
You can with low-level JDBC as follows:
CallableStatement stmnt = conn.prepareCall("{call demoSp(?, ?)}");
stmnt.setString(1, "abcdefg");
ResultSet resultSet1 = stmnt.executeQuery();
resultSet1.getMetaData(); // etc

Need DB Table name for multiple queries executed using spring JDBCTemplate

I am executing multiple queries concurrently and retrieving the results. But, the queries belong to multiple tables so, when resultset is retrieved, it is difficult to identify that a resultset belong to which table.
Can anyone help here as to how to identify the table names for each query resultset?
I tried below code but table name is blank!!!!
public static void getColumnNames(ResultSet rs) throws SQLException {
if (rs == null) {
return;
}
// get result set meta data
ResultSetMetaData rsMetaData = rs.getMetaData();
int numberOfColumns = rsMetaData.getColumnCount();
// get the column names; column indexes start from 1
for (int i = 1; i < numberOfColumns + 1; i++) {
String columnName = rsMetaData.getColumnName(i);
// Get the name of the column's table name
String tableName = rsMetaData.getTableName(i);
System.out.println("column name=" + columnName + " table=" + tableName + "");
}
}
I am calling this method like this:
jdbcTemplate.query(sql, new ResultSetExtractor<ResultSet>() {
#Override
public ResultSet extractData(ResultSet resultSet) throws SQLException,
DataAccessException {
getColumnNames(resultSet);
return resultSet;
}
});
Please advise, what is done wrong here? :(
You're not doing anything wrong here. The problem is caused by the method itself in connection with your DBMS or your JDBC driver, respectively.
See this doc please. 'table name or "" if not applicable' suggests that in your case the DBMS/driver does not provide the required information, causing the method to return an empty string.
I'm afraid, you'll have to find another way to detect which query the result originated from.

Unable to call stored procedure in oracle through JDBC

I am trying to call a stored procedure where i am inserting 7 values into a table. But the below code is not working, please tell what am i doing wrong ?
i do not get any error, the page just remains static though after successful query execution it is suppose to redirect to a new page.
public class admincontrol extends TagSupport
{
HttpServletRequest request;
HttpServletResponse response;
String msg="";
public int doStartTag() throws JspException
{
request=(HttpServletRequest)pageContext.getRequest();
response=(HttpServletResponse)pageContext.getResponse();
return EVAL_PAGE;
}
public void check ()
{
JspWriter out=pageContext.getOut();
Connection con;
CallableStatement stmt;
ResultSet rs;
try
{
try
{
Class.forName("oracle.jdbc.driver.OracleDriver");
}
catch(ClassNotFoundException ex)
{
out.println(ex.getMessage());
}
HttpSession mysession=request.getSession();
String sess=(String)mysession.getAttribute("user");
String rr=(String)adminmodel.time.trim();
String tempid=(String)adminmodel.employeid.trim();
String tdept=(String)adminmodel.department.trim();
String tsup=(String)adminmodel.supervisor.trim();
String tact=(String)adminmodel.action.trim();
String tdate=(String)adminmodel.date.trim();
HttpSession session1=request.getSession();
session1.setAttribute("requestnum",rr);
Random rand = new Random();
int r= rand.nextInt(80001) + 19999;
String reff = String.valueOf(r);
if (!tempid.matches(".*[%#^<>&;'\0-].*") && !tdept.matches(".*
[%#^<>&;'\0-].*") && !tsup.matches(".*[%#^<>&;'\0-].*"))
{
if (tempid.equals(sess) )
{
if (adminmodel.department!="" && adminmodel.supervisor!="" && adminmodel.action!="" && adminmodel.date!="" && adminmodel.time!="")
{
try
{
con= DriverManager.getConnection("jdbc:oracle:thin:#localhost:1521:XE","gaurav","oracle");
stmt=con.prepareCall("begin requestdetail (?,?,?,?,?,?,?); end;");
stmt.setString(1,tempid);
stmt.setString(2,tsup);
stmt.setString(3,tdept);
stmt.setString(4,tact);
stmt.setString(5,tdate);
stmt.setString(6,rr);
stmt.setString(7,reff);
rs=stmt.executeQuery();
response.sendRedirect("requestnum.jsp");
}
catch(SQLException ex)
{
out.println(ex.getMessage());
}
catch(Exception ex)
{
out.println(ex.getMessage());
}
}
else
out.println("Enter complete details");
}
else
out.println("Incorrect Employee Id");
}
else
out.println("Invalid Details ");
}
catch(Exception ex)
{
}
}
public int doEndTag() throws JspException
{
check();
return super.doEndTag();
}
}
Below is the stored procedure
create or replace procedure requestdetail (id number, sup varchar2, department varchar2,aaction varchar2, adate number,atime number, ref number)
is
begin
insert into myadmin(employe_id,supervisor,department,action,sdate,stime,reference_no)values (id,sup,department,aaction,adate,atime, ref);
end;
/
You can't use a procedure in a query, only a function. To execute a procedure you need to do:
stmt=con.prepareCall("{ call requestdetail (?,?,?,?,?,?,?) }");
stmt.setString(1,tempid);
stmt.setString(2,tsup);
stmt.setString(3,tdept);
stmt.setString(4,tact);
stmt.setString(5,tdate);
stmt.setString(6,rr);
stmt.setString(7,reff);
rs=stmt.execute();
Or if you prefer you can pass a PL/SQL block:
stmt=con.prepareCall("begin; requestdetail (?,?,?,?,?,?,?); end;");
You will also need to commit at some point, unless you have auto commit turned on for the connection.
Also, you're setting all the parameters using setString, and you said in a comment that the table was created with all varchar2 columns; but your procedure is expecting numbers for the id, adate and atime columns:
create or replace procedure requestdetail (id number, sup varchar2,
department varchar2, aaction varchar2, adate number, atime number,
ref number) is
That will get a numeric or value error if the values you pass in for tempid, tdate and rr are not actually numbers. It looks like you're expecting tempid at least to be a string.
But if they are numeric (or, as the name suggests, date) values then your table columns should be of the appropriate type anyway, not all varchar2. Always use the correct data type; don't try to store numbers or dates as strings, it will only cause you pain later. It affects performance as Oracle can't optimise execution plans and indexes based on the actual data type. But more importantly because you can get invalid or just bad data, which you might not notice until much later, and will struggle to correct.
If someone inserts a string into your 'number' field nothing will stop them; but at some point when you query with where adate = 1 it'll throw an error. Same with dates, but even worse depending on the format(s) used - even with a single format, if you think everything is DD/MM/YYYY and someone puts in a value by mistake as MM/DD/YYYY, again you won't know; and it'll either work when retrieved (if the DD and MM are both <= 12) but have the wrong value and you will have no way to tell; or it'll fail when retrieved. If the column was DATE then it's up to the person inserting to get it right, not up to you to try and fix mistakes when you retrieve the data.

Query returning values from Oracle and no records when run from Java

This query is returning the record with Min Create time Stamp for the Person Pers_ID when I run it in SQL Developer and the same query is not returning any value from Java JDBC connection.
Can you please help?
select PERS_ID,CODE,BEG_DTE
from PRD_HIST H
where PERS_ID='12345'
and CODE='ABC'
and CRTE_TSTP=(
select MIN(CRTE_TSTP)
from PRD_HIST S
where H.PERS_ID=S.PERS_ID
and PERS_ID='12345'
and EFCT_END_DTE is null
)
Java Code
public static List<String[]> getPersonwithMinCreateTSTP(final String PERS_ID,final String Category,final Connection connection){
final List<String[]> personRecords = new ArrayList<String[]>();
ResultSet resultSet = null;
Statement statement = null;
String PersID=null;
String ReportCode=null;
String effBegDate=null;
try{
statement = connection.createStatement();
final String query="select PERS_ID,CODE,EFCT_BEG_DTE from PRD_HIST H where PERS_ID='"+PERS_ID+"'and CODE='"+Category+"'and CRTE_TSTP=(select MIN(CRTE_TSTP) from PRD_HIST S where H.PERS_ID=S.PERS_ID and PERS_ID='"+PERS_ID+"' and EFCT_END_DTE is null)";
if (!statement.execute(query)) {
//print error
}
resultSet = statement.getResultSet();
while (resultSet.next()) {
PersID=resultSet.getString("PERS_ID");
ReportCode=resultSet.getString("CODE");
effBegDate=resultSet.getString("EFCT_BEG_DTE");
final String[] personDetails={PersID,ReportCode,effBegDate};
personRecords.add(personDetails);
}
} catch (SQLException sqle) {
CTLoggerUtil.logError(sqle.getMessage());
}finally{ // Finally is added to close the connection and resultset
try {
if (resultSet!=null) {
resultSet.close();
}if (statement!=null) {
statement.close();
}
} catch (SQLException e) {
//print error
}
}
return personRecords;
}
Print out your SQL SELECT statement from your java program and paste it into SQL*Plus and see what is happening. It's likely you're not getting your variables set to what you think you are. In fact, you're likely to see the error when you print out the SELECT statement without even running it - lower case values when upper is needed, etc.
If you still can't see it, post the actual query from your java code here.
I came here with similar problem - just thought I'd post my solution for others following - I hadn't run "COMMIT" after the inserts I'd made (via sqlplus) - doh!
The database table has records but the JDBC client can't retrieve the records.
Means the JDBC client doesn't have the select privileges. Please run the below query on command line:
grant all on emp to hr;

Resultset Metadata from Spring JDBCTemplate Query methods

Is there any way I can get resultset object from one of jdbctemplate query methods?
I have a code like
List<ResultSet> rsList = template.query(finalQuery, new RowMapper<ResultSet>() {
public ResultSet mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs;
}
}
);
I wanted to execute my sql statement stored in finalQuery String and get the resultset. The query is a complex join on 6 to 7 tables and I am select 4-5 columns from each table and wanted to get the metadata of those columns to transform data types and data to downstream systems.
If it is a simple query and I am fetching form only one table I can use RowMapper#mapRow and inside that maprow method i can call ResultsetExtractor.extractData to get list of results; but in this case I have complex joins in my query and I am trying to get resultset Object and from that resultset metadata...
The above code is not good because for each result it will return same resultset object and I dont want to store them in list ...
Once more thing is if maprow is called for each result from my query will JDBCTemplate close the rs and connection even though my list has reference to RS object?
Is there any simple method like jdbcTemplate.queryForResultSet(sql) ?
Now I have implemented my own ResultSet Extractor to process and insert data into downstream systems
sourceJdbcTemplate.query(finalQuery, new CustomResultSetProcessor(targetTable, targetJdbcTemplate));
This CustomResultSetProcessor implements ResultSetExtractor and in extractData method I am calling 3 different methods 1 is get ColumnTypes form rs.getMetaData() and second is getColumnTypes of target metadata by running
SELECT NAME, COLTYPE, TBNAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME ='TABLENAME' AND TABCREATOR='TABLE CREATOR'
and in 3rd method I am building the insert statement (prepared) form target columntypes and finally calling that using
new BatchPreparedStatementSetter()
{
#Override
public void setValues(PreparedStatement insertStmt, int i) throws SQLException{} }
Hope this helps to others...
Note that the whole point of Spring JDBC Template is that it automatically closes all resources, including ResultSet, after execution of callback method. Therefore it would be better to extract necessary data inside a callback method and allow Spring to close the ResultSet after it.
If result of data extraction is not a List, you can use ResultSetExtractor instead of RowMapper:
SomeComplexResult r = template.query(finalQuery,
new ResultSetExtractor<SomeComplexResult>() {
public SomeResult extractData(ResultSet) {
// do complex processing of ResultSet and return its result as SomeComplexResult
}
});
Something like this would also work:
Connection con = DataSourceUtils.getConnection(dataSource); // your datasource
Statement s = con.createStatement();
ResultSet rs = s.executeQuery(query); // your query
ResultSetMetaData rsmd = rs.getMetaData();
Although I agree with #axtavt that ResultSetExtractor is preferred in Spring environment, it does force you to execute the query.
The code below does not require you to do so, so that the client code is not required to provide the actual arguments for the query parameters:
public SomeResult getMetadata(String querySql) throws SQLException {
Assert.hasText(querySql);
DataSource ds = jdbcTemplate.getDataSource();
Connection con = null;
PreparedStatement ps = null;
try {
con = DataSourceUtils.getConnection(ds);
ps = con.prepareStatement(querySql);
ResultSetMetaData md = ps.getMetaData(); //<-- the query is compiled, but not executed
return processMetadata(md);
} finally {
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, ds);
}
}

Resources