PL-SQL Oracle desde Java
Martes, 20 de Diciembre de 2005Si necesitas utilizar procedimientos almacenados con Oracle desde Java y te aburre o, simplemente, no te apetece leerte la documentación de Oracle (o no te parece clara), este este tu artículo.
Yo sabía SQL, pero nunca había hecho procedimientos almacenados en Oracle. Tras un pequeño, pero intenso, periodo de aprendizaje, fui capaz de renuir la información necesaria para realizar un proyecto J2EE sin utilizar ni un solo SQL en el código Java, todo a base de procedimientos almacenados (era un requerimiento del cliente). Bueno, y para no aburriros, iré directamente al grano. Os voy a resumir las técnicas que, a mi entender, son básicas pero suficientes para utilizar PL-SQL de Oracle desde Java.
Utilicemos la técnica de “un ejemplo vale más que mil palabras” que tanto nos gusta a los programadores más inquietos. En vez de extenderme en explicar en detalle cual es el formato y la estructura completa de un PL-SQL, (lo cual me llevaria muchas lineas y tiempo, además de que para eso ya hay tutorial o la propia documentación de Oracle), simplemente pondré un PL-SQL elemental, pero completito, y explicaré en detalle cada una de sus partes. Listo para cut&paste y search&replace.
Supongamos que tenemos una tabla llamada “tabla” con un solo campo llamado “campo” (original eh?)
CREATE OR REPLACE PACKAGE nombrePaquete IS TYPE vCursor IS REF CURSOR; PROCEDURE procedimiento ( datoABuscar IN tabla.campo%TYPE, otroDato IN tabla.campo%TYPE, parametroTres OUT NUMBER, vUserCursor OUT vCursor ); END nombrePaquete; / CREATE OR REPLACE PACKAGE BODY nombrePaquete IS PROCEDURE procedimiento ( datoABuscar IN tabla.campo%TYPE, otroDato IN tabla.campo%TYPE, parametroTres OUT NUMBER, vUserCursor OUT vCursor ) IS vTotal NUMBER; BEGIN SELECT COUNT(*) INTO vTotal FROM tabla WHERE campo = datoABuscar; IF vTotal = 0 THEN INSERT INTO tabla (campo) VALUES (otroDato); ELSE UPDATE tabla SET campo = otroDato WHERE campo = datoABuscar; END IF; SELECT COUNT(*) INTO parametroTres FROM tabla WHERE campo = datoABuscar; OPEN vUserCursor FOR SELECT campo FROM tabla WHERE campo = otroDato; END procedimiento; END nombrePaquete; /
Lo que hace el PL-SQL es poco útil, casi absurdo. Su única funcionalidad es la de servir como ejemplo durante este artículo.
El PL-SQL debe ir en un fichero de texto independiente. De esta manera podemos modificarlo con nuestro editor favorito y volverlo a ejecutar depués cuando queramos.
Por esta misma razón, el script empieza con CREATE OR REPLACE, de manera que aunque se ejecute por primera vez (cuando los procedimientos no existen) o por segunda o tercera vez (cuando ya están creados) siempre funcionará.
El Script se compone de dos partes muy parecidas: la primera, que solo define las cabeceras de los procedimientos y la segunda, en la que se define el cuerpo de los mismos.
La diferencia entre una y otra es que la primera empieza con
CREATE OR REPLACE PACKAGE nombrePaquete IS
y la segunda con
CREATE OR REPLACE PACKAGE BODY nombrePaquete IS
Podríamos haberlas creado en dos archivos distintos y ejecutarlas una después de otra, pero me gusta más hacerlo todo junto. Esto es importante: dado que lo hacemos así (todo junto en mismo archivo), es necesario separar las dos partes por una barra invertida /. Podeis ver la barra de separación:
END nombrePaquete; / CREATE OR REPLACE PACKAGE BODY nombrePaquete IS
Esa barra separa las dos partes. Si no la ponéis no funcionará.
Bueno, cada parte define el nombre del paquete en “nombrePaquete”. Ni que decir que en las dos partes debe ser el mismo nombre y que deben de acabar con:
END nombrePaquete
Cuando creemos procedimientos en este paquete, para llamarlos tendremos que hacerlo con el nombre nombrePaquete.procedimiento
Volvamos a la parte de la declaración (la primera) de los procedimientos. Aquí se declaran las cabeceras de los mismos, es decir, se pone el procedimiento entero, tal cual, pero sin el cuerpo. Una recomendación es que primero se haga el cuerpo (en la segunda parte) y luego se copie y pegue para hacer la cabecera, así no nos equivocaremos en el nombre de los parámetros etc.
Además, tenemos también la declaración de nuevos tipos. Por ahora solo nos hace falta un CURSOR, el cual nos servirá para retornar conjuntos de datos a Java (por medio de un ResultSet). Esto se hace así:
TYPE vCursor IS REF CURSOR;
El nombre vCursor no es obligatorio, podeis usar el que queráis.
Para definir un procedimiento, debemos especificar el nombre del mismo y los parámetros que acepta. Los parámetros pueden ser de entrada (IN), de salida (OUT) o de entrada y salida (INOUT). Yo prefiero utilizar los IN por un lado y los OUT por otro, por lo que no uso los INOUT. Vamos a ver nuestro ejemplo:
PROCEDURE procedimiento ( datoABuscar IN tabla.campo%TYPE, otroDato IN tabla.campo%TYPE, parametroTres OUT NUMBER, vUserCursor OUT vCursor );
Aquí se definen 4 parámetros, 2 de entrada y 2 de salida. Al lado de cada parámetro hay que ponerle el tipo al que pertenece. Esto podemos hacerlo de dos formas: especificando directamente el tipo (NUMBER o nuestro cursor, vCursor, que es el tipo que hemos declarado justo antes como CURSOR), o la referencia al tipo de un campo de una tabla.
Así, en nuestro ejemplo, datoABuscar y otroDato son del tipo del campo “campo” de la tabla “tabla”. Es una manera de ahorrarnos pensar o buscar el tipo de una variable o campo, cuando este va a ser utilizado siempre con un campo (en un INSERT o UPDATE por ejemplo).
En este otro ejemplo veréis mucho mas claro la utilidad:
PROCEDURE updateUser (
vId IN USER.ID%TYPE,
vNick IN USER.NICK%TYPE,
vEmail IN USER.EMAIL%TYPE,
vPwd IN USER.PASSWORD%TYPE,
vBirthday IN USER.BIRTHDAY%TYPE
) IS
BEGIN
UPDATE USER
SET
NICK = vUserNick,
EMAIL = vUserEmail,
PASSWORD = vUserPwd,
BIRTHDAY = vBirthday
WHERE ID = vId;
END updateUser;
Mucho más comodo que poner uno a uno los tipo de cada variable. Usando los tipos de los campos en los que serán utilizados nos despreocupamos.
Ya hemos definido nuestra cabecera. En resumen, se compone de la definición de un tipo CURSOR y un procedimiento con parámetros. Ahora definamos el cuerpo de los procedimientos en la segunda parte.
En nuestro ejemplo utilizamos, además, una variable temporal llamada vTotal, de tipo NUMBER. Esta variable se declara entre el IS y el BEGIN de nuestro procedimiento. Es opcional, es decir, podemos dejar esta parte del procedimiento vacía.
El procedimiento usa la sentencia SELECT COUNT(*) para contar registros. El resultado se puede guardar en nuestra variable temporal vTotal, o se puede guardar en el parámetro de salida parametroTres, el cual podremos leer desde Java. Para esto usamos la cláusula INTO variable:
SELECT COUNT(*) INTO vTotal FROM tabla WHERE campo = datoABuscar; ... SELECT COUNT(*) INTO parametroTres FROM tabla WHERE campo = datoABuscar;
Si queremos hacer una consulta y devolver sus resultados en un ResultSet, tenemos que utilizar el parámetro de salida de tipo CURSOR como medio de transporte. Esto se hace añadiendo OPEN vUserCursor FOR antes de la consulta, tal y como sigue:
OPEN vUserCursor FOR SELECT campo FROM tabla WHERE campo = otroDato;
Ya sabemos devolver valores simples y conjuntos de resultados en cursores.
El resto del procedimiento como las condiciones, el INSERT y el UPDATE se explican por si mismos.
Vamos directamente a utilizar este procedimiento desde Java.
Para llamar a nuestro procedimiento lo haremos así:
String sql ="{call nombrePaquete.procedimiento (?,?,?,?)}";
CallableStatement cs = con.prepareCall(sql);
Como esto es un poco rollo hacerlo así, sobre todo si tenemos 200 procedimientos distintos, y tenemos todas las llamadas desperdigadas por nuestro fuentes, primero crearemos un método Java que nos genere las cadenas SQL para llamara a estos procedimientos. (Y de paso, un método muy comodo para cerrar conexiones, resultset y todo)
public class SQLTools {
public static String buildProcedureCall(String packageName, String procedureName, int paramCount) {
StringBuffer sb = new StringBuffer("{call "+packageName+"."+procedureName+"(");
for (int n = 1; n <= paramCount; n++) {
sb.append("?");
if (n < paramCount) sb.append(",");
}
return sb.append(")}").toString();
}
public static void close(ResultSet rs, Statement s, Connection c) {
try { if (rs != null) rs.close(); } catch (Exception e) {}
try { if (s != null) s.close(); } catch (Exception e) {}
try { if (c != null) c.close(); } catch (Exception e) {}
}
}
Y cuando queramos crear procedimientos, lo haremos dentro de una clase de constantes así:
public abstract class Constants {
public static String PROCEDIMIENTO1 =
SQLTools.buildProcedureCall("nombrePaquete", "procedimiento", 4);
public static String PROCEDIMIENTO2 =
SQLTools.buildProcedureCall("nombrePaquete", "otroProcedimiento", 6);
public static String PROCEDIMIENTO3 =
SQLTools.buildProcedureCall("nombrePaquete", "yOtroMasProcedimiento", 2);
}
Más comodo y sencillo, ?no?
Ahora vamos a utilizar nuestro ejemplo desde Java (por fín!):
import oracle.jdbc.OracleTypes;
public class EjemploDao {
public int ejemplo(int datoABuscar, int otroDato) {
Connection con = null;
ResultSet rs = null;
CallableStatement cs = null;
int resultado = -1;
try {
con = dataSource.getConnection(); // Cambiar por tu implementación favorita
cs = con.prepareCall(Constants.PROCEDIMIENTO1);
int pos = 0;
// Cargamos los parametros de entrada IN
cs.setInt(++pos, datoABuscar);
cs.setInt(++pos, otroDato);
// Registramos los parametro de salida OUT
cs.registerOutParameter(++pos, java.sql.Types.INTEGER);
cs.registerOutParameter(++pos, OracleTypes.CURSOR);
// Ejecutamos
cs.execute();
// Cosechamos los parametros de salida OUT
resultado = cs.getInt(3); // Nuestro number
rs = (ResultSet) cs.getObject(4); // Nuestro cursor, convertido en ResultSet
while (rs.next()) {
// Aqui hacemos lo que queramos...
}
} catch (Exception e) {
e.printStackTrace();
} finally {
SQLTools.close(rs, cs, con);
}
return resultado;
}
}
Nuestro ejemplo absurdo ya tiene su codigo Java correspondiente que lo ejecuta. El código se explica por si mismo (espero), pero resumiremos los pasos utilizados:
- Crear una conexion y un CallableStatement. Se utiliza nuestra clase de constantes Constants, la cual utiliza SQLTools.buildProcedureCall para generar las cadenas SQL de llamada.
- Cargamos los parámetros de entrada. Fijaos el truco de utilizar un contador (pos): lo hace más cómodo que llevar la cuenta a mano, ?verdad? Sobre todo si luego tienes que insertar un parámetro más entre medias de otro, así no tienes que alterar el orden de los numeros.
- Registramos los parámetros de salida. En este caso tenemos un INT y un CURSOR, los cuales se mapean con sus clases correspondientes.
- Ejecutamos el procedimiento y recogemos los parámetros, utilizando el método apropiado y su casting correspondiente.
- Operamos el ResultSet, retornamos el valor del parametro OUT parametroTres y cerramos todos los objetos en el finally.
Bueno, espero que este artículo tan “particular” os haya ayudado a empezar a utilizar Oracle PL-SQL en Java de la manera más rápida posible.
Pese a que no es un verdadero tutorial ni explica realmente nada, me baso en que cualquier programador intrépido puede coger el código aquí presentado, modificarlo y aprovecharlo en su proyecto en mucho menos tiempo que si se lee la documentación de Oracle y Java y tiene que picarse todo a mano.
De todas formas, espero que esto sea solo una especie de “QuickStart Guide”. Con PL-SQL se pueden hacer muchísimas cosas más y muy utiles. Esto es solo el principio, desde aquí os animo a seguir investigando y leyendo documentación. No teneis excusa: la base ya la tenéis hecha!
Si alguien quiere más ejemplos o algo le parece poco claro, puede escribir un comentario. Saludos!
PD.: Si has llegado hasta aquí, significa que te ha interesado. Como regalo, aquí tienes un poco de documentación
Actualización: para crear una conexión con la base de datos, ver el post Crear una conexión JDBC
















Enero 14th, 2006 7:19 pm
es algo nuevo apara mi y quisiciera conocer mas de este tema, agradecere a los que me brinden mas informacion acerca de esto
Febrero 3rd, 2006 9:50 pm
Gracias por entregar tus conociminetos.
?Existe la forma de hacer lo contrario, osea entregar un ResulSet o un Objeto desde Java a un cursor en ORACLE?.
Saludos…
Febrero 4th, 2006 11:25 am
Pues la verdad es que no lo se. ?Para que necesitas eso?
Puedes pasar parametros a los PL-SQL, pero como datos de Oracle (string como varchar, integers, etc) pero un objeto como tal, creo que no.
De todas formas, si tienes un resultset, es porque lo has sacado de la bbdd, ?para que lo quieres volver a meter?
Febrero 22nd, 2006 2:43 pm
Me parece excelente el ejemplo que expone vilches, pero sería bueno que alguien muestre como hacer lo contrario, por ejemplo desde java consulto un web-service y el resultado lo quiero ingresar a DB mediante bulk loader, pero ?como mapeo la data a pl/sql?.
Marzo 27th, 2006 3:40 pm
EXCELENTE!!!!COMO DICES EN TU ARTICULO, UNA ESTUPENDA QuickStart Guide PARA LOS QUE NO SABEMOS MUCHO DEL TEMA Y QUEREMOS INICIARNOS…..GRACIAS VILCHES….
Abril 4th, 2006 9:21 pm
Excelente el ejemplo, me gustaria saber como podemos porbar el llamado al stored procedure desde pl/sql command, para luego si pasar a llamarlo desde java, la idea es hacer la depuracion necesaria y pruebas desde plsql
Gracias
Abril 6th, 2006 6:24 am
ALguien sabe como se puede manejar el ciframiento o encripcion de la cadena de conexion para que no quede quemada en el codigo java.
Abril 6th, 2006 10:16 am
Va a ser mas sencillo que la guardes en un fichero externo (en un properties) y la encriptes, y desde el codigo java leas este properties y luego la desencriptes.
utiliza alguna libreria de encriptacion sencilla. Si tienes dudas de como hacerlo te explico como hacerlo.
Abril 18th, 2006 4:15 pm
Muchas gracias, justo lo que necesitaba, y además en castellaño, no me lo puedo creer!!!
Te voy a añadir a Favoritos :-)
Solo una cosa, creo que el enlace a documentación esta roto.
Abril 19th, 2006 3:15 am
Si me haces el favor y me ayudas a preparar una presentación en Power Point del tema Java Stored Procedures que no entiendo mucho y es para presentarlo en una clase que tengo
Abril 19th, 2006 3:17 am
Si me haces el favor y me ayudas a preparar una presentación en Power Point del tema Java Stored Procedures que no entiendo mucho y es para presentarlo en una clase que tengo
Abril 20th, 2006 10:37 am
Buenos Dias
Tengo netBeans 4.1 y Oracle 10 g y cuando pongo lo de import oracle.jdbc.*; me dice que el paquete no existe , ?Por que?
Agradeceria su respuesta
Un saludo
Abril 20th, 2006 10:58 am
Tienes que añadir en tu NetBeans la libreria con el driver JDBC de Oracle a tu proyecto.
Es decir, NetBeans debe tener (digo debe porque no lo conozco) algun sitio en el proyecto donde se indican que librerias utilizas (los jar). Pues ahi es donde debes añadir los driver JDBC de oracle (es un jar tambien).
Mayo 3rd, 2006 12:37 am
Para utilizar la oracla.jdbc.*, puedes buscar en el directorio ORA_HOME/jdbc/lib/classes.jar eso, es todo.
Pregunta? Alguien sabe la direccion del sitio de Oracle donde puedo, hallar, la documentacion de Java para el Oracle10g?
Mayo 26th, 2006 8:23 pm
Vilches me parecio excelente tu ejemplode iniciación para PL-SQL Oracle desde Java. Felicitaciones.
Fue de grana yuda para mi …. Saludos
Junio 2nd, 2006 9:02 pm
Saludos, Me parece bien tu guia, porque es un resumen de algo que se encuentra bastante disperso. LLegue a tu pagina porque quiero saber si existe una manera de invocar un procedimiento almacenado desde java, sin utilizar el nombre Oracle mas de una vez, en la carga del driver. Lo que quiero decir, es que no quiero utilizar
cs.registerOutParameter(++pos, OracleTypes.CURSOR);
En otro DBMS puedo retornar el resultset de inmediato, porque Oracle debe ser diferente???
Junio 22nd, 2006 3:30 pm
Es un ejemplo bien ilustrativo, y de admirar que mucha gente no comparte sus conocimientos, gracias
Junio 30th, 2006 2:26 pm
Excelente ejemplo, muy práctico quisiera saber si tienes un ejemplo de como subcribirse a un web-services utilizando plsql.
Julio 4th, 2006 3:55 pm
Me ha parecido un artículo muy interesante. Gracias.
Julio 17th, 2006 10:05 am
Me parece muy útil e interesante. Podrías indicarme cual sería la librería para realizar lo mismo con SQL SERVER y en vez de PL/SQL con T-SQL??
Noviembre 11th, 2006 3:32 pm
me parece muy interesante pero he creado todo en oracel u java y aun no puedo tener la conexion prodrias mandarme un poco de documentacion mas ampli
Noviembre 22nd, 2006 1:04 pm
Hola!!!!
Estoy trabajando en un proyecto con PL/SQL y con jsp´s, pero a la hora de hacer:
con = dataSource.getConnection(); ¿Cómo hago el dataSource?
muchas gracias al q responda o me mande un mail explicandome.
Marzo 5th, 2007 10:59 pm
Saludos me gustaria saber sobre Java Stored Procedures como trabajar para poder pasarle objeto de una aplicacion
por favor agradesco tu colaboracion
gracias de antemano
Marzo 5th, 2007 11:00 pm
Saludos me gustaria saber sobre Java Stored Procedures como trabajar para poder pasarle objeto de una aplicacion
Marzo 7th, 2007 8:01 pm
Hola he estado trabajando con esta forma de programar, excepto que mando a llamar al procedimiento desde un EJB, peor tengo un problemita; el procedimiento es algo pesado tarda como 5 o mas minutos en ejecutarse, hace una especie de respaldo de varias tablas para fomar una tabla como una especie de informacion consolidada, tengo el problema que cuando envio el submit de la pagina jsp, despues de un lapso de tiempo en que mando a ejecutar el procedimiento este se vuelve a enviar, como si despues de un lapso de tiempo se volviera a enviar la peticion. No se que pueda ser, agradeceria la ayuda.
Gracias.
Saludos.
Octubre 4th, 2007 3:30 am
Hola querido amig@ acabo de leer tu articulo sobre “PL-SQL Oracle desde Java”, te cuento q he construido un paquete plsql que hace llamadas a otros paquetes (estos vienen por defecto en la base de datos y provee el funcionamiento de mi paquete).
Lo llamo desde sql*Plus y es correcto …
pero al llamar al mismo procedimento desde Java … me retorna resultados vacios …
Probando me di cuenta q lo q no ejecuta desde java es la llamada al metodo del otro paquete ( al q viene por defecto en la BD)….
Asi que dedusco q es problema de privilegios.. sera esto posible???…..podrias ayudarme??
Atte. Dabeyva Mejia T.
Santa Cruz — Bolivia
Noviembre 19th, 2007 2:51 am
Hola Alberto Vilches. Realmente me siento muy contento por haber encontrado tu página. Con esto podré resolver el saturamiento de la red con selects muy grandes y administrar en la base misma la cantidad de registros a retornar, de tal forma a no saturar la red y regir los datos a llevar a la aplicacion Web Java.