Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come recuperare *tutto* da una procedura memorizzata utilizzando JDBC

Quando eseguiamo una procedura memorizzata in JDBC, otteniamo una serie di zero o più "risultati". Possiamo quindi elaborare quei "risultati" in sequenza chiamando CallableStatement#getMoreResults() . Ogni "risultato" può contenere

  • zero o più righe di dati che possiamo recuperare con un ResultSet oggetto,
  • un conteggio degli aggiornamenti per un'istruzione DML (INSERT, UPDATE, DELETE) che possiamo recuperare con CallableStatement#getUpdateCount() , o
  • un errore che genera un'eccezione SQLServerException.

Per "Problema 1" il problema è spesso che la procedura memorizzata non inizia con SET NOCOUNT ON; ed esegue un'istruzione DML prima di eseguire un SELECT per produrre un set di risultati. Il conteggio degli aggiornamenti per il DML viene restituito come primo "risultato" e le righe di dati sono "bloccate dietro di esso" finché non chiamiamo getMoreResults .

"Problema 2" è essenzialmente lo stesso problema. La procedura memorizzata produce un "risultato" (in genere un SELECT o eventualmente un conteggio degli aggiornamenti) prima che si verifichi l'errore. L'errore viene restituito in un successivo "risultato" e non causa un'eccezione finché non lo "recupero" utilizzando getMoreResults .

In molti casi il problema può essere evitato semplicemente aggiungendo SET NOCOUNT ON; come prima istruzione eseguibile nella procedura memorizzata. Tuttavia, una modifica alla stored procedure non è sempre possibile e resta il fatto che per ottenere tutto dalla procedura memorizzata dobbiamo continuare a chiamare getMoreResults fino a quando, come dice il Javadoc:

There are no more results when the following is true: 

     // stmt is a Statement object
     ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))

Sembra abbastanza semplice ma, come al solito, "il diavolo è nei dettagli", come illustrato dal seguente esempio. Per una stored procedure di SQL Server ...

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

... il seguente codice Java restituirà tutto ...

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]");
}

... producendo il seguente output della console:

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 '[email protected]'. 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]