El framework iBatis DataMapper (también conocido como SQL Maps) permite reducir significativamente la codificación en java para una aplicación que maneja base de datos relacional. Quizás la primera impresión que uno tenga es que sea igual a Hibernate (que es un mapeador de objetos con tablas relacionales – ORM). Ibatis es diferente. Como veremos en este tutor, iBatis mapea las consultas SQL y permite interactuarlas con JavaBeans tanto como parámetros de entrada y como salidas.
Manos a la obra
Para comenzar, obtendremos el framework de la página de ASF: http://ibatis.apache.org/ El archivo que habremos bajado (iBATIS_DBL-2.1.7.XX.zip) contendrá tres archivos .jar. En este tutor solo usaremos ibatis-common-2.jar y ibatis-sqlmap-2.jar. No necesita de algún otro .jar, al menos para este capítulo.
Definiendo la estructura de los datos
Crearemos nuestro JavaBean Categoria con la siguiente estructura.
package com.jugperu.tutores.ibatis.beans;<br /><br />public class Categoria {<br /> private int id; // podemos utilizar tipos de datos nativos<br /> private String nombre;<br />/*... poner sus respectivos métodos<br />setXX() y getXX() para que tenga el<br />patrón JavaBean (value object)*/<br />}
También necesitamos crear nuestra base de datos. Podemos usar cualquier motor; naturalmente necesitaremos su respectivo driver.
Crearemos nuestra tabla CATEGORIA, la cual tendrá la siguiente estructura:
Nombre | Tipo |
|---|---|
CAT_ID | Autonumérico (clave principal) |
CAT_NOMBRE | Varchar(30) |
Como se puede ver, hemos puesto nombres de campos distintos a las propiedades de nuestro JavaBean. Veremos como en iBatis podremos asociar cada columna con su respectiva propiedad en los objetos.
Configuración de la conexión a la base de datos desde nuestra aplicación
Crearemos el archivo database.properties dentro del paquete com.jugperu.tutores.ibatis.resources el cual tendrá los siguientes valores:
#el driver de nuestra base de datos<br />jdbc.driver=org.hsqldb.jdbcDriver<br />#el url para nuestra conexion.<br />jdbc.url=jdbc:hsqldb:file:data/productos.hsqldb<br />#el usuario<br />jdbc.username=sa<br />#y la contraseña<br />jdbc.password=
Crearemos el archivo *SqlMapConfig.xml *que tendrá el siguiente contenido:
<?xml version="1.0" encoding="UTF-8" ?><br /><!DOCTYPE sqlMapConfig<br />PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"<br />"http://www.ibatis.com/dtd/sql-map-config-2.dtd"><br /><sqlMapConfig><br /> <properties resource="com/jugperu/tutores/ibatis/resources/database.properties"/><br /> <transactionManager type="JDBC"><br /> <dataSource type="SIMPLE"><br /> <property name="JDBC.Driver" value="${jdbc.driver}"/><br /> <property name="JDBC.ConnectionURL" value="${jdbc.url}"/><br /> <property name="JDBC.Username" value="${jdbc.username}"/><br /> <property name="JDBC.Password" value="${jdbc.password}"/><br /> </dataSource><br /> </transactionManager><br /> <sqlMap resource="com/jugperu/tutores/ibatis/resources/Almacen.xml"/><br /></sqlMapConfig>
El atributo “resource” del elemento “properties” debe apuntar a la ubicación del archivo .properties que acabamos de crear. Debe estar en posición relativa a nuestras clases. Si deseamos utilizar una ubicación que esté fuera del alcance de la aplicación, utilizaremos el atributo “url”.
<properties url="file://c:/proyecto/database.properties"/>
Vemos que las propiedades de las conexión (elementos
El elemento
Los demás elementos de este .xml lo detallaremos en el siguiente capítulo.
Definiendo el mapa de consultas.
Crearemos el archivo Almacen.xml dentro del paquete com.jugperu.tutores.ibatis.resources el cual tendrá el siguiente contenido
<?xml version="1.0" encoding="UTF-8" ?><br /><!DOCTYPE sqlMap<br />PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"<br />"http://www.ibatis.com/dtd/sql-map-2.dtd"><br /><sqlMap namespace="almacen"><br /> <typeAlias type="com.jugperu.tutores.ibatis.beans.Categoria" alias="categoria"/><br /> <insert id="insertCategoria" parameterClass="categoria"><br /> insert into CATEGORIA<br /> (cat_nombre)<br /> values (#nombre#)<br /> </insert><br /> <select id="getCategoriaPorId" resultClass="categoria" parameterClass="int"><br /> select cat_id as id, cat_nombre as nombre<br /> from categoria<br /> where cat_id=#value#<br /> </select><br /></sqlMap>
Detallaremos el contenido de este .xml:
El elemento
Vemos que tiene elementos que se pueden asociar rápidamente a las sentencias SQL. Todos estos elementos tienen el atributo “id”. Este nos permitirá identificar a cada uno de ellos. Detallaremos la estructura de cada uno:
<insert id="insertCategoria" parameterClass="categoria"><br /> insert into CATEGORIA<br /> (cat_nombre)<br /> values (#nombre#)<br /> </insert>
Este elemento recibe como parámetro un objeto cuyo tipo está
definido en el atributo “resultClass”. Como el nombre
completo de nuestra clase es bien largo, hemos definido un alias con
el elemento
El comando INSERT debe guardar relación con la sintaxis del motor que estamos usando. Los valores encerrados en signos numerales (#) hacen referencia a las propiedades del objeto recibido como parámetro.
El elemento *
<select id="getCategoriaPorId" resultClass="categoria" parameterClass="int"><br /> select cat_id as id, cat_nombre as nombre<br /> from categoria<br /> where cat_id=#value#<br /> </select>
En este elemento tiene una sentencia SQL donde los campos que son seleccionados tienen un alias. Es decir, el campo CAT_ID tiene como alias “id”, y CAT_NOMBRE tiene como alias “nombre”. De esta manera iBatis colocará cada campo de la tabla resultante y los colocará en sus respectivas propiedades del objeto a devolver.
Programa de prueba
La mejor manera de hacer un programa de prueba es utilizando un TestCase en JUnit. Crearemos nuestro TestCase llamándolo IbatisMapsTestCase y tendrá el siguiente método setUp().
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");<br />// el .xml para la conexión<br /> sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);<br />//construye el manejador de llamadas al iBatis
Crearemos nuestro test para registrar objetos Categorias.
public void testInsertarCategorias() throws SQLException {<br /> try {<br /> sqlMap.startTransaction();<br /> Categoria c1 = new Categoria();<br /> Categoria c2 = new Categoria();<br /> Categoria c3 = new Categoria();<br /> c1.setNombre("memorias");<br /> c2.setNombre("placas");<br /> c3.setNombre("procesadores");<br /><br /> sqlMap.insert("insertCategoria", c1);<br /> sqlMap.insert("insertCategoria", c2);<br /> sqlMap.insert("insertCategoria", c3);<br /><br /> sqlMap.commitTransaction();<br /> } catch (SQLException ex) {<br /> ex.printStackTrace();<br /> assertTrue(false);<br /> } finally {<br /> sqlMap.endTransaction();<br /><br /> }<br /> }
También haremos un test para obtener un objeto.
public void testGetCategoriaPorId() throws SQLException {<br /> try {<br /> sqlMap.startTransaction();<br /><br /> Categoria c1 = (Categoria) sqlMap.queryForObject(<br /> "getCategoriaPorId", Integer.valueOf(2));<br /> assertNotNull(c1);<br /> mostrar(c1);<br /><br /> } catch (SQLException ex) {<br /> ex.printStackTrace();<br /> assertTrue(false);<br /> } finally {<br /> sqlMap.endTransaction();<br /> }<br /> }<br /><br /> private static void mostrar(Categoria cat) {<br /> System.out.println(cat.getId() + "\t" + cat.getNombre());<br /> }
Claves autogeneradas
El método “insert” de sqlMap devuelve un objeto que contiene el valor de la nueva clave generada (si aplica). Pero en nuestro ejemplo el valor que devuelve es null, a pesar que hemos declarado que la clave primaria es autonumérica . iBatis nunca sabrá cual es el valor a menos que pongamos en la sentencia sql qué valor tiene que devolver.
Editemos Almacen.xml en el
elemento
...<br /> <insert id="insertCategoria" parameterClass="categoria"><br /> insert into CATEGORIA<br /> (cat_nombre)<br /> values (#nombre#)<br /> <b><selectKey resultClass="int"></b><br /> <b>select distinct identity() as id </b><br /> <b>from categoria </b><br /> <b></selectKey></b><br /> </insert><br />...
Esta es la sentencia para HSQLDB, que es el motor usado en este ejemplo. Para MySQL el comando SQL que está dentro de
select last_insert_id() as id
... ``` y para SQL Server: ```java ...
select @@IDENTITY as ID
... ``` ### Manejando resultados #### Definiendo mapa de resultados Hemos visto que para asociar todos los campos de una consulta a las propiedades del JavaBean, cada uno de estos debe tener un alías. Lo cual nos puede ser un gran problema, ya que si se tratase de varios campos, tendríamos que poner varios “as” por cada uno. Y si fueran varias sentencias SQL, también nos puede resulta problemático hacer una modificación. iBatis nos permite crear un mapa de resultado. Allí podremos definir la asociación que utilizaremos entre propiedades del objeto y campos de la consulta. Para ello agregaremos el siguiente elemento en *Almacen.xml* ```java
select *
from categoria
</select> ``` Como se puede ver, ya no es necesario colocar un alias por cada campo. Y crearemos un nuevo test para probarlo: ```java List lista = sqlMap.queryForList("getAllCategoria", null);
for (Iterator iter = lista.iterator(); iter.hasNext(); ) {
Categoria cat = (Categoria) iter.next();
mostrar(cat);
} ``` #### Resultados sin JavaBeans No necesariamente utilizaremos un JavaBean para obtener un resultado. Podemos utilizar un *java.util.HashMap* para obtener todos los campos de una consulta. Cada *key* será el nombre del campo, y su respectivo *value* será el valor asociado. ```java
...
``` El objeto devuelto por *queryForObject()* será de clase *java.util.HashMap*. ```java HashMap map = (HashMap) sqlMap.queryForObject(
"getMapCategoriaPorId", Integer.valueOf(1)); ``` Por tanto, podríamos construir una consulta con diversos campos sin preocuparnos por la estructura que va a devolver. También podemos obtener solo un campo en una lista. ```java
...
``` Y cada objeto de la lista obtenida de *queryForList()* será un *java.lang.String*. ```java List lista = sqlMap.queryForList("getNombresCategoria", null); ``` Naturalmente, podríamos crear una consulta que tenga como *resultMap* un objeto *HashMap *e invocarlo con *queryForList()* . El resultado será una lista donde cada elemento será un *hashmap*. ### Manejando parámetros iBatis también permite definir los parámetros que van a recibir las consultas. Su estructura es muy similar a los *resultMaps*. ```java
//......
update categoria
set cat_nombre=#nombre#
where cat_id=#id#
update categoria
set cat_nombre=#nombre#
where cat_id=#id#
Campo | Tipo |
|---|---|
PR_ID | Autonumérico (clave principal) |
PR_NOMBRE | Varchar(30) |
PR_PRECIO | Decimal |
PR_STOCK | Numérico entero |
PR_CATEGORIA | Numérico no nulo (clave foránea de CATEGORIA) |
public class Producto {
private int id;
private String nombre;
private int stock;
private double precio;
private Categoria categoria;
//.....
} ``` #### Agregando un registro Crearemos un *
//...
insert into producto
(pr_nombre,pr_stock,pr_precio,pr_categoria)
values (#nombre#,#stock#,#precio#,#categoria.id#)
"getCategoriaPorId", Integer.valueOf(1));
Producto p1 = new Producto();
p1.setNombre("Kingston");
p1.setPrecio(300.50);
p1.setStock(5);
p1.setCategoria(cat);
sqlMap.insert("insertProducto", p1); ``` Es necesario recalcar que iBatis no agrega automáticamente los objetos asociados que no existan en la base de datos. Es decir, si se crea un objeto *Categoria* y se asocia a un nuevo objeto de *Producto*, al hacer el *insert()* solo se guardarán los valores del objeto *Producto* y no los del objeto *Categoria*. #### Obteniendo un registro Para obtener un registro mapeado en objeto, al <*resultMap>* se agregará un atributo más: ```java
Integer.valueOf(1));
mostrar(p); //muestra cada campo del objeto “p”
//.....
private static void mostrar(Producto p) {
System.out.println(p.getId() + "\t" + p.getNombre() + "\t" +
p.getPrecio() + "\t" + p.getStock() + "(" +
p.getCategoria().getNombre() + ")");
} ``` Pero esta solución tiene una deficiencia: para obtener un producto, iBatis hará dos consultas: uno para el producto y otro para la categoría. Si fueran varios productos, hará una consulta por los productos y N consultas por cada producto para obtener su categoria. Esto se puede solucionar haciendo un *join* modificando el *
....
``` ## Conclusiones Con iBatis se puede mapear las consultas que necesitamos para nuestro proyecto. La sintaxis que se utilice para manejar los registros de la base de datos está fuertemente aislada en la lógica de negocio. Esto nos permite tener un código limpio de sentencias SQL. Si es necesario hacer alguna modificación en SQL, bastará con editar el XML y no una clase en java evitando la compilación de esta.