Invoking procedure defaults without binding values to parameters in Jdbc - java

I am trying to call a PL/SQL procedure which has defaults defined for some of its parameters. I am doing this using CallableStatement in JDBC.
This procedure has a large number of parameters with defaults defined. I do not want to explicitly set the defaults in the Java code. Doing this would make maintaining the code harder. If the PL/SQL code changes , would have to make the same changes in the Java code too.
Is it possible to accomplish this in JDBC ? For instance just bind values to the parameter you are interested in and ignore the rest.
I tried this on the following sample procedure :
-- PURPOSE: Takes a parameter which has defaults set. Returns the value of the same parameter
-- Example of: FUNCTION that takes a parameter with DEFAULT values
FUNCTION handle_defaults(empId IN NUMBER DEFAULT 20 , empCity IN VARCHAR2) RETURN NUMBER IS
BEGIN
RETURN empId;
EXCEPTION
WHEN others THEN
dbms_output.put_line('Error!');
END handle_defaults;
Here is the relevant portions of the code (NOTE: Have stripped off the try catch block , cleaning up of database resources etc for sake of readability)
// Create a database connection
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
// Create a query string
String queryStr = "{ ? = call HR.EMP_PKG.handle_defaults( ? , ? ) }";
// Create a Callable Statements
callStmt = conn.prepareCall(queryStr);
// Bind values to the IN parameter
callStmt.setString(3, "Mumbai");
// Register OUT parameter
callStmt.registerOutParameter(1, java.sql.Types.NUMERIC);
// Execute the Callable Statement
callStmt.execute();
// Retrieve the value of the OUT parameter
parameterValue = callStmt.getInt(1);
System.out.println("Value returned : " + parameterValue);
I get the following error:
Exception occured in the database
java.sql.SQLException: Missing IN or OUT parameter at index:: 2
Database error code: 17041
As a desperate attempt I also tried to pass Nulls for those parameters. Just included the following line:
callStmt.setNull(2, java.sql.Types.NUMERIC);
I get the following result:
Value returned : 0
That makes sense bcoz setNull supplies SQL Null to the parameter.
I am using Oracle 11g and Oracle 12c Jdbc Driver Version 12.1.0.2.

I don't think there's a simple answer to this question, and that's not because of JDBC, but because of Oracle.
Put simply, the only way I know of calling a procedure and using the default value for a parameter is to not specify the parameter when you call the procedure.
If you are writing
String queryStr = "{ ? = call HR.EMP_PKG.handle_defaults( ? , ? ) }";
you are always specifying both parameters, so you can never use the default value for one of them. If you only want to specify one of them and use the default for the other, write:
String queryStr = "{ ? = call HR.EMP_PKG.handle_defaults( empCity => ? ) }";
In this case you need to specify the parameter name in the call, as the first parameter is the optional one. If the second parameter was optional instead, the parameter name can be dropped.
Unfortunately, this becomes quite complicated for your real procedure with lots of parameters. What I would do would be to:
Use a StringBuilder to build up the procedure call string.
Run through the parameters, adding paramName => ? parts to it for each parameter you have a value for. Ensure the parts are separated by commas.
Prepare a CallableStatement using the output of the StringBuilder.
Run through the parameters again and call various setString/setInt/setDate/etc. methods on the CallableStatement for each parameter you have a value for.

Related

Error ORA-00900 while calling a Oracle Function from Java

I am getting ORA-00900 error while trying the call the Oracle function from code, whereas it works from sqldeveloper.
Code from which I am trying to call the Oracle function is below.
passwordCriteria = session.doReturningWork(connection -> {
try (CallableStatement function = connection
.prepareCall("{ ? = call test$ui_mob.password_change (?,?,?,?) }")) {
function.setEscapeProcessing(false);
function.registerOutParameter("p_response", Types.VARCHAR);
function.setString("p_username", "TEST_USER");
function.setString("p_companycode", "BSH");
function.setString("p_deviceid", "123456798");
function.setString("p_language", "en");
function.executeQuery();
return function.getString(1);
}
});
}
and stack trace is
Caused by: java.sql.SQLSyntaxErrorException: ORA-00900: invalid SQL
statement
According to the JavaDoc:
The interface used to execute SQL stored procedures. The JDBC API provides a stored procedure SQL escape syntax that allows stored procedures to be called in a standard way for all RDBMSs. This escape syntax has one form that includes a result parameter and one that does not. If used, the result parameter must be registered as an OUT parameter. The other parameters can be used for input, output or both. Parameters are referred to sequentially, by number, with the first parameter being 1.
{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
{call <procedure-name>[(<arg1>,<arg2>, ...)]}
It looks like there should be no spaces between {?=, while you have spaces.

JPA calling stored procedure with argument

I have this procedure in my postgreSQL database:
CREATE OR REPLACE FUNCTION getItemsForCategory(categoryId integer)
RETURNS SETOF ITEM AS $_$
DECLARE
result ITEM;
BEGIN
FOR result IN SELECT *
FROM item it
JOIN item_category itcat ON it.id = itcat.item_id WHERE itcat.category_id = categoryId LOOP
RETURN NEXT result;
END LOOP;
END; $_$ LANGUAGE 'plpgsql';
which works excellent using terminal, but I have trouble with calling it using JPA. Here is my code snippet(4 is value of argument cateforyId):
transactions.begin();
final StoredProcedureQuery storedProcedureQuery = entityManager.createStoredProcedureQuery("getItemsForCategory");
storedProcedureQuery.setParameter(1,4).execute();
final List<ItemEntity> itemEntityList = (List<ItemEntity>) storedProcedureQuery.getResultList();
transactions.commit();
after running code above I receive this error:
Exception in thread "main" java.lang.IllegalArgumentException:
You have attempted to set a parameter at position 1 which does not exist in this query string getItemsForCategory
Has anyone some idea how to set the value of argument correctly? I've also tried to set parameter using 0 instead of 1, calling setParameter with others datatypes of arguments (String,Object) but everytime I am receiving familiar kind of error like the one, which is shown there. Thank you very much
You need to register your parameters, before setting values.
spq.registerStoredProcedureParameter("categoryId", int.class, ParameterMode.IN);
See this link.

How to pass null to SQL parameter in java?

I have a scenario where in i have to pass null to a SQL parameter through java but i am getting SQLGrammarException. If i am passing some value it works fine. Please guide if i am wrong somewhere.
Below is my code:
StringBuffer query;
query = new StringBuffer(
"SELECT * "+
"FROM table(package.func(travel_type => travel_search_type("+
"travel_place_no => :travelPlaceNo"+
")))" );
Query query1 = em.createNativeQuery(query.toString());
query1.setParameter("travelPlaceNo",null);
searchresults = query1.getResultList();
Exception:
org.hibernate.exception.SQLGrammarException
This is what i do through SQL Developer and it works fine:
SELECT *
FROM table(package.func(travel_type => travel_search_type(
travel_place_no => NULL))) ;
Please guide.
While calling the 2 argument signatures of the method, null is not an allowed value. You can use, instead, the 3 argument signature setParameter(String name,
Object val,
Type type) specifying the data type and it should work.
EDIT:
Ok, I see there is also another problem: even if the replacemente worked, what you are trying to execute is a >= NULL. In this case, I'm afraid that you are going to have to handle mannually the StringBuffer creation. Maybe just force an always false condition without parameters if its null, like `1!=2', and otherwise just handle it as you are doing on your sql example (write mannually 'NULL' instead of the parameter placeholder).

#Resolved - Positional parameter does not exist in query // stored function call

I have this method to call a stored function from ORACLE, in java (spring) - using entity manager + createNativeQuery ..
(...)
String set_professional = "{? = call
pk_backoffice.set_professional(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?}";
//32 parameters IN
query = entity.createNativeQuery(set_professional);
(...)
And everytime I try to test it, it shows:
Positional parameter does not exist: 31 in query: {? = call (...)
But do I have something at position 31..it exists..
query.setParameter(31, prof.getFax()); // fax
Also, I started the parameters at 1 cause in previous exceptions it said it was 1-based
I've tried with a string and a null value instead of the get, still the same outcome..
About the query, I also counted the ? many times, so I'm sure it has 32 (for parameters) + 1(return - first ?)...
Can anyone help?
Found a solution, I replaced all ? for variables, even the first one, and the error disappeared.

Implementing a Stored Procedure Call with Optional Parameters in Spring 3.0

I've been trying to find a way to make stored proc calls from Spring 3.0 to Oracle 11.2 with following the items in mind:
Many of the calls are made to stored procedures that have plethora of
optional parameters (parameters that have default values).
There are stored procedures that can have a mix of: IN, OUT, and/or IN OUT.
I do not need to handle the OUT's.
I would like to be able to call a stored proc with the needed parameters (required and/or optional). In other word, I do not wish to pass a value (not even a null) to optional parameters to my choosing (it seems like when null is being passed programmatically [not in PL/SQL though] to a parameter mapper, the default values don't get used). I have attempted to implement these invocations as many possible ways as I could but nothing has worked:
create or replace
procedure update_stored_proc (
h1 in boolean default false,
h2 in number,
h3 in varchar2 default 'H3',
h4 in varchar2 default 'H4',
h5 in varchar2 default 'H5',
h6 in out number
);
For update_stored_proc(), there is only two required parameters (h2 and h6), and four optional ones. My ultimate goal is to call update_stored_proc() by passing h1, h2 and h6. Even when I invoking the stored proc via SimpleJdbcCall with all the values set, I get an exception:
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(updatingDataSource)
.withProcedureName("update_stored_proc")
.withoutProcedureColumnMetaDataAccess();
simpleJdbcCall.declareParameters(new SqlParameter("h1", OracleTypes.BOOLEAN))
.declareParameters(new SqlParameter("h2", OracleTypes.NUMBER))
.declareParameters(new SqlParameter("h3", OracleTypes.VARCHAR))
.declareParameters(new SqlParameter("h4", OracleTypes.VARCHAR))
.declareParameters(new SqlParameter("h5", OracleTypes.VARCHAR))
.declareParameters(new SqlInOutParameter("h6", OracleTypes.NUMBER));
MapSqlParameterSource in = new MapSqlParameterSource()
.addValue("h1", false, OracleTypes.BOOLEAN)
.addValue("h2", 123, OracleTypes.NUMBER)
.addValue("h3", "h3", OracleTypes.VARCHAR)
.addValue("h4", "h4", OracleTypes.VARCHAR)
.addValue("h5", "h5", OracleTypes.VARCHAR)
.addValue("h6", "h6", OracleTypes.NUMBER);
simpleJdbcCall.compile();
simpleJdbcCall.execute(in);
The exception I get indicates that the column type is somehow invalid:
org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException for SQL [{call update_stored_proc(?, ?, ?, ?, ?, ?)}]; SQL state [99999]; error code [17004]; Invalid column type; nested exception is java.sql.SQLException: Invalid column type
I have replaced OracleTypes with Types and even taken out withoutProcedureColumnMetaDataAccess() but the error persists.
This question turned out to require several solutions to get to work. First and foremost, Oracle's implementation of SQL standards does not contain BOOLEAN type, even though its propriety PL/SQL script language does support it (more reason for the DBA's not to use BOOLEAN type in their stored procedures). The solution I came up with was to use an anonymous block, declare a local variable, and assign it to the BOOLEAN parameter:
DECLARE
h1_false BOOLEAN := false;
h6_ NUMBER;
BEGIN
update_stored_proc(
h1 => h1_false,
h2 => :h2,
h6 => h6_
);
END;
Note that I do not care about the value of h1 (and for that matter, the OUT parameter/h6) parameter for my particular functionality, but I could imagine by having a simple IF THEN ELSE statement, one can assign a binded parameter to h1, i.e.
DECLARE
h1_false BOOLEAN;
h6_ NUMBER;
BEGIN
IF :h1 THEN
h1_false = true;
ELSE
h1_false = false;
END IF;
update_stored_proc(
h1 => h1_false,
h2 => :h2,
h6 => h6_
);
END;
The second issue is the way Spring handles the optional parameters. If one to declare the optional parameters, then Spring's SimpleJdbcCall -- and StoredProcedure for that matter -- expects values for those parameters. Therefore, one must take care of these values when they become available. Otherwise, when the optional parameter does not have a value, you must pass NULL which will not trigger the DEFAULT value of the PL/SQL's parameter to be used. This means that your query string (in this case an anonymous PL/SQL block) must be generated dynamically. So, the anonymous block becomes:
DECLARE
h1_false BOOLEAN := false;
h6_ NUMBER;
BEGIN
update_stored_proc(
h1 => h1_false,
h2 => :h2,
h6 => h6_
I switched to StoredProcedure solution rather than SimpleJdbcCall, as it turned out to be more simpler. Albeit I must add that I had to extend StoredProcedure to create a common class for my stored procedure classes and use that extended class for my customized stored procedure classes. In your StoredProcedure class, you only declare the required parameters (make sure not to compile() the query at this point):
declareParameter(new SqlParameter("h2", OracleTypes.NUMBER));
Note: If you do not need the OUT parameter, like in my case, do not include it in your declaration. Otherwise, assign a binding variable, i.e. h6 => :h6 in the anonymous block and declare it in your StoredProcedure, i.e. declareParameter(new SqlOutParameter("h6", OracleTypes.NUMBER)); and make sure to get :h6 value out when execute() returns a Map<String, Object>. If your OUT value is of BOOLEAN type, then I do not know how to retrieve the value.
The remaining optional parameters should be dynamically constructed in your StoredProcedure. i.e.:
if (someObj.getH3() != null) {
declareParameter(new SqlParameter("h3", OracleTypes.VARCHAR));
paramMap.put("h3", someObj.getH3());
anonymousPLSQLBlockQueryString += " , h3 => :h3";
}
where paramMap represents a Map that is going to be passed to StoredProcedure#execute(paramMap). You do the same with h4 and h5, and at the end, you have to make sure to properly close the anonymous block query string, i.e.:
anonymousPLSQLBlockQueryString += " ); END;";
setSql(anonymousPLSQLBlockQueryString);
setSqlReadyForUse(true);
compile();

Categories

Resources