martes, 12 de junio de 2012

Migrando a JEE 6 / EJB 3.1 (I)

“I’ve finally learned what ‘upward compatible’ means. It means we get to keep all our old mistakes.”
Dennie van Tassel

La especificación JEE 6 junto con la enorme lista de nuevas tecnologías que comporta (EJB 3.1, JAX-RS 1.1, JAX-WS 2.2, Java Servlet 3.0, etc...) está disponible desde diciembre de 2009. A pesar de haber pasado 2 años y medio, la verdad es que apenas he podido ponerla en funcionamiento en apenas un par de nuevos proyectos.
De esta nueva especificación destaca especialmente la especificación EJB 3.1 con muchas, potentes y útiles novedades. Sin embargo, en la adopción de esta especificación me he encontrado con algunos aspectos ásperos, incompatibilidades y diversos problemas indocumentados. Este artículo (y alguno que le sucederá) intentará ahorrar al lector interesado, al menos, el tiempo que yo he perdido intentando hacer funcionar cosas que teóricamente (en base a la documentación) deberían funcionar.

1. Incompatibilidad de la notación @Webservice con la vista sin interfaz (no-interface view)

Supongamos que tienes un EJB 3.0 publicado como Webservice, así:

 
/**
 * Stateless Session EJB 3.0
 * @author szarza
 */

@Stateless(name="CacheService")
@TransactionManagement(TransactionManagementType.BEAN)
@WebService(serviceName="CacheService",name="CacheService")
public class CacheServiceBean implements CacheServiceLocal {

 

Si vas a migrar a EJB 3.1, puedes tener la tentación de eliminar la interfaz local y usar la vista sin interfaz para simplificar tu proyecto... pero, lamentablemente, al eliminar la interfaz local, en Glassfish 3.1 me encuentro con un desagradable mensaje en despliegue:

[#|2012-06-11T15:43:38.754+0200|SEVERE|glassfish3.1.2|javax.enterprise.system.core.com.sun.enterprise.v3.server|_ThreadID=108;_ThreadName=Thread-2;|Cannot resolve reference Local ejb-ref name=cestel.services.cache.timer.CacheUpdaterTimedBean/cacheService,Local 3.x interface =cestel.services.cache.facade.CacheServiceLocal,ejb-link=null,lookup=,mappedName=,jndi-name=,refType=Session
java.lang.RuntimeException: Cannot resolve reference Local ejb-ref name=cestel.services.cache.timer.CacheUpdaterTimedBean/cacheService,Local 3.x interface =cestel.services.cache.facade.CacheServiceLocal,ejb-link=null,lookup=,mappedName=,jndi-name=,refType=Session...

En definitiva: simplemente no funciona. La notación @Webservice es incompatible con la vista sin interfaz. Puede parecer más o menos lógico... pero no lo he visto en la documentación por ninguna parte.

2. Incompatibilidad de la notación @Path y @Produces en un módulo ejb

De la misma forma que se puede "publicar" de manera sencilla un EJB como un SOAP webservice JAX-WS simplemente añadiendo la notación @Webservice, podría ser igualmente sencillo publicarlo como un webservice RESTful (JAX-RS) añadiendo la notación @Path  y @Produces... pero no. Al menos en Glassfish 3.1.2 no es posible publicar un webservice REST en un módulo EJB. Esto me ha parecido un verdadero engorro, ya que nos obliga a realizar un módulo war sólo para esto.

En definitiva, si necesitáis un servicio REST, debes hacer un módulo war.

El próximo capítulo lo dedicaré a la adaptación a la nueva definición de JNDI portable y sus sorpresas inexplicables (por poco documentadas).


Referencias y más información:

viernes, 4 de mayo de 2012

Analizar y procesar los argumentos de línea de comandos en Java

(Parsing and processing command line arguments in Java)


“19 Jan 2038 at 3:14:07 AM”
(End of the word according to Unix–2^32 seconds after January 1, 1970)

Los límites tecnológicos son muy provisionales. Las cosas que aparentemente durarán mucho al final son más efímeras de lo que parece. El año pasado se agotaron oficialmente las direcciones IPv4 y me preguntaba, a propósito de la "cita" de hoy si, en el año 2038, aún existirán procesadores de 32 bits. El tema de este articulo, sin embargo, es una paradoja al inexorable paso del tiempo: seguimos necesitando realizar aplicaciones de consola que puedan invocarse a través de la línea de comandos.

En java, podemos procesar una línea de comandos sencilla a partir del argumento del método main. Pero, si necesitamos procesar una línea de comandos más compleja, podemos enfrentarnos a un arduo trabajo. Nuestra aplicación puede necesitar características de procesado más complejas como:
  • Argumentos con opciones
  • Análisis sintáctico (tipos de dato, restricciones, etc)
  • Valores por defecto en caso de omisión
  • Control de opciones obligatorias o requeridas
  • Generación de opción de ayuda
  • etc...

Por ejemplo, supongamos las siguientes opciones:

Option (* = required)                        Description
---------------------                        -----------
-?, -h                                       show help
-c <integer: count="">                       (default: 1)                           
--classpath, --cp <file: path1:="" path2:...="">                                                                   
* -d <mm dd="" yy="">                        some date
--output-file [File: file]
-q [Double: quantity]
-v, --chatty, --talkative                    be more verbose

Procesar las opciones mostradas anteriormente por nuestra cuenta sería reinventar la rueda. Para ayudarnos en esta tarea existen múltiples librerías que nos permiten realizar un procesado de opciones de argumentos muy complejo en unas pocas líneas de código, de las que expondré una recopilación al final. En este artículo comentaré la que he utilizado: JOpt Simple.


JOpt Simple
Es la librería usada en OpenJDK. JOpt Simple permite realizar análisis de argumentos compatibles con la sintaxis POSIX getopt() y getopt_long(), manteniendo toda la simplicidad posible. Como otras librerías, tiene opciones muy útiles:
  • generación automática del texto (formateado) de la opción de ayuda
  • control de parámetros obligatorios en argumentos
  • control de tipos de parámetros
  • control de opciones de argumentos
  • valores por defecto
  • etc...

A continuación, un ejemplo de uso:

 private static final String IMPORTED = "imported";
 private static final String A_H = "h";
 private static final String A_P = "p";
 private static final String A_U = "u";
 private static final String A_URL = "url";
 private static final String A_E = "e";
 private static final String A_I = "i";
 private static final String A_Q = "q";
 private static final String A_V = "v";
 private static final String A_GC = "gc";
 private static final String A_DR = "dry-run";

 public static void main(String[] args) {
  OptionParser parser = new OptionParser() {
            {
             acceptsAll( asList( "h", "?", "help" ), "Muestra la ayuda" );
             acceptsAll( asList( A_V, "verbose" ), "Muestra información detallada" );
                accepts( A_URL, "Url de conexión al repositorio" ).withRequiredArg().ofType( String.class ).defaultsTo( "//localhost:1099/jackrabbit.repository" );
                acceptsAll( asList( A_U, "user"), "Usuario" ).withOptionalArg().ofType( String.class ).defaultsTo( "gesif" );
                acceptsAll( asList( A_P, "password"), "Contraseña" ).withRequiredArg().ofType( String.class ).defaultsTo( "gesif" );
                acceptsAll( asList( A_E, "export"), "Exportación de consulta a directorio (requiere -q)" ).withRequiredArg().ofType( String.class ).defaultsTo( "." ).describedAs( "directorio" );
                acceptsAll( asList( A_I, "import"), "Importación de ficheros del directorio" ).withRequiredArg().ofType( String.class ).describedAs( "Path completo de ficheros a importar (se permiten comodines)" );
                acceptsAll( asList( A_Q, "query"), "Consulta a realizar" ).withRequiredArg().ofType( String.class ).describedAs("consulta XPATH");
                accepts( A_GC , "Ejecuta la limpieza de borrado (requiere -u y -p)" ).withRequiredArg().ofType( String.class ).describedAs("repository home");
                accepts( A_DR , "Ejecución de importación de prueba (no importa)");
            }
        };

        OptionSet options = parser.parse( args );
        Date time = new Date();

  try {
         if ( args.length < 1 || options.has( A_H ) ) {
             parser.printHelpOn( System.out );
             System.out.println("Ejemplos:\n" +
               " RTool -q \"//*[@gst:file and @g:stepId = 13872238]\" -e /home/szarza/tmp3\n" +
               " RTool -q \"//*[@gst:file and @g:stepId=20754674]\" -e . --url //192.168.20.12:1099/jackrabbit.repository\n" +
               " RTool --url //192.168.20.12:1099/jackrabbit.repository --import ./fc15099e-d586-4046-afd1-8fc9c03bdf17.0002.pdf");
             System.exit(0);
         }
                ....

Como se ve en este ejemplo, con Jopt Simple se puede realizar todo el control de opciones y argumentos de una forma rápida y sencilla. Sin embargo no es la única, hay realmente muchas:
Usé JOpt Simple por ser una librería muy versátil y muy sencilla de usar. Sin embargo, tengo que reconocer que, en mi búsqueda de librerías, JewelCli y JCommander me dejaron impresionado por su elegancia y limpieza. JOpt Simple es una librería al estilo clásico, donde la definición de sintaxis es programática. JewelCli y JCommander son librerías con un nuevo enfoque actualizado, que usan un estilo declarativo para la definición de sintaxis, usando anotaciones. Estas últimas librerías me parecieron más adecuadas para un proyecto mayor, con más opciones y/o que fuese a tener un mantenimiento mayor.

En todo caso, creo que en una próxima ocasión probaré JewelCli. Y tu, ¿has probado alguna? ¿qué opinión tienes?

P.D.: Por cierto, feliz Stars Wars Day a todos!! ... y  #maythe4thbewithyou.

Referencias y más información:

domingo, 15 de abril de 2012

Transacciones autónomas en PostgreSQL


The cheapest, fastest, and most reliable components are those that aren’t there.
Gordon Bell.

En algunos casos, necesitamos que ciertas operaciones de una transacción de base de datos sean realizadas aunque la transacción se deshaga con un rollback. Por ejemplo, imaginemos que tenemos una función o procedimiento almacenado en la que hemos agrupado 10 operaciones. Cuando dicha función se llame, se ejecutará en un contexto transaccional, es decir, se realizará una transacción con dichas 10 operaciones de forma atómica (o todas, o ninguna). Supongamos que queremos garantizar que un subconjunto de esas operaciones (por ejemplo un par de ellas que hacen auditoría de la propia llamada) se realicen siempre, con independencia de las demás: en este caso necesitamos una transacción autónoma.

Las transacciones autónomas (Autonomous Transactions) permiten suspender el contexto de la transacción en curso (llamante), realizar una transacción independiente y reanudar el contexto de la transacción llamante sin afectar su estado. Los casos de uso típicos de uso son funcionalidades cuyos cambios queremos que persistan con independencia de la transacción que las llama: trazado (logging), operaciones de auditoría, contadores, funciones de propósito general...etc.

Un caso que se me ha dado recientemente es el de realizar una transacción batch muy muy larga (de importación de gran cantidad de  datos), donde la unidad de importación supone operaciones de modificación (inserción o actualización) en varias tablas. Imaginemos que importamos usuarios con todos sus datos: dirección, provincia, datos bancarios, teléfonos, etc... Cada usuario puede suponer 8 o 10 operaciones en diversas tablas. Si tenemos que importar 30.000 usuarios, podemos encontrarnos con una transacción compuesta por 300.000 operaciones de inserción/actualización. Este tipo de transacciones tan grandes exige muchos recursos de un servidor de base de datos: crea ocupación de memoria y genera muchos puntos de bloqueos que podrían socavar el desempeño de un sistema en producción. Una forma de solucionar un problema así es realizar una confirmación de datos por cada unidad de importación. Es decir, que el bucle general de importación (que ya se ejecuta en un contexto transaccional) llame a una función que realiza y confirma la tarea de importación de una unidad (un usuario en el ejemplo) en una transacción autónoma, con lo cual cada usuario se procesaría de forma atómica, sin que la transacción general acumule trabajo.

Con Oracle, las transacciones autónomas se declaran sin mayor dificultad usando la directiva:

PRAGMA AUTONOMOUS_TRANSACTION;

Sin embargo, en PostgreSQL las transacciones autonomas no están soportadas. No hay forma de hacerlo de forma declarativa. Sólo hay una forma de hacerlo (con dos enfoques, como veremos enseguida): realizando la parte que queremos que sea autónoma en otro contexto transaccional, es decir, en otra conexión a la base de datos:
  1. Usando un lenguaje de programación externo (untrusted).  Es decir, reducir la función PL/PgSQL a la parte que queremos garantizar su ejecución (la parte que queremos que sea autónoma) y realizar el resto en un lenguaje de programación externo a la base de datos. La función se llamará en una nueva conexión
  2. Usando dblink. Esta opción, es parecida a la anterior, pero usando siempre PL/PgSQL. Es decir, realizamos la transacción autónoma en una nueva conexión usando dblink.


dblink

dblink es un módulo del contrib de PostgreSQL que permite realizar conexiones y realizar consultas a bases de datos locales o remotas dentro de una sesión. Con un ejemplo se verá más claro:

SELECT nombre, apellidos
FROM usuario INNER JOIN
    dblink('dbname=otrabasededatos port=5432 host=localhost_u_otro' ||
           'user=usuario password=password',
           'SELECT direccion, poblacion, provincia from usuario')
            AS u(direccion varchar, poblacion varchar, provincia varchar)
    ON usuario.id = u.id;

En el anterior ejemplo, realizamos un JOIN entre dos tablas de dos bases de datos distintas (en el mismo host u otro, eso da igual). Es decir, en la sesión en curso, y a través de las funciones de dblink, se crea una conexión sobre la que enviamos nuestra query. Si esto lo hacemos contra el mismo servidor y misma base de datos... voilá: ya tenemos una conexión nueva y, por tanto, un contexto transaccional diferente en el que podemos ejecutar nuestra transacción de forma independiente (es otra conexión).

Veamos un ejemplo de cómo usarlo. Supongamos el ejemplo del inicio, donde tenemos un cursor que recorremos en el que, para cada registro, debemos realizar un par de operaciones transaccionales de forma independiente (autónoma). Aprovecharemos el ejemplo también para ilustrar cómo se puede realizar una inserción y obtener la clave del elemento recién insertado para la siguiente sentencia.

for usuario in (select * from iusuarios) loop
   raise notice 'Procesando usuario->, %',usuario.id || '-' || usuario.nombre;

   PERFORM dblink_connect_u('dbname=dbname connect_timeout=5');
   PERFORM dblink_exec('begin;');

   l_dbl_sql :=
      'insert into u2 (name,edad) values (' ||
      quote_nullable(l_name) || ', ' ||
      coalesce(l_edad::varchar,'null') || ', ' ||
      '0) returning id;';
   raise notice '[ALTA] SQL INSERT u2: %', l_dbl_sql;

   select id into l_user_id from dblink(l_dbl_sql) as d(id int4);

   l_dbl_sql :=
      'update otra_tabla set date = now() where id_user = ' || l_user_id || ';';
   raise notice '[ALTA] SQL UPDATE otra_tabla: %', l_dbl_sql;
   PERFORM dblink_exec(l_dbl_sql);

   PERFORM dblink_exec('commit;');
   PERFORM dblink_disconnect();
end loop;

Examinaremos el ejemplo, porque hay cosas interesantes que quiero destacar:
  1. Linea 4: es importante especificar el timeout, para evitar efectos indeseables. Dado que se trata de la misma base de datos en el mismo servidor, no hay que especificar nada más.
  2. Linea 5: inicio de la transacción, ya que hay más de una sentencia
  3. Líneas 9 y 10: uso de las funciones quote_nullable() y coalesce() para garantizar el correcto escape de valores y la generación de 'null' cuando existan valores nulos.
  4. Línea 14: ejemplo de ejecución de sentencia INSERT con dblink para obtención del registro recién insertado.
  5. Línea 19: ejemplo de ejecución de sentencia UPDATE (o cualquier otra sentencia que no devuelva resultados)
  6. Línea 21: confirmación de la transacción
  7. Línea 22: desconexión
  8. Líneas 11 y 17: es muy importante asegurarse de que terminamos las sentencias que enviamos a dblink con el separador de terminación punto y coma (;). Un olvido puede hacer que nuestra función se detenga esperando la terminación.
Finalmente, recomiendo usar las funciones quote_nullable() para las fechas y cadenas y coalesce() para los valores numéricos para garantizar una correcta sintaxis y escapado de las sentencias. Si estás usando PostgreSQL 9.1 o superior puedes evitar ese engorro y usar la enormemente más cómoda función format().


Referencias y más información:

martes, 6 de marzo de 2012

Balteus cumple su tercer año

(Ilustración: Fran Barquero)

"¿Se acuerda de la araña que había en su ventana? Era naranja, con las patas verdes. La vio tejer una telaraña todo el verano. Un día puso un huevo. Luego el huevo eclosionó..." - Rick Deckard

"Y salieron de él cientos de arañas... que se la comieron." - Rachael
- Blade Runner.

Siguiendo con la tradición de los post de aniversario de este blog, comienzo el apunte con otra cita del único y excepcional film "Blade Runner". Si no habéis visto la película, este (como cualquier otro) es un buen momento para hacerlo. Si la habéis visto, volved a verla. Siempre descubriréis nuevos ángulos y elementos interesantes. Es una obra tremenda y absolutamente extraordinaria.

Este 6 de Marzo este blog cumple tres años. El hecho de mantener un blog durante tres años supone claramente que no es una ocurrencia momentánea, sino hay por mi parte un compromiso claro con él y que este proyecto mío del blog es algo consolidado. No obstante, un blog sin sus correspondientes actualizaciones no es nada, así que haré un esfuerzo por incrementar el número de artículos.

El número tres es un número especial para este blog. No en vano el título del blog está dedicado a 3 estrellas (ver Acerca de Balteus). Ese motivo, esas mágicas tres estrellas, han servido de base para la ilustración especialmente realizada para la ocasión por el genial ilustrador Fran Barquero. Gracias Fran (esto se va a convertir en otra tradición...).

Tengo un montón de temas y artículos en mente para este año que espero sean de tú interés.

Un saludo a todos.

martes, 28 de febrero de 2012

Orden y formato al código PL/pgSQL


“Any code of your own that you haven’t looked at for six or more months might as well have been written by someone else.”
Eagleson’s Law

Leer y entender un trozo de código de unas pocas líneas es fácil, aunque haya sido escrito como si se hubiese caído al suelo y esparcido por toda la habitación. Ahora bien, si tenemos que leer unos cuantos cientos o miles de líneas, la cosa cambia bastante. Es fundamental tener un código ordenado y bien identado para facilitar la legibilidad.

Existen muchas herramientas que facilitan o realizan directamente esta tarea. Son las llamadas herramientas de embellecimiento o formato ("code beautifiers/formatters tools"). La mayoría de los IDE's y editores tienen herramientas para una gran variedad de lenguajes... salvo para PL, donde me he encontrado bastantes carencias.

Si necésitáis una herramienta de este estilo, una de las mejores que me he encontrado es Pl/Sql tidy. Algunas de sus características son:
  • Identación por bloques
  • Compactación
  • Ajuste de mayúsculas/minusculas
  • Alineamiento vertical en asignaciones
El uso de la herramienta es a través de línea de comando, pero también podéis usar la interesante versión online disponible.

Funciona con PL/pgSQL.


Referencias y más información:

miércoles, 1 de febrero de 2012

Apache Commons DbUtils: sencillo, rápido, ligero


“Controlling complexity is the essence of computer programming”
Brian Kernigan

Hoy traigo aqui otra de las joyas de Apache Commons: DbUtils. Ya comenté en el artículo anterior "Librerías imprescindibles en Java" que, en cualquier projecto, antes de pensar en hacer nuestras utilities, debemos pasar por la página de Commons para ver las librerías existentes antes de ponernos a reinventar la rueda. Pues bien, Commons tiene otra pequeña joya que he descubierto y aplicado recientemente con éxito.

Hay ocasiones en las que no usamos un framework de mapeo tipo ORM (JPA/EclipseLink, Hibernate, Mybatis -antiguo iBATIS-) por varias razones: modelo muy cambiante, proyecto muy pequeño, necesitamos un control más fino de las consultas, generación dinámica, etc... En estos casos, no nos queda más remedio que usar directamente JDBC. Sin embargo JDBC es un API muy engorroso que requiere mucho código de limpieza de recursos y mucha fontanería a través de sus capas. Más del 80% de nuestro código JDBC es la instanciación de objetos del API, establecimiento de parámetros, liberación y cierre.


DbUtils es un pequeño conjunto de clases diseñadas para hacer mucho más fácil el trabajo que hacemos con JDBC centrándonos en lo que queremos: consultar, actualizar, insertar. Especialmente nos ayuda en:
  • Se encarga automáticamente de la fontanería previa (conexión y preparación) y de la limpieza, evitando posibles errores de liberación de recursos.
  • Rellena las propiedades de JavaBean automáticamente a partir de ResultSets. No es necesario copiar manualmente valores en instancias de beans.
Por todo la anterior, el uso de DbUtils reduce drásticamente la cantidad de código que necesitamos con JDBC. Por hacer una comparativa, yo díría que DbUtils es a JDBC lo que JDOM a javax.xml.

Usar DbUtils es muy sencillo. Hay dos clases/interfaces principales que constituyen el corazón de DbUtils: QueryRunner y ResultSetHandler. QueryRunner se encarga de realizar todo el trabajo: abre la conexión, ejecuta la consulta o actualización y devuelve el resultado. Para devolver el resultado de diferentes formas se usan las distintas implementaciones de la clase ResultSetHandler, que permite devolver el resultado en una lista de  POJO's, lista de arrays, lista de Map, etc...

A continuación pongo un ejemplo simple de una pequeña DAO de una aplicación que tuviese que acceder a una base de datos PostgreSQL y a un AS/400 simultáneamente. En el ejemplo se pueden observar características muy importantes de ahorro de código y, por tanto, de su consecuencia más valiosa, ahorro de tiempo:
  • No hay declaraciones de Connection, Statement y ResultSet.
  • No hay liberación de recursos (DbUtil lo hace automáticamente)
  • No es necesaria la engorrosa recolección de campos a través de la iteración del ResultSet. Podemos devolver las consultas en un Map<String, Object> (NombreCampo, Valor) o en un JavaBean cuyas propiedades coincidan con los campos que devuelva nuestra consulta.


/**
   Suponemos una clase DAO cualquiera de acceso a datos
*/
public class ASDao {
   
   private AS400JDBCDataSource asDataSource;
   private PGSimpleDataSource pgDataSource;
   private QueryRunner qr;

   public ASDao() throws Exception {
       // El datasource puede instanciarse...
       asDataSource = new AS400JDBCDataSource("host4.cestel.es","asuser","xxxxxx");
       // U obtenerse a partir de un JNDI de un contenedor
       pgDataSource = new InitialContext().lookup("java:comp/env/jdbc/pgDS");
       qr = new QueryRunner(asDataSource);
       qrp = new QueryRunner(pgDataSource);
   }


   /**
       Realiza una consulta parametrizada (que debe devolver una sóla fila) y 
       devuelve un Map<String,Object> con el nombre del campo
       y su valor. Se asume que la consulta devuelve una sóla fila.
       Son apenas dos líneas.
   */
   public Map<String,Object> findData(String clientCode, String debtCode) 
                                                                throws SQLException {
       MapHandler result = new MapHandler();
       return qrp.query(
               "select c.id, c.code, de.id, de.customer_debt_id, dos.id, dos.code\n" +
               "from debt de join dossier dos on de.dossier_id = dos.id \n" + 
               "join customer c on dos.customer_id = c.id\n" +
               "where customer_debt_id = ?\n" +
               "and c.code=?\n" +
               "and end_process_date is null", result,debtCode, clientCode);
   }

   /**
       Realiza una consulta parametrizada y devuelve el resultado como una lista de 
       JavaBeans "TablaAS". La creación de la lista y "llenado" de los objetos corre 
       por su cuenta. Lo único que tenemos que hacer es crear la clase TablaAS con 
       propiedades (getter, setter). Las propiedades de los POJO (JavaBeans) que 
       se "llenaran" serán aquellas cuyo nombre coincida con el campo devuelto.
       Dos líneas (más la clase)
   */
   public List<TablaAS> findByClient(String clientCode) throws SQLException {
       ResultSetHandler<list<TablaAS>> blh = new BeanListHandler<TablaAS>(TablaAS.class);
       return qr.query("select * from  CESQ.TABLA_AS where CODCLTE = ?", blh,clientCode);
   }

   /**
       Ejemplo de la misma consulta devuelto como un array 
       o como una lista de POJO's (JavaBean)
   */
   public void test() throws SQLException {
       ArrayHandler ah = new ArrayHandler();
       ResultSetHandler<list<TablaAS>> h = 
                       new BeanListHandler<TablaAS>(TablaAS.class);
       
       Object[] resultArray = 
           qr.query("select * from  CESQ.TABLA_AS FETCH FIRST 2 ROWS ONLY", ah);

       List<TablaAS> result = 
           qr.query("select * from  CESQ.TABLA_AS FETCH FIRST 2 ROWS ONLY", h);

   }
}

Como todas las cosas, obviamente DbUtils tiene un target concreto de aplicación, no es una bala de plata para todo. Ahora bien, si no devolvemos conjuntos de datos grandes, podría usarse como perfecto sustituto de JDBC con un mínimo impacto en el rendimiento y con una importante mejora en legibilidad y mantenibilidad.

Puedes ver más ejemplos en el sitio de DbUtils.


Referencias y más información:
Related Posts Plugin for WordPress, Blogger...
cookieassistant.com