Is there a way I can batch a stored procedure in JDBC where only the return-value is an out-paramter?
CallableStatement ps = con.prepareCall ("{? = call MyProc (?)}");
for (Integer i : MyLst) // whatever MyLst is...
{
ps.registerOutParameter (1, Types.INTEGER);
ps.setInt (2, i);
ps.addBatch ();
}
ps.executeBatch ();
If I do so I get an exception that let me know that it is not possible with in/out or out parameters. Maybe there is a sort of workaround?
Related
I am trying to execute a simple stored procedure using Oracle OCI. The stored procedure takes a string as an input and copies it to the output parameter. Below is the oracle statement that I am executing:
DECLARE OutParam VARCHAR2(50);
BEGIN
my_stored_procedure('Test String', OutParam);
END;
And I wrote the OCI code as follows:
/* Bind a placeholder for the output parameter */
if (status = OCIBindByPos(stmthp, &bnd6p, errhp, 1,
(dvoid *)result, 1024, SQLT_STR,
(dvoid *)0, (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DEFAULT))
{
checkerr(errhp, status);
cleanup();
return OCI_ERROR;
}
/* execute and fetch */
if (status = OCIStmtExecute(svchp, stmthp, errhp, (ub4)1, (ub4)0,
(CONST OCISnapshot *) NULL, (OCISnapshot *)NULL, OCI_DEFAULT))
{
if (status != OCI_NO_DATA)
{
checkerr(errhp, status);
cleanup();
return OCI_ERROR;
}
}
With Oracle 11g and older versions, this worked fine and I was able to get the output parameter stored in the 'result' variable I used in the OCIBindByPos call.
However, with Oracle 12 and above this does not work for me, I am getting the following error:
OCI_ERROR - ORA-03137: malformed TTC packet from client rejected: [kpoal8Check-5] [32768]
Does anyone know why this does not work with Oracle versions 12 and above? I tested this with Oracle 12 and Oracle 19 and got the same error.
I have a stored procedure with the following signature and local variables:
PROCEDURE contract_boq_import(i_project_id IN RAW,
i_boq_id IN RAW,
i_master_list_version IN NUMBER,
i_force_update_if_exists IN BOOLEAN,
i_user_id IN NUMBER,
o_boq_rev_id OUT RAW) AS
v_contract_id RAW(16);
v_contract_no VARCHAR2(100);
v_series_rev_id_count NUMBER(1);
v_project_id_count NUMBER(5);
v_now DATE;
v_boq_import_rev_id RAW(16);
v_master_project_id RAW(16);
v_prj_duplicate_items VARCHAR2(1000) := '';
I set up an output parameter using one of our DAL utilities:
var revParam = new byte[16];
dataHandler.CreateParameterRaw("o_boq_rev_id", revParam).Direction = ParameterDirection.Output;
Where CreateParameterRaw is declared as:
public DbParameter CreateParameterRaw(string name, object value)
{
OracleParameter oracleParameter = new OracleParameter();
oracleParameter.ParameterName = name;
oracleParameter.OracleDbType = OracleDbType.Raw;
oracleParameter.Value = value;
this.Parameters.Add((DbParameter) oracleParameter);
return (DbParameter) oracleParameter;
}
Then when I execute the procedure with ExecuteNonQuery I get the following error:
Oracle.ManagedDataAccess.Client.OracleException
HResult=0x80004005
Message=ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at "ITIS_PRCDRS.PA_PRJ_IMP", line 1235
The exception is thrown on line 1235:
o_boq_rev_id := v_boq_import_rev_id;
As you can see from the procedure declaration above, v_boq_import_rev_id has type RAW(16) and o_boq_rev_id has type OUT RAW, so why should the assignment on line 1235 fail? What am I doing wrong?
PS: The proc executes fine when I call it in plain PL/SQL.
In OracleParameter the default size is 0 for the parameters that may have size values. (Official reference here.)
That is why you need to modify your method which generates the raw values. Below you can find the modified method:
public DbParameter CreateParameterRaw(string name, object value, int parameterSize)
{
OracleParameter oracleParameter = new OracleParameter();
oracleParameter.ParameterName = name;
oracleParameter.OracleDbType = OracleDbType.Raw;
oracleParameter.Value = value;
oracleParameter.Size = parameterSize; /* THIS IS THE ADDED PARAMETER */
this.Parameters.Add((DbParameter) oracleParameter);
return (DbParameter) oracleParameter;
}
And as a result you can pass the size while calling CreateParameterRaw as you did in your existing code:
var revParam = new byte[16];
/* CHECK THE 16 value in the parameters that are sent to CreateParameterRaw */
dataHandler.CreateParameterRaw("o_boq_rev_id", revParam, 16).Direction = ParameterDirection.Output;
Additional suggestion: In order to keep apples with apples, I would suggest you can take Direction parameter also into the CreateParameterRawmethod. By this way CreateParameterRawbecomes the whole responsible about generating the parameters.
Credits:
Official page: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/odpnt/ParameterCtor5.html#GUID-04BE7E69-A80A-4D28-979A-CDC2516C0F93
A blog that has similar problem: http://devsilos.blogspot.com/2013/01/ora-06502-with-out-parameter-called.html?m=1
Size usage from Microsoft: https://learn.microsoft.com/en-us/dotnet/api/system.data.oracleclient.oracleparameter.size?view=netframework-4.8
This is an interesting problem with a weird solution.
Actually while using the RAW in output parameter you MUST provide some buffer space for it when adding this parameter.
Can you please provide some buffer space for this variable and try something like the following:
byte[] RAWPlaceholder = new byte[16];
cmd.AddParameter(new OracleParameter("o_boq_rev_id",
OracleDbType.Raw,
16,
RAWPlaceholder,
ParameterDirection.Output);
Please share the result of the aforementioned exercise.
Thanks
I have stored procedure in Oracle
CREATE OR REPLACE PROCEDURE procTest (p_param1 varchar2, p_param2 out varchar2)
AS
BEGIN
...
END;
How can I handle out parameters?
_db.Execute("EXEC procTest ('dsds')");
reed about Stored Procedures And Functions Support on http://pocoproject.org/docs-1.5.0/00200-DataUserManual.html
Stored Procedures And Functions Support
Most of the modern database systems support stored procedures and/or functions. Does Poco::Data provide any support there? You bet. While the specifics on what exactly is possible (e.g. the data types passed in and out, automatic or manual data binding, binding direction, etc.) is ultimately database dependent, POCO Data does it's best to provide reasonable access to such functionality through in, out and io binding functions. As their names imply, these functions are performing parameters binding tho pass in or receive from the stored procedures, or both. The code is worth thousand words, so here's an Oracle ODBC example:
session << "CREATE OR REPLACE "
"FUNCTION storedFunction(param1 IN OUT NUMBER, param2 IN OUT NUMBER) RETURN NUMBER IS "
" temp NUMBER := param1; "
" BEGIN param1 := param2; param2 := temp; RETURN(param1+param2); "
" END storedFunction;" , now;
int i = 1, j = 2, result = 0;
session << "{? = call storedFunction(?, ?)}", out(result), io(i), io(j), now; // i = 2, j = 1, result = 3
Stored procedures are allowed to return data sets (a.k.a. cursors):
typedef Tuple<std::string, std::string, std::string, int> Person;
std::vector<Person> people;
int age = 13;
session << "CREATE OR REPLACE "
"FUNCTION storedCursorFunction(ageLimit IN NUMBER) RETURN SYS_REFCURSOR IS "
" ret SYS_REFCURSOR; "
"BEGIN "
" OPEN ret FOR "
" SELECT * FROM Person WHERE Age < ageLimit; "
" RETURN ret; "
"END storedCursorFunction;" , now;
session << "{call storedCursorFunction(?)}", in(age), into(people), now;
The code shown above works with Oracle databases.
I have written a method insert() in which I am trying to use JDBC Batch for inserting half a million records into a MySQL database:
public void insert(int nameListId, String[] names) {
String sql = "INSERT INTO name_list_subscribers (name_list_id, name, date_added)" +
" VALUES (?, ?, NOW())";
Connection conn = null;
PreparedStatement ps = null;
try {
conn = getConnection();
ps = conn.prepareStatement(sql);
for (String s : names ) {
ps.setInt(1, nameListId);
ps.setString(2, s);
ps.addBatch();
}
ps.executeBatch();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
closeDbResources(ps, null, conn);
}
}
But whenever I try to run this method, I get the following error:
java.lang.OutOfMemoryError: Java heap space
com.mysql.jdbc.ServerPreparedStatement$BatchedBindValues.<init>(ServerPreparedStatement.java:72)
com.mysql.jdbc.ServerPreparedStatement.addBatch(ServerPreparedStatement.java:330)
org.apache.commons.dbcp.DelegatingPreparedStatement.addBatch(DelegatingPreparedStatement.java:171)
If I replace ps.addBatch() with ps.executeUpdate() and remove ps.executeBatch(), it works fine, though it takes some time. Please let me know if you know if using Batch is appropriate in this situation, and if it is, then why does it give OurOfMemoryError?
Thanks
addBatch and executeBatch give you the mechanism to perform batch inserts, but you still need to do the batching algorithm yourself.
If you simply pile every statement into the same batch, as you are doing, then you'll run out of memory. You need to execute/clear the batch every n records. The value of n is up to you, JDBC can't make that decision for you. The larger the batch size, the faster things will go, but too large and you'll get memory starvation and things will slow down or fail. It depends how much memory you have.
Start off with a batch size of 1000, for example, and experiment with different values from there.
final int batchSize = 1000;
int count = 0;
for(String s : names ) {
ps.setInt(1, nameListId);
ps.setString(2, s);
ps.addBatch();
if (++count % batchSize == 0) {
ps.executeBatch();
ps.clearBatch(); //not sure if this is necessary
}
}
ps.executeBatch(); // flush the last few records.
It is out of memory because it hold all the transaction in memory and only send it over to the database when you call executeBatch.
If you don't need it to be atomic and would like the get better performance, you can keep a counter and call executeBatch every n number of records.
I have the following query (column log is of type CLOB):
UPDATE table SET log=? where id=?
The query above works fine when using the setAsciiStream method to put a value longer than 4000 characters into the log column.
But instead of replacing the value, I want to append it, hence my query looks like this:
UPDATE table SET log=log||?||chr(10) where id=?
The above query DOES NOT work any more and I get the following error:
java.sql.SQLException: ORA-01461: can bind a LONG value only for insert into a LONG column
It looks to me like you have to use a PL/SQL block to do what you want. The following works for me, assuming there's an entry with id 1:
import oracle.jdbc.OracleDriver;
import java.sql.*;
import java.io.ByteArrayInputStream;
public class JDBCTest {
// How much test data to generate.
public static final int SIZE = 8192;
public static void main(String[] args) throws Exception {
// Generate some test data.
byte[] data = new byte[SIZE];
for (int i = 0; i < SIZE; ++i) {
data[i] = (byte) (64 + (i % 32));
}
ByteArrayInputStream stream = new ByteArrayInputStream(data);
DriverManager.registerDriver(new OracleDriver());
Connection c = DriverManager.getConnection(
"jdbc:oracle:thin:#some_database", "user", "password");
String sql =
"DECLARE\n" +
" l_line CLOB;\n" +
"BEGIN\n" +
" l_line := ?;\n" +
" UPDATE table SET log = log || l_line || CHR(10) WHERE id = ?;\n" +
"END;\n";
PreparedStatement stmt = c.prepareStatement(sql);
stmt.setAsciiStream(1, stream, SIZE);
stmt.setInt(2, 1);
stmt.execute();
stmt.close();
c.commit();
c.close();
}
}
BLOBs are not mutable from SQL (well, besides setting them to NULL), so to append, you would have to download the blob first, concatenate locally, and upload the result again.
The usual solution is to write several records to the database with a common key and a sequence which tells the DB how to order the rows.