Omet navegació

6.3 - Tractament d'errors en aplicacions JDBC

L’execució de sentències SQL està sotmesa a molts de factors que poden provocar algun error. Pot passar que la connexió falle, que el controlador no siga l’adequat, que les sentències tinguen errades, que el SGBD no suporte la sentència, i un llarg etcètera de possibilitats.

Nota
Podeu trobar informació referida als codis de SQLSTATE en la wikipedia, en aquest enllaç. El codi SQLSTATE està format per cinc caràcters. Els dos primers indiquen la tipologia de l’error i els tres últims el concreten. 

Els errors SQL es troben molt ben definits a l’especificació estàndard, la qual descriu el valor de la variable anomenada SQLSTATE, que identifica l’estat d’una sentència SQL immediatament després de la seua execució. Quan JDBC detecta que després d’una execució el valor d’aquesta variable es correspon a un error, dispara una excepció de tipus SQLException la qual, a més de contenir un missatge clarificador, incorpora el valor del SQLSATE. Podem recuperar aquest valor amb el mètode getSQLState().

L’ús de try-catch ens permetrà capturar específicament excepcions SQLException o derivades. Una vegada capturades, utilitzarem el codi SQLSTATE per decidir com cal actuar.

Un altre mètode molt útil és getMessage() que torna una cadena amb l'error produït. Pot servir perfectament per a la resta d'errors no tractats, ja que amb aquest missatge sempre donarem una pista, encara que no personalitzada com abans. 

Imaginem, per exemple, que en intentar connectar amb un SGBD capturem una excepció SQL amb el valor SQLState igual a 28000. Si consulteu aquest codi a la pàgina que us indiquem en la nota de dalt veureu que el valor 28000 correspon a un error en l’autenticació. En canvi, si el codi rebut haguera estat 08001 significaria que JDBC està trobant problemes de xarxa a l’hora de connectar, ja siguen deguts a una desconnexió física, o simplement a un host o adreça IP desconegut.

Nota
PostgreSQL defineix millor l'error d'autenticació. En el cas de PostgreSQL és el 28P01. Per tant hauríem de substituir per aquest valor en el programa posterior si volem connectar a ell. La taula d'errors de PostgreSQL la podeu trobar en aquest enllaç.

No cal informar detalladament l’usuari de tots i cadascun dels possibles errors, però sí que cal decidir quins errors requeriran un tractament específic i quins no. Segurament no seria mala idea, si detectem un SQLState de valor 08001, aconsellar l’usuari que abans de trucar al servei tècnic revise les connexions de xarxa o s’assegure que el SGBD es troba en marxa.

D’altra banda, la detecció precisa del SQLState ens pot també permetre realitzar accions per reconduir l’error. Imaginem, per exemple, que per raons de seguretat l’administrador del SGBD va canviant de contrasenya. L’administrador tria una contrasenya a l’atzar d’entre un conjunt de tres o quatre prefixades. Per tal de no haver d’estar contínuament configurant la nostra aplicació cada vegada que canvie la contrasenya, podem implementar una utilitat que accepte un conjunt de tres o quatre contrasenyes de manera que puga anar provant d’una en una quan reba un error d’autenticació.

Per a la resta d'errors, podem avisar a l'usuari de l'error que s'ha produït, o podem utilitzar una altra tècnica, que és utilitzar enregistradors. Els enregistradors (loggers) van guardant automàticament en un fitxer les coses que van succeint.

Vegem un possible exemple on posem en pràctica totes les consideracions que acabem de comentar. Està fet sobre MySQL, ja que com hem comentat abans, PostgreSQL utilitza ara un altre codi d'error per a la contrasenya invàlida. Tindrem 3 contrasenyes possibles per a la connexió. Si qualsevol de les 3 és bona, es connectarà. Sinó indicarà que hi ha un error en la contrasenya.

Copieu el següent codi en un fitxer Kotlin anomenat Exemple_4_51_TractamentErrors.kt :

package exemples

import java.sql.Connection
import java.sql.DriverManager
import java.sql.SQLException

fun main(args: Array<String>) {

    var connectat = false
    var con: Connection? = null
    println("tractamentErrorConnexio()")

    try {

        val url = "jdbc:postgresql://89.36.214.106:5432/geo_ad"

        val usuari = "geo_ad"
        val contrasenyes = arrayOf("geo0", "geo1", "geo_ad")

        for (i in 0 until contrasenyes.size) {
            try {
                con = DriverManager.getConnection(url, usuari, contrasenyes[i])
                connectat = true
                break
            } catch (ex: SQLException) {
                if (!ex.getSQLState().equals("28P01")) {
                    // NO és un error d'autenticació
                    throw ex
                }
            }
        }
        if (connectat)
            println("Connexió efectuada correctament")
        else
            println("Error en la contrasenya")
    } catch (ex: SQLException) {
        if (ex.getSQLState().equals("08001")) {
            println(
                "S'ha detectat un problema de connexió. Reviseu els cables de xarxa i assegureu-vos que el SGBD està operatiu."
                        + " Si continua sense connectar, aviseu el servei tècnic"
            )

        } else {
            println(
                "S'ha produït un error inesperat. Truqueu al servei tècnic indicant el següent codi d'error SQL:"
                        + ex.getSQLState()
            )
        }
    } catch (ex: ClassNotFoundException) {
        println("No s'ha trobat el controlador JDBC (" + ex.message + "). Truqueu al servei tècnic")
    } finally {
        try {
            if (con != null && !con.isClosed()) {
                con.close()
            }
        } catch (ex: SQLException) {
            throw ex
        }
    }
}