Stored Procedure does not and should not return anything results in error - linq

I have a stored Procedure that we are using to simply do an insert of a new record into various tables. This Stored Procedure is setup to not return anything only to execute. When I pull it into my DBML it sets it up as a "Void" which I think is correct. Here is the auto generated code from the DMBL.
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="dbo.sp_PTS_Action_Insert_Comment")]
public void sp_PTS_Action_Insert_Comment([global::System.Data.Linq.Mapping.ParameterAttribute(Name="Form_Name", DbType="VarChar(20)")] string form_Name, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="Form_Number", DbType="VarChar(25)")] string form_Number, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="UID", DbType="VarChar(15)")] string uID, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="Cmt", DbType="VarChar(200)")] string cmt)
{
this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), form_Name, form_Number, uID, cmt);
}
}
I call this SP as follows:
_context.sp_PTS_Action_Insert_Comment(formInformation["formType"], formInformation["formNumber"], userId, comment);
When this code executes it throws an error saying
"System Void not a valid return type
for a mapped Stored Procedure"
My question is can you use linq to Stored Procedure with SP's that purposely do not return anything? And if so what am I doing wrong in this example?

I'm unsure of why, when putting a procedure that doesn't return anything, that Linq builds the above void function, because it DOES throw the 'not a valid return type' error.
To fix this I've added a return to the procedure using SCOPE_IDENTITY() as my return so then I can use it in my code also.
Hope that helps.

Related

How to succinctly call a PL/SQL procedure and return its output variables in Spring with JdbcTemplate?

New to Oracle here. My experience is building web apps which send queries to a database and get a "result set" back, for example in Java with Spring and JdbcTemplate.
For example, here's a code sample with a regular SQL query, using a RowMapper class to turn the rows of a result set into a Java list:
public List<Widget> findAllWidgets() {
return jdbcTemplate.query("SELECT * FROM widgets", new WidgetRowMapper());
}
Now I am tasked with writing a query that calls a PL/SQL stored procedure. This procedure has two input arguments (these I can handle) and two output arguments (let's call them error_code and error_message). I want to send a query to the database that will (a) run the procedure with my inputs and (b) return the two outputs. I would be equally happy to have the two outputs as a "row" or simply bind them to two Java variables.
Here's what I've tried, and it's not throwing an error but it's not getting the output values, either. The Java variables errorCode and errorMessage remain empty strings:
public Map<String,String> callMyProcedure() {
String errorCode="";
String errorMessage="";
jdbcTemplate.update("call myprocedure(?,?,?,?)","input1","input2",errorCode,errorMessage);
return Map.of("errorCode",errorCode,"errorMessage",errorMessage);
}
The question is: How can I capture the values of the PL/SQL procedure's "OUT" variables when calling the procedure from Java with JdbcTemplate?
EDIT: I accepted Alex's answer which doesn't use JdbcTemplate, because it seems to be the better way. My own answer does use JdbcTemplate but takes a lot more code, so if you're searching for something that specifically answers the question, that will do it.
You can use plain JDBC.
final String charlie;
final String zulu;
try (CallableStatement cs = connection.prepareCall("{call myprocedure(?,?,?,?,?,?,?,?)}")) {
cs.setString(1, "foo");
cs.setString(2, "bar");
cs.setString(3, "baz");
cs.setString(4, "whisky");
cs.setString(5, "tango");
cs.setString(6, "foxtrot");
cs.registerOutParameter(7, Types.VARCHAR);
cs.registerOutParameter(8, Types.VARCHAR);
cs.execute();
connection.commit(); // optional
charlie = cs.getString(7);
zulu = cs.getString(8);
}
When using JDBC, it is dangerous to use the getInt method and similar ones, since they convert the type to primitive and zero is replaced by 0. It is better to use a (Integer) cs.getObject(). Similarly, setInt does not support the reference type.
You can get the connection under jdbcTemplate and get output using get methods as getNString
try (Connection connection = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
CallableStatement statement = connection.prepareCall("{call myprocedure(?,?,?,?,?,?,?,?)}");
statement.execute();
statement.getNString(1); // using index or your parameter name
Retrieves the value of the designated NCHAR, NVARCHAR or LONGNVARCHAR parameter as a String in the Java programming language.
I found some guidance from an older question here, and came up with this monstrosity:
public Map<String,Object> callMyProcedure() {
return jdbcTemplate.call(new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection connection) throws SQLException {
CallableStatement cs = connection.prepareCall("{call myprocedure(?,?,?,?,?,?,?,?)}");
cs.setString(1,"foo");
cs.setString(2,"bar");
cs.setString(3,"baz");
cs.setString(4,"whisky");
cs.setString(5,"tango");
cs.setString(6,"foxtrot");
cs.registerOutParameter(7, Types.VARCHAR);
cs.registerOutParameter(8, Types.VARCHAR);
return cs;
}
},Arrays.asList(
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlOutParameter("errorCode",Types.VARCHAR),
new SqlOutParameter("errorMessage",Types.VARCHAR)
));
}
It does work, but I'm looking for an answer that can do the same thing more succinctly. Maybe Spring has added a new interface to JdbcTemplate in the years since that older answer?

JDBC: Call Oracle function like a procedure

I use an Oracle procedure and I batch it.
CallableStatement st = con.prepareCall ("{call MyProc (123)}");
...
st.addBatch ();
Now the procedure was converted into a function. Therefore batching is not longer working and ignoring the return-value does not work too.
If I do not set the "? =" the function is not found.
CallableStatement st = con.prepareCall ("{? = call MyFunc (123)}");
But without batching it takes too long to run all the calls. I have to do many of them.
So is there a way I can ignore the return-value on the JDBC level without touching the function? I want my batching back.
You may of course used the Oracle syntax and ignore the function value in the PL/SQL Block.
Something like this
con.prepareCall("""
declare
v_ignore number;
begin
v_ignore := MyFunc (?);
end;""")
for oracle db it will be like:
private Long getResultOfFunction(final long param1) {
CallableStatementCallback<Long> action = new CallableStatementCallback<Long>() {
public Long doInCallableStatement(CallableStatement cs)
throws SQLException, DataAccessException {
cs.registerOutParameter(1, Types.NUMERIC);
cs.setLong(2, param1);
cs.executeQuery();
return cs.getLong(1);
}
};
return getJdbcTemplate().execute("{call ? := package_name.function_name (?)}", action);
}

How to properly call PostgreSQL functions (stored procedures) within Spring/Hibernate/JPA?

I'm using Spring MVC 4, Hibernate and PostgreSQL 9.3 and have defined function (stored procedure) inside Postgres like this:
CREATE OR REPLACE FUNCTION spa.create_tenant(t_name character varying)
RETURNS void AS
$BODY$
BEGIN
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I AUTHORIZATION postgres', t_name);
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION spa.create_tenant(character varying)
OWNER TO postgres;
If I run this function inside pgAdmin like this it's working fine:
select spa.create_tenant('somename');
Now I'm trying to run this function from my service like this:
#Override
#Transactional
public void createSchema(String name) {
StoredProcedureQuery sp = em.createStoredProcedureQuery("spa.create_tenant");
sp.registerStoredProcedureParameter("t_name", String.class, ParameterMode.IN);
sp.setParameter("t_name", name);
sp.execute();
}
If I run my method I'm getting following error:
javax.persistence.PersistenceException: org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111
I'm guessing this is because of return type void that is defined in function so I changed return type to look like this:
RETURNS character varying AS
If I run my method again I'm getting this exception instead:
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Error calling CallableStatement.getMoreResults
Does anyone know what is going on here and how to properly call stored procedures in PostgreSQL even with void as return type?
In case you are using also spring data, you could just define a procedure inside your #Repository interface like this,
#Procedure(value = "spa.create_tenant")
public void createTenantOrSomething(#Param("t_name") String tNameOrSomething);
More in the docs.
In your entity class, define a NamedNativeQuery like you would call postgresql function with select.
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.Entity;
#NamedNativeQueries(
value={
// cast is used for Hibernate, to prevent No Dialect mapping for JDBC type: 1111
#NamedNativeQuery(
name = "Tenant.createTenant",
query = "select cast(create_tenant(?) as text)"
)
}
)
#Entity
public class Tenant
hibernate is not able to map void, so a workaround is to cast result as text
public void createSchema(String name) {
Query query = em.createNamedQuery("Tenant.createTenant")
.setParameter(1, name);
query.getSingleResult();
}
Since you're using PostgreSQL, you can, as you've already written, call any stored procedure of type function in SELECT (Oracle, otherwise, would let you only execute functions declared to be read only in selects).
You can use EntityManager.createNativeQuery(SQL).
Since you're using Spring, you can use SimpleJdbcTemplate.query(SQL) to execute any SQL statement, as well.
I think it's the RETURN VOID that's causing the issue. So, changed the FUNCTION definition like this:
CREATE OR REPLACE FUNCTION spa.create_tenant(t_name character varying)
RETURNS bigint AS
$BODY$
BEGIN
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I AUTHORIZATION postgres', t_name);
RETURN 1;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION spa.create_tenant(character varying)
OWNER TO postgres;
After you changed your function to return some dummy value, change the stored procedure query to this:
StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("spa.create_tenant")
.registerStoredProcedureParameter(1,
Long.class, ParameterMode.OUT)
.registerStoredProcedureParameter(2,
String.class, ParameterMode.IN)
.setParameter(2, name);
query.getResultList();
If you want to keep it simple, just do this:
em.createSQLQuery("SELECT * FROM spa.create_tenant(:t_name) ")
.setParameter("t_name", name)").list();
Notice I used list() intentionally.. for some reason .update() didn't work for me.
PostgreSQL
Hibernate
Kotlin
CREATE OR REPLACE FUNCTION your_procedure() RETURNS text AS $$
BEGIN
RETURN 'Some text';
END;
$$ LANGUAGE plpgsql;
val query = session.createNativeQuery("SELECT your_procedure()")
query.list().map {
println("NativeQuery: $it")
}
For a procedure, try this:
#Procedure("spa.create_tenant")
String createTenant(String tenant);

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.

Call Oracle Stored procedure from JDBC with complex Input and Output type

I'm so close in solving this question but I'm apparently missing something. My requirement is to call a stored procedure in Oracle from JDBC. The stored procedure takes 1 user-defined Oracle object as INput and another user-defined Oracle object as OUTput. The INput and OUTput objects have mix of both primitive Oracle data types and collection of another set of user-defined objects. I'm successfully able to call the stored procedure and get results back as long as I set NULL for the collection types in the INput and OUTput objects. If I try to create ArrayDescriptor for the list of Oracle objects to send it to the stored procedure I keep hitting roadblocks. So I need help with figuring out how to set the Array to the INput object and set that to CallableStatement. Please note, I am aware of how I can send the primitive type and array as direct inputs to the stored procedure. But I do not want to go that way as we later have to send 10 additional fields to the procedure, I do not want to add them to method signature. Here's the list of classes. Also, there is no compilation errors for the code below.
Package in oracle:
CREATE OR REPLACE PACKAGE testPkg AS
PROCEDURE spGetTestData (
TESTDATA_IN IN TESTDATA_IN_OBJ,
TESTDATA_OUT OUT TESTDATA_OUT_OBJ
);
END;
INput object for the stored procedure:
CREATE OR REPLACE TYPE TESTDATA_IN_OBJ AS OBJECT(
testStr1 VARCHAR2(5),
arrObj1 ARR_OBJ_1_NT);
Array Object as part of INput Object:
create or replace TYPE ARR_OBJ_1_NT AS TABLE OF ARR_OBJ_1_OBJ;
UserDefined Object part of INput Object:
CREATE OR REPLACE TYPE ARR_OBJ_1_OBJ AS OBJECT
(
teststr VARCHAR2(14),
testNumber NUMBER(4),
);
TestDataINObj.java:
import java.sql.Array;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class TestDataINObj implements SQLData
{
private String sql_type = "TESTDATA_IN_OBJ";
protected String testStr1;
protected Array arrObj1;
#Override
public String getSQLTypeName() throws SQLException
{
return this.sql_type;
}
// getter and setter for fields
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
this.sql_type=typeName;
this.testStr1 = stream.readString();
this.arrObj1 = stream.readArray();
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeString(this.testStr1);
stream.writeArray(this.arrObj1);
}
}
TestDataINObjConverter.java:
public class TestDataINObjConverter
{
public static TestDataINObj convertPOJOToDBInObj(Connection connection)
throws SQLException
{
TestDataINObj testDataINObj = new TestDataINObj();
testDataINObj.setTestStr1("some string");
ArrObj1NT[] ArrObj1NTList = ArrObj1NTConverter.convertPOJOToDBObj(); // this will return Java array of ArrObj1NT class
testDataINObj.setArrObj1(getOracleArray("ARR_OBJ_1_NT",connection, ArrObj1NTList));
return testDataINObj;
}
private static Array getOracleArray(final String typeName, Connection connection, ArrObj1NT[] ArrObj1NTList) throws SQLException
{
if (typeName == null)
{
return null;
}
Array oracleArray = new ARRAY(new ArrayDescriptor(typeName, connection), connection, ArrObj1NTList);
return oracleArray;
}
Code that actually executes call to stored procedure:
... //code to get connection
..// connection is of type T4CConnection
Map typeMap = connection.getTypeMap();
typeMap.put("TESTDATA_IN_OBJ", TestDataINObj.class);
typeMap.put("TESTDATA_OUT_OBJ", TestDataOUTObj.class);
typeMap.put("ARR_OBJ_1_NT", ArrObj1NT.class);
TestDataINObj testDataINObj = TestDataINObjConverter.convertPOJOToDBInObj(connection);
getMetaDataCallableStatement = connection.prepareCall("begin " + "testPkg" + ".spGetTestData (?,?);"+ " end;");
getMetaDataCallableStatement.setObject(1, testDataINObj);
getMetaDataCallableStatement.registerOutParameter(2, Types.STRUCT, "TESTDATA_OUT_OBJ");
rs = getMetaDataCallableStatement.executeQuery();
TestDataOUTObj testDataOUTObj = (TestDataOUTObj) getMetaDataCallableStatement.getObject(2, typeMap);
Miscellaneous:
1. The objects are declared in Schema level and is available for the db user to access it.
2. I've not included all of the corresponding Java objects here as it will take more space. They implement SQLData interface and their type names match with DB names. The read and writeSQL methods uses getString, getArray and corresponding setter methods.
This is a very old approach, why are you not using "Oradata" and "Oradatum" interface?
It will save lot of effort.
Your approach leaves a lot of scopr for error, you will have to read the stream in proper manner and check for ordering of fields yourself which can be tricky. Oradata approach will do that for you.
Coming to your approach, Your code is not very clear.
But just to give an overview, StructDescriptor will map to oracle record type and ArrayDescriptor will map to oracle table type, from your code i am confused about whta you are trying to achieve.
I can help if you can make it more clear.

Resources