How to get *everything* back from a stored procedure using JDBC - java

I occasionally encounter two forms of strange behaviour when using JDBC to work with SQL Server stored procedures:
Issue 1: I run a stored procedure in SQL Server Management Studio (SSMS) and it returns a result set. However, when I try
try (CallableStatement cs = conn.prepareCall("{call dbo.TroublesomeSP}")) {
ResultSet rs = cs.executeQuery();
I get the exception
com.microsoft.sqlserver.jdbc.SQLServerException: The statement did not return a result set.
Issue 2: I run a stored procedure in SSMS and it raises an error, but when I use JDBC to .execute the stored procedure no exception is thrown.
Why do these issues occur and how can I avoid them?

When we execute a stored procedure in JDBC we get back a series of zero or more "results". We can then process those "results" sequentially by calling CallableStatement#getMoreResults(). Each "result" can contain
zero or more rows of data that we can retrieve with a ResultSet object,
an update count for a DML statement (INSERT, UPDATE, DELETE) that we can retrieve with CallableStatement#getUpdateCount(), or
an error that throws an SQLServerException.
For "Issue 1" the problem is often that the stored procedure does not begin with SET NOCOUNT ON; and executes a DML statement before doing a SELECT to produce a result set. The update count for the DML is returned as the first "result", and the data rows are "stuck behind it" until we call getMoreResults.
"Issue 2" is essentially same problem. The stored procedure produces a "result" (usually a SELECT, or possibly an update count) before the error occurs. The error is returned in a subsequent "result" and does not cause an exception until we "retrieve" it using getMoreResults.
In many cases the problem can be avoided by simply adding SET NOCOUNT ON; as the first executable statement in the stored procedure. However, a change to the stored procedure is not always possible and the fact remains that in order to get everything back from the stored procedure we need to keep calling getMoreResults until, as the Javadoc says:
There are no more results when the following is true:
// stmt is a Statement object
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
That sounds simple enough but as usual, "the devil is in the details", as illustrated by the following example. For a SQL Server stored procedure ...
ALTER PROCEDURE dbo.TroublesomeSP AS
BEGIN
-- note: no `SET NOCOUNT ON;`
DECLARE #tbl TABLE (id VARCHAR(3) PRIMARY KEY);
DROP TABLE NonExistent;
INSERT INTO #tbl (id) VALUES ('001');
SELECT id FROM #tbl;
INSERT INTO #tbl (id) VALUES ('001'); -- duplicate key error
SELECT 1/0; -- error _inside_ ResultSet
INSERT INTO #tbl (id) VALUES ('101');
INSERT INTO #tbl (id) VALUES ('201'),('202');
SELECT id FROM #tbl;
END
... the following Java code will return everything ...
try (CallableStatement cs = conn.prepareCall("{call dbo.TroublesomeSP}")) {
boolean resultSetAvailable = false;
int numberOfResultsProcessed = 0;
try {
resultSetAvailable = cs.execute();
} catch (SQLServerException sse) {
System.out.printf("Exception thrown on execute: %s%n%n", sse.getMessage());
numberOfResultsProcessed++;
}
int updateCount = -2; // initialize to impossible(?) value
while (true) {
boolean exceptionOccurred = true;
do {
try {
if (numberOfResultsProcessed > 0) {
resultSetAvailable = cs.getMoreResults();
}
exceptionOccurred = false;
updateCount = cs.getUpdateCount();
} catch (SQLServerException sse) {
System.out.printf("Current result is an exception: %s%n%n", sse.getMessage());
}
numberOfResultsProcessed++;
} while (exceptionOccurred);
if ((!resultSetAvailable) && (updateCount == -1)) {
break; // we're done
}
if (resultSetAvailable) {
System.out.println("Current result is a ResultSet:");
try (ResultSet rs = cs.getResultSet()) {
try {
while (rs.next()) {
System.out.println(rs.getString(1));
}
} catch (SQLServerException sse) {
System.out.printf("Exception while processing ResultSet: %s%n", sse.getMessage());
}
}
} else {
System.out.printf("Current result is an update count: %d %s affected%n",
updateCount,
updateCount == 1 ? "row was" : "rows were");
}
System.out.println();
}
System.out.println("[end of results]");
}
... producing the following console output:
Exception thrown on execute: Cannot drop the table 'NonExistent', because it does not exist or you do not have permission.
Current result is an update count: 1 row was affected
Current result is a ResultSet:
001
Current result is an exception: Violation of PRIMARY KEY constraint 'PK__#314D4EA__3213E83F3335971A'. Cannot insert duplicate key in object 'dbo.#tbl'. The duplicate key value is (001).
Current result is a ResultSet:
Exception while processing ResultSet: Divide by zero error encountered.
Current result is an update count: 1 row was affected
Current result is an update count: 2 rows were affected
Current result is a ResultSet:
001
101
201
202
[end of results]

Related

executeUpdate() returns always 1 with MERGE statement

ExecuteUpdate() is always returning 1. Pls suggest and appreciate any input.
Procedure:
PROCEDURE INSERT_USER_PREFERENCES(owner_id_var varchar2, stripeid_var varchar2, type_var varchar2, metadata_var CLOB) AS
BEGIN
MERGE INTO CXO_USER_PREFERENCES d
USING(SELECT stripeid_var id FROM dual) s
ON (d.stripe_id = s.id)
WHEN NOT MATCHED THEN
INSERT (OWNER_ID, STRIPE_ID, PREF_TYPE, METADATA, CREATED_DATE )
VALUES(owner_id_var, stripeid_var, type_var, metadata_var, CURRENT_TIMESTAMP);
COMMIT;
END INSERT_USER_PREFERENCES;
Java code:
try (CallableStatement stmt = connection.prepareCall(CREATE_USER_PREF_SQL)) {
SQLParameterMapper sqlParamMapper = new SQLParameterMapper(CREATE_USER_PREF_SQL);
sqlParamMapper.setString(stmt, ":ownerId", userName);
sqlParamMapper.setCharacterStream(stmt, ":metadata", reader, metadata.length());
sqlParamMapper.setString(stmt, ":stripeId", stripeId);
sqlParamMapper.setString(stmt, ":type", userPreference.getType());
// invoke the database
int value = stmt.executeUpdate();
return value;
} catch (SQLException e) {
throw e;
}
stmt.executeUpdate() - always returns 1, even though insertion happens only once. Appreciate any inputs on this. Ideally, if there are no insertions or errors, it should return 0 or any exception trace. Pls, suggest.**
We can make the column a primary key. Now when multiple VM's tries to execute, the other one will throw a Unique Integrity exception and in the catch block, we can read the SQLException. Once we read the SQLException, we can also read the sqlstate and we can perform the needed operations based on the sqlstate.
Sample code below
if (e instanceof SQLException) {
String sqlState = ((SQLException) e).getSQLState();
if (sqlState.equalsIgnoreCase("08000") || sqlState.equalsIgnoreCase("08006")) {
logger.error("POD DB is down with the sql state:" + sqlState);
return true;
}
}

JDBC CallableStatement.execute() not throwing exception [duplicate]

I occasionally encounter two forms of strange behaviour when using JDBC to work with SQL Server stored procedures:
Issue 1: I run a stored procedure in SQL Server Management Studio (SSMS) and it returns a result set. However, when I try
try (CallableStatement cs = conn.prepareCall("{call dbo.TroublesomeSP}")) {
ResultSet rs = cs.executeQuery();
I get the exception
com.microsoft.sqlserver.jdbc.SQLServerException: The statement did not return a result set.
Issue 2: I run a stored procedure in SSMS and it raises an error, but when I use JDBC to .execute the stored procedure no exception is thrown.
Why do these issues occur and how can I avoid them?
When we execute a stored procedure in JDBC we get back a series of zero or more "results". We can then process those "results" sequentially by calling CallableStatement#getMoreResults(). Each "result" can contain
zero or more rows of data that we can retrieve with a ResultSet object,
an update count for a DML statement (INSERT, UPDATE, DELETE) that we can retrieve with CallableStatement#getUpdateCount(), or
an error that throws an SQLServerException.
For "Issue 1" the problem is often that the stored procedure does not begin with SET NOCOUNT ON; and executes a DML statement before doing a SELECT to produce a result set. The update count for the DML is returned as the first "result", and the data rows are "stuck behind it" until we call getMoreResults.
"Issue 2" is essentially same problem. The stored procedure produces a "result" (usually a SELECT, or possibly an update count) before the error occurs. The error is returned in a subsequent "result" and does not cause an exception until we "retrieve" it using getMoreResults.
In many cases the problem can be avoided by simply adding SET NOCOUNT ON; as the first executable statement in the stored procedure. However, a change to the stored procedure is not always possible and the fact remains that in order to get everything back from the stored procedure we need to keep calling getMoreResults until, as the Javadoc says:
There are no more results when the following is true:
// stmt is a Statement object
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
That sounds simple enough but as usual, "the devil is in the details", as illustrated by the following example. For a SQL Server stored procedure ...
ALTER PROCEDURE dbo.TroublesomeSP AS
BEGIN
-- note: no `SET NOCOUNT ON;`
DECLARE #tbl TABLE (id VARCHAR(3) PRIMARY KEY);
DROP TABLE NonExistent;
INSERT INTO #tbl (id) VALUES ('001');
SELECT id FROM #tbl;
INSERT INTO #tbl (id) VALUES ('001'); -- duplicate key error
SELECT 1/0; -- error _inside_ ResultSet
INSERT INTO #tbl (id) VALUES ('101');
INSERT INTO #tbl (id) VALUES ('201'),('202');
SELECT id FROM #tbl;
END
... the following Java code will return everything ...
try (CallableStatement cs = conn.prepareCall("{call dbo.TroublesomeSP}")) {
boolean resultSetAvailable = false;
int numberOfResultsProcessed = 0;
try {
resultSetAvailable = cs.execute();
} catch (SQLServerException sse) {
System.out.printf("Exception thrown on execute: %s%n%n", sse.getMessage());
numberOfResultsProcessed++;
}
int updateCount = -2; // initialize to impossible(?) value
while (true) {
boolean exceptionOccurred = true;
do {
try {
if (numberOfResultsProcessed > 0) {
resultSetAvailable = cs.getMoreResults();
}
exceptionOccurred = false;
updateCount = cs.getUpdateCount();
} catch (SQLServerException sse) {
System.out.printf("Current result is an exception: %s%n%n", sse.getMessage());
}
numberOfResultsProcessed++;
} while (exceptionOccurred);
if ((!resultSetAvailable) && (updateCount == -1)) {
break; // we're done
}
if (resultSetAvailable) {
System.out.println("Current result is a ResultSet:");
try (ResultSet rs = cs.getResultSet()) {
try {
while (rs.next()) {
System.out.println(rs.getString(1));
}
} catch (SQLServerException sse) {
System.out.printf("Exception while processing ResultSet: %s%n", sse.getMessage());
}
}
} else {
System.out.printf("Current result is an update count: %d %s affected%n",
updateCount,
updateCount == 1 ? "row was" : "rows were");
}
System.out.println();
}
System.out.println("[end of results]");
}
... producing the following console output:
Exception thrown on execute: Cannot drop the table 'NonExistent', because it does not exist or you do not have permission.
Current result is an update count: 1 row was affected
Current result is a ResultSet:
001
Current result is an exception: Violation of PRIMARY KEY constraint 'PK__#314D4EA__3213E83F3335971A'. Cannot insert duplicate key in object 'dbo.#tbl'. The duplicate key value is (001).
Current result is a ResultSet:
Exception while processing ResultSet: Divide by zero error encountered.
Current result is an update count: 1 row was affected
Current result is an update count: 2 rows were affected
Current result is a ResultSet:
001
101
201
202
[end of results]

Expecting multiple ResultSets, but only get one

I have a stored procedure which returns multiple result sets as follows,
create proc test as
begin
select '1'
select a,b into #temp from TABLE1
select * from #temp
select '2'
select 'Done'
end
And my java call is,
CallableStatement stmt = null;
String procString= "EXEC test";
stmt = conn.prepareCall(procString);
boolean results = stmt.execute();
System.out.println(results);
do {
if(results) {
rs = stmt.getResultSet();
while (rs.next()) {
System.out.println(rs.getString(1) + ", " );
}
rs.close();
}
System.out.println();
results = stmt.getMoreResults();
System.out.println("results - "+results);
} while(results);
So as per the above snippet, the output should have all the 4 selects. But I just get the first select and nothing else. I just removed the second select which does insert into temp table, after which I get all the 4 selects.
Why does this happen?
You are misinterpreting the meaning of the boolean returned by getMoreResults:
There are no more results when the following is true:
// stmt is a Statement object
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
Returns:
true if the next result is a ResultSet object; false if it is an update count or there are no more results
As you haven't got a SET NOCOUNT ON in your stored procedure, the SELECT ... INTO ... will generate an update count. This will be returned after the first select. Only when getMoreResults returns false and getUpdateCount returns -1 can you be sure that there are no more results.
Your current code will exit the do .. while as soon as getMoreResults has returned false. You either need to add SET NOCOUNT ON to your stored procedure, or process multiple results taking into account the update counts.
To correctly process multiple results with update counts you need to do something like:
PreparedStatement pstmt = connection.prepareStatement(...);
// ...
boolean result = pstmt.execute();
while(true)
if (result) {
ResultSet rs = pstmt.getResultSet();
// Do something with resultset ...
} else {
int updateCount = pstmt.getUpdateCount();
if (updateCount == -1) {
// no more results
break;
}
// Do something with update count ...
}
result = pstmt.getMoreResults();
}
I copied the above from my answer to another question, it is similar but not exactly the same. The rest of that answer might provide some more details: Java SQL: Statement.hasResultSet()?

statement.executeUpdate() always returns 1

I have a problem with the JDBC executeUpdate() method. It always returns 1 whether it updates a row or not. As far as I understand the method it should return 0 is no rows are altered.
Here is a sample of my code:
try {
conn = pool.getConnection();
PreparedStatement ps = conn.prepareStatement("{CALL UPDATE_USER (?,?,?)}");
ps.setString(1, field.toString());
ps.setString(2, change);
ps.setString(3, userID);
int updated = ps.executeUpdate();
System.out.println(updated);
if(updated==0){
throw new NoUserException();
}
ps.close();
} catch (SQLException e) {
log.error("An error occurred while creating the connection");
e.printStackTrace();
} finally {
pool.returnConnection(conn);
}
Could this be because I'm using a prepared statement or a stored procedure?
Here is the stored procedure:
create or replace
PROCEDURE UPDATE_USER
(
updateColumn IN user_tab_columns.column_name%type,
changeStr IN VARCHAR2,
unID IN VARCHAR2
)
IS
BEGIN
EXECUTE IMMEDIATE
'UPDATE
users
SET ' || updateColumn || '= :1
WHERE
uniqueID = :2'
USING changeStr, unID;
END;
It can't get info from stored proc execution. If you want to get row count (or anything else) switch PROCEDURE to FUNCTION, add return clause in this function, and change your call to something like ? = CALL ... Test with sql%rowcount inside function to get impacted row count.
You can use Callable statement a procedure or function. And return the no of rows affected as a return parameter

Creating tables in Oracle database by JDBC

I have problem with creating tables by JDBC controller on Oracle database.
When I create table by "creata table...." is ok. Table create and I see that table by SQL Developer. I have method to check if table with that name exist - and it works good.
So when I create table (I try to do commit, too - do not help), table appear in SQL developer but when i check by my method if that table exist i get 'false' (do not exist), when I restart database and run again, my method return 'true'.
I think is a problem with session but I don't now how to get over with that.
enter code here public void prepStatExecuteCreateTable(String name){
String createTable= "Create table "+ name +" (ID NUMBER(*, 0) NOT NULL PRIMARY KEY, CZAS NUMBER(*, 0) NOT NULL, OTWARCIE NUMBER(6, 2) NOT NULL , MAX_KURS NUMBER(6, 2) NOT NULL , MIN_KURS NUMBER(6, 2) NOT NULL , ZAMKNIECIE NUMBER(6, 2) NOT NULL , VOLUMEN NUMBER(*, 0) NOT NULL , FOREIGN KEY(czas) references CZAS(ID))";
PreparedStatement ps=null;
try{
ps = returnConnection().prepareStatement(createTable);
}catch(SQLException e){
System.out.println("Error with preperadStatement(create table): "+ e.getMessage());
}
try{
ps.execute();
returnConnection().commit();
ps.close();
closeConnection();
}
catch(SQLException e){
System.out.println("Error with execute: "+ e.getMessage());
}
}
public boolean ifExistTable(String tablename) throws SQLException{
String sql = "Select TABLE_NAME from user_tables where table_name='"+tablename.toUpperCase()+"' ";
System.out.println(sql);
ResultSet rs =null;
Statement ps = null;
try{
ps = returnConnection().createStatement();
}catch(SQLException e){
System.out.println("Error with preperadSatement(checking): "+ e.getMessage());
}
try{
rs=ps.executeQuery(sql);
while (rs.next()){
System.out.println (rs.getString(1)); // Print col 1
if(rs.getString(1).equals(tablename)){
return true;
}
}
}
catch(SQLException e){
System.out.println("Error with executing checking " +e.getMessage()+ " " + e.getStackTrace());
}
ps.close();
return false;
}
this is how I use in the Main method: (condDB is a object that has above methods)
enter code here String table_name="BBB";
System.out.println(conDB.ifExistTable(table_name));
conDB.prepStatExecuteCreateTable(table_name);
so if I run first, I get message "false" (table do not exist)
and is created (check by SQL developer, table with name 'BBB' appear)
when I run secondly I get message "false" and error message from second method:
Error with execute: ORA-00955: name is already used by an existing object
1) Your ifExistTable function really ought to be using bind variables rather than building the SQL statement using string concatenation if only to avoid SQL injection attacks.
2) Your ifExistTable function is running a query that uses the upper-case value of the tablename that is passed in. But then when you're fetching the data from rs, you're making a case-sensitive comparison with rs.getString(1).equals(tablename). At a minimum, that ought to be equalsIgnoreCase. But there should be no need to do the comparison at all in Java since your SQL statement is already doing it. If the ResultSet has a row, the table exists (or you can write the query as a COUNT(*) and check to see whether the row that is returned has a value of '1').
Perhaps when you created your stored procedure inside the package, you didn't close it with '/'
...
END;
/
so the connection is stucked and the resource is not liberated to be used by other query (which is actually another logical connection). commit will not work as you are performing a DDL sentence which could care less about commiting hehehe

Categories

Resources