## Utilizar Sentencias Prepared

Algunas veces es más conveniente o eficiente utilizar objetos PreparedStatement para enviar sentencias SQL a la base de datos. Este tipo especial de sentencias se deriva de una clase más general, Statement, que ya conocemos.

#### Cuándo utilizar un Objeto PreparedStatement

Si queremos ejecutar muchas veces un objeto Statement, reduciremos el tiempo de ejecución si utilizamos un objeto PreparedStatement, en su lugar.

La caracterísitca principal de un objeto PreparedStatement es que, al contrario que un objeto Statement, se le entrega una sentencia SQL cuando se crea. La ventaja de esto es que en la mayoría de los casos, esta sentencia SQL se enviará al controlador de la base de datos inmediatamente, donde será compilado. Como resultado, el objeto PreparedStatement no sólo contiene una sentencia SQL, sino una sentencia SQL que ha sido precompilada. Esto significa que cuando se ejecuta la PreparedStatement, el controlador de base de datos puede ejecutarla sin tener que compilarla primero.

Aunque los objetos PreparedStatement se pueden utilizar con sentencias SQL sin parámetros, probablemente nosotros utilizaremos más frecuentemente sentencias con parámetros. La ventajA de utilizar sentencias SQL que utilizan parámetros es que podemos utilizar la misma sentencia y suministrar distintos valores cada vez que la ejecutemos. Veremos un ejemplo de esto en las página siguientes.

#### Crear un Objeto PreparedStatement

Al igual que los objetos Statement, creamos un objeto PreparedStatement con un objeto Connection. Utilizando nuestra conexión con abierta en ejemplos anteriores, podríamos escribir lo siguiente para crear un objeto PreparedStatement que tome dos parámetros de entrada:

    PreparedStatement updateSales = con.prepareStatement(
    			"UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ?");

La variable updateSales contiene la sentencia SQL, "UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ?", que también ha sido, en la mayoría de los casos, enviada al controlador de la base de datos, y ha sido precompilado.

#### Suministrar Valores para los Parámetros de un PreparedStatement

Necesitamos suministrar los valores que se utilizarán en los luegares donde están las marcas de interrogación, si hay alguno, antes de ejecutar un objeto PreparedStatement. Podemos hacer esto llamado a uno de los métodos setXXX definidos en la clase PreparedStatement. Si el valor que queremos sustituir por una marca de interrogación es un int de Java, podemos llamar al método setInt. Si el valor que queremos sustituir es un String de Java, podemos llamar al método setString, etc. En general, hay un método setXXX para cada tipo Java.

Utilizando el objeto updateSales del ejemplo anterior, la siguiente línea de código selecciona la primera marca de interrogación para un int de Java, con un valor de 75:

    updateSales.setInt(1, 75);

Cómo podríamos asumir a partir de este ejemplo, el primer argumento de un método setXXX indica la marca de interrogación que queremos seleccionar, y el segundo argumento el valor que queremos ponerle. El siguiente ejemplo selecciona la segunda marca de interrogación con el string "Colombian":

    updateSales.setString(2, "Colombian");

Después de que estos valores hayan sido asignados para sus dos parámetros, la sentencia SQL de updateSales será equivalente a la sentencia SQL que hay en string updateString que utilizando en el ejemplo anterior. Por lo tanto, los dos fragmentos de código siguientes consiguen la misma cosa:

Código 1:

    String updateString = "UPDATE COFFEES SET SALES = 75 " +
    		      "WHERE COF_NAME LIKE 'Colombian'";
    stmt.executeUpdate(updateString);

Código 2:

    PreparedStatement updateSales = con.prepareStatement(
    	"UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
    updateSales.setInt(1, 75);
    updateSales.setString(2, "Colombian");
    updateSales.executeUpdate():

Utilizamos el método executeUpdate para ejecutar ambas sentencias stmt updateSales. Observa, sin embargo, que no se suministran argumentos a executeUpdate cuando se utiliza para ejecutar updateSales. Esto es cierto porque updateSales ya contiene la sentencia SQL a ejecutar.

Mirando esto ejemplos podríamos preguntarnos por qué utilizar un objeto PreparedStatement con parámetros en vez de una simple sentencia, ya que la sentencia simple implica menos pasos. Si actualizáramos la columna SALES sólo una o dos veces, no sería necesario utilizar una sentencia SQL con parámetros. Si por otro lado, tuvieramos que actualizarla frecuentemente, podría ser más fácil utilizar un objeto PreparedStatement, especialmente en situaciones cuando la utilizamos con un bucle while para seleccionar un parámetro a una sucesión de valores. Veremos este ejemplo más adelante en esta sección.

Una vez que a un parámetro se ha asignado un valor, el valor permanece hasta que lo resetee otro valor o se llame al método clearParameters. Utilizando el objeto PreparedStatement: updateSales, el siguiente fragmento de código reutiliza una sentencia prepared después de resetar el valor de uno de sus parámetros, dejando el otro igual:

    updateSales.setInt(1, 100);
    updateSales.setString(2, "French_Roast");
    updateSales.executeUpdate();
    // changes SALES column of French Roast row to 100
    updateSales.setString(2, "Espresso");
    updateSales.executeUpdate();
    // changes SALES column of Espresso row to 100 (the first
    // parameter stayed 100, and the second parameter was reset
    // to "Espresso")

#### Utilizar una Bucle para asignar Valores

Normalmente se codifica más sencillo utilizando un bucle for o while para asignar valores de los parámetros de entrada.

El siguiente fragmento de código demuestra la utilización de un bucle for para asignar los parámetros en un objeto PreparedStatement: updateSales. El array salesForWeek contiene las cantidades vendidas semanalmente. Estas cantidades corresponden con los nombres de los cafés listados en el array coffees, por eso la primera cantidad de salesForWeek (175) se aplica al primer nombre de café de coffees ("Colombian"), la segunda cantidad de salesForWeek (150) se aplica al segundo nombre de café en coffees ("French_Roast"), etc. Este fragmento de código demuestra la actualización de la columna SALES para todos los cafés de la tabla COFFEES

    PreparedStatement updateSales;
    String updateString = "update COFFEES " +
    		      "set SALES = ? where COF_NAME like ?";
    updateSales = con.prepareStatement(updateString);int [] salesForWeek = {175, 150, 60, 155, 90};
    String [] coffees = {"Colombian", "French_Roast", "Espresso",
    		     "Colombian_Decaf", "French_Roast_Decaf"};
    int len = coffees.length;
    for(int i = 0; i < len; i++) {
    		updateSales.setInt(1, salesForWeek[i]);
    		updateSales.setString(2, coffees[i]);
    		updateSales.executeUpdate();
    	}

Cuando el propietario quiera actualizar las ventas de la semana siguiente, puede utilizar el mismo código como una plantilla. Todo lo que tiene que haces es introducir las nuevas cantidades en el orden apropiado en el array salesForWeek. Los nombres de cafés del array coffees permanecen constantes, por eso no necesitan cambiarse. (En una aplicación real, los valores probablemente serían introducidos por el usuario en vez de desde un array inicializado).

#### Valores de retorno del método executeUpdate

Siempre que executeQuery devuelve un objeto ResultSet que contiene los resultados de una petición al controlador de la base datos, el valor devuelto por executeUpdate es un int que indica cuántas líneas de la tabla fueron actualizadas. Por ejemplo, el siguiente código muestra el valor de retorno de executeUpdate asignado a la variable n:

    updateSales.setInt(1, 50);
    updateSales.setString(2, "Espresso");
    int n = updateSales.executeUpdate();
    // n = 1 because one row had a change in it

La tabla COFFEES se ha actualziado poniendo el valor 50 en la columna SALES de la fila correspondiente a Espresso. La actualización afecta sólo a una línea de la tabla, por eso n es igual a 1.

Cuando el método executeUpdate es utilizado para ejecutar una sentecia DDL, como la creación de una tabla, devuelve el int: 0. Consecuentemente, en el siguiente fragmento de código, que ejecuta la sentencia DDL utilizada pra crear la tabla COFFEES, n tendrá el valor 0:

    int n = executeUpdate(createTableCoffees); // n = 0

Observa que cuando el valor devuelto por executeUpdate sea 0, puede significar dos cosas: (1) la sentencia ejecutada no ha actualizado ninguna fila, o (2) la sentencia ejecutada fue una sentencia DDL.

Ozito