style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

JDBC simple con Spring

Hace poco escribí acerca de la característica principal de Spring, que es la inversión de control, en la cual se pasa el control de configurar y conectar objetos a Spring en vez de tener que hacerlo por medio de código o de una configuración propia (la cual hay que interpretar y para eso hay que escribir código).

En esta ocasión quiero hablar de otro aspecto de Spring, sencillo pero muy poderoso: el módulo de JDBC. Este módulo contiene varias características muy útiles, pero la más poderosa es el JdbcTemplate y su variante para Java 5, el SimpleJdbcTemplate.

Tomemos como ejemplo el ya clásico ciclo para leer datos de una tabla:

  1. Obtener un DataSource de algún lugar, ya sea por JNDI en un contenedor, o bien creándolo por código en aplicaciones independientes.
  2. Obtener una conexión a la base de datos, por medio del DataSource.
  3. Crear un PreparedStatement con el query a realizar.
  4. Si el query trae parámetros, configurarlos uno por uno.
  5. Ejecutar el query para obtener un ResultSet.
  6. Recorrer el ResultSet, haciendo algo con los objetos; un ejemplo simple es crear una instancia de alguna clase de Value Object y ponerle los valores que vienen en la tupla actual, y luego poner ese Value Object en una lista.
  7. Cerrar el ResultSet.
  8. Cerrar el PreparedStatement.
  9. Cerrar la conexión.

Y todo lo anterior tiene que estar dentro de uno o varios try-catch por si ocurre alguna excepción, cerrar los objetos que tengamos. Es algo muy tedioso de programar, sobre todo cuando se quiere obtener una lista corta de objetos que se van a presentar en alguna página web o en una interfaz y dichos objetos pueden ser simples mapas con los nombres de las columnas como llaves.
Pues bien, Spring nos ofrece un objeto llamado JdbcTemplate, el cual se configura con un DataSource (todo esto en un ApplicationContext, con lo cual no tenemos que teclear una sola línea de código para crear y configurar todo esto). Entonces el componente que necesita obtener estos datos (por ejemplo un DAO) puede simplemente tener una propiedad llamada jdbcTemplate y en el applicationContext se le pone el bean de JdbcTemplate que se haya configurado. Por ejemplo:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName"><value>java:/jdbc/ejemplo</value></property>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
  <constructor-arg><ref local="dataSource" /></constructor-arg>
</bean>

<bean id="miComponente" class="com.solab.Ejemplo">
  <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

Ahora, suponiendo que su clase tiene un método fetchUsers por ejemplo, entonces simplemente debe ser algo asi:

public void setJdbcTemplate(SimpleJdbcTemplate value) {
  jdbc = value;
}

public List<Map<String, Object>> fetchUsers(int tipo) {
  try {
    return jdbc.queryForList("SELECT uid, username, nombre, password, status FROM usuario WHERE tipo=?", tipo);
  } catch (DataAccessException ex) {
    log.error("Leyendo datos de usuarios tipo " + tipo, ex);
    return null;
  }
}

Simple, no? Y si queremos devolver instancias de una clase Usuario en vez de mapas? Entonces podemos implementar un ParameterizedRowMapper que devuelve usuarios:

public class UsuarioMapper implements ParameterizedRowMapper<Usuario> {

  public Usuario mapRow(ResultSet rs, int rowNum) throws SQLException {
    Usuario u = new Usuario();
    u.setUid(rs.getString("uid"));
    u.setUsername(rs.getString("username"));
    u.setNombre(rs.getString("nombre"));
    u.setPassword(rs.getString("password"));
    u.setStatus(rs.getInt("status"));
    return u;
  }

}

La interfaz de ParameterizedRowMapper define que el método mapRow arroja SQLException porque es obvio que vamos a estar llamando métodos del ResultSet que nos pasan. Cuando programan un ParameterizedRowMapper simplemente deben ubicar que se va a ejecutar como el paso 6 de la lista que definimos al principio, es decir, dentro del ciclo que recorre el ResultSet. Y simplemente el método de nuestro componente cambia a esto:

public List<Usuario> fetchUsers(int tipo) {
  try {
    return jdbc.query("SELECT uid, username, nombre, password, status FROM usuario WHERE tipo=?", new UsuarioMapper(), tipo);
  } catch (DataAccessException ex) {
    log.error("Leyendo datos de usuarios tipo " + tipo, ex);
    return null;
  }
}

El JdbcTemplate tiene varios métodos para leer datos, por ejemplo para obtener un solo dato o un solo registro, así como para modificar (INSERT, UPDATE, DELETE). Lo que el JdbcTemplate hace es todo lo que habíamos mencionado antes: abre una conexión, crea un PreparedStatement con el SQL y los parámetros que le dimos, lee los datos y los pone en una lista, cierra todo y los devuelve. Además, cualquier excepción de SQL que ocurra la convierte a un DataAccessException (hay toda una jerarquía de clases) pero es de tipo RuntimeException, así que podríamos incluso quitar ese try-catch y dejar que quien invoque el método se encargue de lidiar con cualquier excepción.
Les recomiendo echar un ojo a los paquetes de Spring org.springframework.jdbc y org.springframework.core.simple que es donde vienen la mayoría de las clases que intervienen en esto, aunque como pueden ver, tienen que usar solamente una o dos, pero es importante conocer las excepciones que pueden arrojarse y esas vienen en org.springframework.dao.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de benek

Vaya, esto es muy

Vaya, esto es muy interesante... Me servirá de comienzo con Spring, lo intentaré seguir aunque seguramente estaré por aquí posteando algunas dudas.

Saludos y muy buen artículo.

--
Javier Benek

Imagen de ezamudio

Y faltan transacciones

Y falta ver lo de transaccionalidad... porque hasta con el Spring JDBC puedes tener un Transaction Manager ya sea de JTA o para un standalone DataSource con el que puedes hacer varias operaciones con un JdbcTemplate y todo ocurre dentro de una transaccion que puedes revertir... eso lo posteo luego.

Given the choice of dancing pigs and security, users will choose dancing pigs, every single time. - Steve Riley

Imagen de iberck

BeanPropertyRowMapper

Es poco conocido que además de todas las ventajas que comenta ezamudio existe una clase poco documentada llamada "BeanPropertyRowMapper" la cual permite "mapear" columnas de la base de datos contra propiedades de un bean de forma automática y así evitar escribir a mano los RowMapper. La única regla que se debe seguir es que las columnas de la tabla tengan un nombre y tipo compatibles con la propiedad del bean. De esta forma se evita escribir multiples rowmappers y el desarrollo es aún más simple.

Como alternativa de la clase UsuarioMapper que se explica en el artículo se pudo haber creado un simple BeanPropertyRowMapper para evitar escribir el ParameterizedRowMapper.

Un ejemplo:

public Colonia findById(Serializable id) {
        BeanPropertyRowMapper bprm = new BeanPropertyRowMapper(Colonia.class);

        String sql = "select * from Colonia where idColonia = ?";

        return (Colonia) jdbcTemplate.queryForObject(sql,
                new Object[]{id}, bprm);
    }

Imagen de ezamudio

Interesante

No conocía esta clase; como dices, puede ahorrarte crear los mappers a mano, siempre y cuando sea un mapeo simple 1 a 1 y supongo que las columnas del query deben llamarse igual que las propiedades del bean, ademas de tener tipos compatibles como menciones.
Creo que con esto podré eliminar algunos mappers que tengo, pero a la veces tengo otros que luego devuelven joins de maestro detalle y que el mapper solamente crea un maestro y le pone los detalles en una lista...

Given the choice of dancing pigs and security, users will choose dancing pigs, every single time. - Steve Riley

Imagen de iberck

Ese es el concepto, se

Ese es el concepto, se podría crear una clase con las propiedades exactas del resultado de un join, aunque para estos casos es mejor tener un rowmapper manual para crear objetos maestro detalle. La base de BeanPropertyRowMapper es la reflexión, la busqueda de tipos primitivos y wrappers en tiempo de ejecución... nada del otro mundo pero la clase ya se encontraba en los repositorios centrales de spring.

Imagen de ezamudio

Performance?

Entonces supongo que si el performance es importante, será más rápido un mapper manual que el BeanPropertyRowMapper, ya que no habrá reflexión (que es muuuuy lenta)... tal vez si utilizara CGLIB o algo asi, podría funcionar más rápido en tiempo de ejecución...

Given the choice of dancing pigs and security, users will choose dancing pigs, every single time. - Steve Riley

Imagen de iberck

Performance

http://www.koders.com/java/fid2FA34416252440BA78F4EDB49EF701F553BAFD2F.a...

Podría ser más rápido pero nunca más que un rowmapper manual. Hablando de performance yo te recomendaría utilizar mapeo manual para tablas con muchas columnas y mapeo automático para tablas con pocas columnas ya que los mantenimientos al código fuente con BeanPropertyRowMapper son mas simples.
Otra alternativa para mejorar el performance es crear instancias compartidas de BeanPropertyRowMapper.

JavaRanch big moose saloon member

Para consultas de 50 mil registros?

Estoy haciendo una consulta y me de el siguiente error: Caused by: java.lang.OutOfMemoryError: Java heap space

Se queda sin memoria la maquina virtual de java porque la consulta retorna 50349 registros, como se hace para recuperar registros de 100 en 100 hasta obtener todos los 50349?

Consultas de 50 mil registros

Otorga más memoria a tu heap de Java por medio de los parámetros de inicio de la JVM, y si acaso dos usuarios ejecutan la misma consulta concurrentemente, duplica el parámetro de memoria, y así hasta que agotes la RAM o le compres más. Alguien más nos dará el nombre del parámetro para aumentar la memoria del heap.

A continuación me concentraré en otro enfoque que no asume que la RAM es infinita, o al menos inmensa: cargar sólo los registros requeridos bajo demanda, de manera que si dos, tres, cuatro o más usuarios realizan la misma consulta concurrentemente, cada quien sólo obtengas 100 registros a la vez. Para ello escribirás código específico para cada base de datos.

Un par de ejemplos. Primero, en MySQL puedes escribir algo como lo que sigue (no soy usuario de MySQL, así que asumo que sigue siendo una basecita muy sencilla y sin cursores propiamente dichos):

SELECT SQL_CALC_FOUND_ROWS * FROM nombre_de_la_tabla
    WHERE id > limite_inferior LIMIT 100;

SELECT FOUND_ROWS();

Del otro lado del espectro, en Oracle usarías algo así como:

SELECT *
  FROM ( SELECT a.*, ROWNNUM rnum
           FROM ( tu_consulta_más_el_predicado_order_by ) AS a
          WHERE rownum <= limite_superior )
WHERE rnum >= limite_inferior

En SQL Server lo que yo tengo referenciado es mucho más complicado.

Saludos

Javier Castañón

Imagen de ezamudio

OFFSET/LIMIT

Incrementar memoria es una solución temporal, y es específica para el caso que tienes. Pero si tu consulta mañana devuelve 200mil en vez de 50mil registros, no creo que sea tan fácil que nada mas le digas al cliente o al jefe o a quien sea "compra mas RAM" y aun así, puedes llegar al límite físico de RAM en el equipo. Para manejar conjuntos grandes de datos, en SQL puedes limitar las consultas usando OFFSET y LIMIT, pero es algo que tienes que manejar en tu aplicación. Por ejemplo si ya tienes construido un query con 3 parámetros, agrega otros 2 que van a ser OFFSET y LIMIT, convirtiendo esto:

SELECT * FROM tablota WHERE p1=? AND p2=?

en esto:

SELECT * FROM tablota WHERE p1=? AND p2=? ORDER BY p1 OFFSET ? LIMIT ?

y asegúrate que cuando llenas los parámetros, tengas valores por default, por ejemplo OFFSET 0 y LIMIT 100. Lo anterior va a ejecutar la consulta y te va a devolver un subconjunto del resultado, a partir de la posición indicada por OFFSET, y solamente va a incluir máximo LIMIT resultados. Así, tu consulta de 50mil registros puedes manejarla por páginas, y sabes que por ejemplo si estás mostrando de 100 en 100 los resultados, la página 5 entonces es OFFSET 400 (y LIMIT sigue siendo 100).

Y para saber el número de páginas que tiene tu consulta, entonces ANTES de hacer el SELECT directo de datos haces un SELECT count(*) para saber que tu consulta va a devolver por ejemplo 50349 registros y si vas a tener páginas de 100 registros entonces tendrás un total de 504 páginas.

OJO: es muy importante que pongas un ORDER BY para garantizar que cada vez que hagas la consulta, los datos vengan en el mismo orden y entonces tenga sentido encontrar algo siempre en la página 3 y no que algun dato que aparecia en esa página de repente está en otra página porque si no pones ORDER BY entonces el servidor de base de datos te devuelve los datos como se le ocurra.

Imagen de AlexSnake

Relacionar varias tablas

Que tal amigos, esta muy interesante lo que se hace con spring pero en el ejemplo que muestran no mencionan como se pueden obtener datos de dos tablas diferentes. Suponiendo que el usuario que estan poniendo esta relacionado algun grupo y se quiere obtener el nombre del grupo por medio de la relacion que hay entre ambos como puedes llenar el objeto del grupo???
Bueno espero que me puedan ayudar. Gracias y saludos.

Imagen de ezamudio

Varias tablas

Para relaciones maestro-detalle hay varias opciones. Si quieres obtener un solo maestro con sus detalles, puedes hacer el query con un LEFT JOIN y un mapper especial que primero crea el maestro con las columnas correspondientes y luego le va a agregando a ese mismo maestro los detalles de los demas registros; en este caso es muy importante que uses una nueva instancia del Mapper en cada query. Esto lo haces simplemente teniendo una variable del tipo del objeto maestro; en la primera llamada a mapRow será nula y entonces creas el maestro, en las llamadas subsecuentes ya tendrás el maestro y solamente le vas agregando detalles.

Si ya tienes el maestro y quieres obtener sus detalles, puedes hacer un Mapper que reciba el maestro como parametro (en su constructor por ejemplo) y usarlo con un query que lee solamente los detalles correspondientes al objeto maestro y se los va a agregando en cada llamada a mapRow.

Imagen de ezamudio

Spring 3

Este post lo escribí cuando estaba Spring 2.5; a partir de Spring 3.0 la interfaz ParameterizedRowMapper<T> se considera obsoleta y es mejor usar directamente la interfaz RowMapper<T>.

Imagen de AlexSnake

Oye ezamudio, veo que

Oye ezamudio, veo que participas mucho en los blogs y para ser sincero tengo unas dudas y no se si me puedas ayudar a resolver, es con respecto a integrar hibernate con spring ya qu ese me ha hecho muy laborioso. bueno espero puedas apoyarme.
Saludos.

Imagen de ezamudio

pon tu duda

Pon tu duda en el foro de JEE y puedes obtener respuesta de todos los que han manejado Spring+Hibernate porque creeme que no soy el único. Si bajas el código de JavaMéxico 2.0 también puedes ver ejemplos funcionando de integración Spring+Hibernate. No es la gran cosa y viene perfectamente documentado en el sitio de Spring.

Imagen de ingscjoshua

Duda

Hola estoy configurando un proyecto nuevo y necesito usar simplejdbc templeate pero al momento de inyectarle el dataSource a mi Objeto en el appConext me sale el siguiente:

 Error GRAVE: Context initialization failed
java.lang.NoClassDefFoundError: org/springframework/dao/DataAccessException
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2427)
        at java.lang.Class.getDeclaredMethod(Class.java:1935)
        at org.springframework.core.LocalVariableTableParameterNameDiscoverer$LocalVariableTableVisitor.resolveMember(LocalVariableTableParameterNameDiscoverer.java:236)
        at org.springframework.core.LocalVariableTableParameterNameDiscoverer$LocalVariableTableVisitor.visitEnd(LocalVariableTableParameterNameDiscoverer.java:219)
        at org.springframework.asm.ClassReader.accept(Unknown Source)
        at org.springframework.asm.ClassReader.accept(Unknown Source)

Imagen de ezamudio

Típico...

A mi me pasa a cada rato con proyectos nuevos. En tiempo de ejecución necesitas meter spring-transaction.jar (no lo necesitas para compilar, sólo en runtime), ahí viene esa excepción.

Imagen de ingscjoshua

Sip Muchas Gracias

muchas gracais!!! esto de ser novato en configurar proyectos de 0 chale jajaja muchas gracias esq uso struts2

Imagen de ingscjoshua

Dudada

Hola Buenas Noches una Enorme pregunta , me pueden ubicar en esta dudad, tengo implementado el simpleJdbcTempleate, y necesito hacer una transaccion, tengo un BO que en su logica realiza 5 insert masivos y todos son dependientes entocns si falla alguno necesito hacerle rollback a todas las tablas, mi pregunt es Se puede hacer con el Transaction manager?, si es Asi tienen algun ejemplo o info respecto a eso?

Hola

Hola ezamudio, muchas veces eh entrado a esta web, y tus respuestas me son muy utiles, ahora me eh registrado solo para poder preguntarte algo y espero me puedas ayudar porfavor.
estoy utilizando el limit y el offset para una paginacion con postgress.
mi duda es .. para hacer un

String sql = select * from tb_ejemplo

lo ejecutaria asi -> jdbcTemplate.query(sql, new RowMapperConstants.EjemploMApper());

hasta ahi todo genial...

pero no encuentro solucion para hacer un select enviandole parametros, como un hashmap
por ejemplo

String sql = "Select * from tb_ejemplo where nombre like ' :nombre %'"
Map parametros= new HashMap();
parametros.put("nombre",edgar.getNombre());
jdbcTemplate.query(sql,parametros, new RowMapperConstants.EjemploMApper());

esto me resulta para un insert o un update, pero para un select no me resulta.

Eh encontrado ejemplos donde envian parametros al select mediante un Object[] , sim ebargo es solo par aretornar un Bean, y no una Lista de Beans , que es lo que quiero.

Si supieras algo de esto por favor hechame una mano.
Saludos !

Imagen de ezamudio

Spring 3.1

En Spring 3.0, está la SimpleJdbcTemplate, y en Spring 3.1 por ahí sigue pero ya está deprecada porque ahora JdbcTemplate ya tiene todos los métodos de SimpleJdbcTemplate (al menos para parámetros ordenados, no sé si por nombre). Con SimpleJdbcTemplate puedes hacer consultas así:

jdbc.query("SELECT * FROM tabla WHERE nombre like ? LIMIT ? OFFSET ?", new MiRowMapper(), String.format("%s*", nombre), 50, 250);

Simplemente revisa la documentación de la clase, está bastante completa.

Hola

Gracias , ya estoy revisando la documentacion, estaba utilizando spring 2.6

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">