Configuración y Operaciones Fundamentales con MyBatis en Java

MyBatis es un framework de persistencia que simplifica drásticamente la interacción con bases de datos relacionales en aplicaciones Java. Actúa como un puente entre los objetos Java y las sentencias SQL, eliminando gran parte del código repetitivo asociado a JDBC y ofreciendo un control total sobre las consultas SQL.

Configuración Inicial del Proyecto

Creación del Proyecto Maven

Para comenzar, configuraremos un proyecto Maven e incluiremos las dependencias esenciales para trabajar con MyBatis y una base de datos MySQL. Necesitaremos el núcleo de MyBatis, el conector JDBC de MySQL y JUnit para pruebas.

<dependencies>
       <!-- Core de MyBatis -->
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.5.10</version>
       </dependency>
       <!-- Conector JDBC para MySQL -->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.28</version>
       </dependency>
       <!-- JUnit para pruebas unitarias -->
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.13.2</version>
           <scope>test</scope>
       </dependency>
   </dependencies>
   

Archivo de Configuración Global de MyBatis

El archivo de configuración principal de MyBatis, comúnmente llamado mybatis-config.xml, se ubica en src/main/resources. Este archivo define la conexión a la base de datos, el entorno de transacciones y dónde encontrar los archivos de mapeo SQL (mappers).

A continuación, un ejemplo básico:

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
   <environments default="desarrollo">
       <environment id="desarrollo">
           <!-- Gestión de transacciones manual mediante JDBC -->
           <transactionManager type="JDBC"/>
           <!-- Configuración de DataSource con pool de conexiones -->
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mi_aplicacion_db?serverTimezone=UTC"/>
               <property name="username" value="user_app"/>
               <property name="password" value="my_secure_pass"/>
           </dataSource>
       </environment>
   </environments>
   <!-- Inclusión de archivos de mapeo SQL -->
   <mappers>
       <mapper resource="mappers/UsuarioMapper.xml"/>
   </mappers>
</configuration>
   

En este archivo, la sección <environments> define los detalles de conexión a la base de datos, y <mappers> indica dónde se encuentran los archivos XML que contienen las sentencias SQL.

Definición del Modelo de Datos (POJO)

Para interactuar con las tablas de la base de datos, necesitamos crear clases Java (Plain Old Java Objects o POJOs) que representen las filas de dichas tablas. Por ejemplo, una clase Usuario que mapee a una tabla usuarios.

package com.example.app.model;

public class Usuario {
   private Integer id;
   private String nombre;
   private String contrasena;
   private int edad;
   private String genero;

   // Constructor vacío
   public Usuario() {}

   // Getters y Setters
   public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   public String getNombre() { return nombre; }
   public void setNombre(String nombre) { this.nombre = nombre; }
   public String getContrasena() { return contrasena; }
   public void setContrasena(String contrasena) { this.contrasena = contrasena; }
   public int getEdad() { return edad; }
   public void setEdad(int edad) { this.edad = edad; }
   public String getGenero() { return genero; }
   public void setGenero(String genero) { this.genero = genero; }

   @Override
   public String toString() {
       return "Usuario{id=" + id + ", nombre='" + nombre + "', edad=" + edad + ", genero='" + genero + "'}";
   }
}
   

Interfaz Mapper

MyBatis fomenta la programación orientada a interfaces. Creamos una interfaz Java que declara los métodos para las operaciones de base de datos. MyBatis generará una implementación de esta interfaz en tiempo de ejecución. Esta interfaz reemplaza el patrón DAO tradicional.

package com.example.app.mappers;

import com.example.app.model.Usuario;
import java.util.List;

public interface UsuarioMapper {
   int insertarUsuario(Usuario usuario);
   Usuario buscarUsuarioPorId(Integer id);
   List<Usuario> obtenerTodosLosUsuarios();
   int actualizarUsuario(Usuario usuario);
   int eliminarUsuario(Integer id);
}
   

Archivo XML de Mapeo SQL

Cada interfaz Mapper se asocia con un archivo XML que contiene las sentencias SQL correspondientes a los métodos declarados en la interfaz. Estos archivos suelen tener el formato [NombreInterfaz]Mapper.xml y se ubican, por convención, en el mismo paquete que la interfaz Mapper.

Dos reglas fundamentales para la programación con interfaces en MyBatis:

  1. El atributo namespace del archivo XML debe ser el nombre completo de la interfaz Mapper.
  2. El atributo id de cada sentencia SQL (<insert>, <select>, etc.) debe coincidir exactamente con el nombre del método en la interfaz Mapper.
<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="com.example.app.mappers.UsuarioMapper">

   <!-- Mapea el método insertarUsuario(Usuario usuario) -->
   <insert id="insertarUsuario" useGeneratedKeys="true" keyProperty="id">
       INSERT INTO usuarios (nombre, contrasena, edad, genero)
       VALUES (#{nombre}, #{contrasena}, #{edad}, #{genero})
   </insert>

   <!-- Mapea el método buscarUsuarioPorId(Integer id) -->
   <select id="buscarUsuarioPorId" resultType="com.example.app.model.Usuario">
       SELECT id, nombre, contrasena, edad, genero FROM usuarios WHERE id = #{id}
   </select>

   <!-- Mapea el método obtenerTodosLosUsuarios() -->
   <select id="obtenerTodosLosUsuarios" resultType="com.example.app.model.Usuario">
       SELECT id, nombre, contrasena, edad, genero FROM usuarios
   </select>

   <!-- Mapea el método actualizarUsuario(Usuario usuario) -->
   <update id="actualizarUsuario">
       UPDATE usuarios
       SET nombre = #{nombre}, contrasena = #{contrasena}, edad = #{edad}, genero = #{genero}
       WHERE id = #{id}
   </update>

   <!-- Mapea el método eliminarUsuario(Integer id) -->
   <delete id="eliminarUsuario">
       DELETE FROM usuarios WHERE id = #{id}
   </delete>

</mapper>
   

Ejecución de Operaciones y Pruebas

Para interactuar con la base de datos a través de MyBatis, se utiliza el objeto SqlSession, que representa una sesión de comunicación con la base de datos. El flujo general es:

  1. Cargar el archivo de configuración de MyBatis.
  2. Construir un SqlSessionFactory.
  3. Obtener un SqlSession de la fábrica.
  4. Obtener una instancia de la interfaz Mapper usando el SqlSession.
  5. Invocar los métodos del Mapper.

El siguiente código de prueba JUnit demuestra cómo insertar un nuevo usuario:

package com.example.app.tests;

import com.example.app.mappers.UsuarioMapper;
import com.example.app.model.Usuario;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class PruebaOperacionesUsuario {

   @Test
   public void testInsertarYBuscarUsuario() throws IOException {
       // 1. Cargar el archivo de configuración de MyBatis
       InputStream configStream = Resources.getResourceAsStream("mybatis-config.xml");

       // 2. Construir el SqlSessionFactory (una por aplicación)
       SqlSessionFactory fabricaSQL = new SqlSessionFactoryBuilder().build(configStream);

       // 3. Abrir una SqlSession. true habilita el auto-commit.
       // Si es false, se requiere session.commit() explícitamente.
       try (SqlSession sesionSQL = fabricaSQL.openSession(true)) {
           // 4. Obtener una instancia del Mapper a través de la sesión
           UsuarioMapper mapperUsuario = sesionSQL.getMapper(UsuarioMapper.class);

           // Crear y registrar un nuevo usuario
           Usuario nuevoUsuario = new Usuario();
           nuevoUsuario.setNombre("Ana García");
           nuevoUsuario.setContrasena("ana_pass_segura");
           nuevoUsuario.setEdad(28);
           nuevoUsuario.setGenero("Femenino");

           int filasInsertadas = mapperUsuario.insertarUsuario(nuevoUsuario);
           System.out.println("Filas insertadas: " + filasInsertadas);
           System.out.println("ID del nuevo usuario: " + nuevoUsuario.getId());

           // Buscar el usuario insertado por su ID
           Usuario usuarioEncontrado = mapperUsuario.buscarUsuarioPorId(nuevoUsuario.getId());
           System.out.println("Usuario encontrado: " + usuarioEncontrado);

           // Obtener todos los usuarios
           List<Usuario> todosLosUsuarios = mapperUsuario.obtenerTodosLosUsuarios();
           System.out.println("Total de usuarios: " + todosLosUsuarios.size());
           todosLosUsuarios.forEach(System.out::println);

           // Actualizar un usuario
           if (usuarioEncontrado != null) {
               usuarioEncontrado.setEdad(29);
               usuarioEncontrado.setContrasena("nueva_pass");
               int filasActualizadas = mapperUsuario.actualizarUsuario(usuarioEncontrado);
               System.out.println("Filas actualizadas: " + filasActualizadas);
           }

           // Eliminar un usuario
           int filasEliminadas = mapperUsuario.eliminarUsuario(nuevoUsuario.getId());
           System.out.println("Filas eliminadas: " + filasEliminadas);

       } catch (Exception e) {
           e.printStackTrace();
           // En caso de error y si auto-commit es false, se haría rollback aquí
       }
   }
}
   

Optimizaciones y Configuración Avanzada

Confirmación Automática de Transacciones

Por defecto, SqlSession no realiza commits automáticos. Para facilitar el desarrollo y las pruebas, se puede habilitar la confirmación automática pasando true al método openSession():

SqlSession sesionSQL = fabricaSQL.openSession(true); // Las operaciones se confirman automáticamente
   

Si se utiliza openSession() sin argumentos o con false, se deberá llamar a sesionSQL.commit() para guardar los cambios en la base de datos.

Integración de Logging con Log4j

MyBatis puede integrarse con sistemas de logging como Log4j para visualizar las sentencias SQL ejecutadas y otros detalles del framework. Esto es invaluable para la depuración.

Primero, añada la dependencia de Log4j a su pom.xml:

<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
</dependency>
   

Luego, cree un archivo log4j.xml en src/main/resources para configurar los niveles de logging:

<?xml version="1.0" encoding="UTF-8"?>

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

   <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
       <param name="Target" value="System.out"/>
       <layout class="org.apache.log4j.PatternLayout">
           <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
       </layout>
   </appender>

   <!-- Configuración para ver las sentencias SQL ejecutadas -->
   <logger name="java.sql">
       <level value="debug"/>
   </logger>
   <!-- Configuración para mensajes de MyBatis -->
   <logger name="org.apache.ibatis">
       <level value="info"/>
   </logger>

   <root>
       <priority value="debug"/>
       <appender-ref ref="CONSOLE"/>
   </root>

</log4j:configuration>
   

Los niveles de logging (FATAL > ERROR > WARN > INFO > DEBUG > TRACE) controlan la verbosidad de los mensajes. debug mostrará las sentencias SQL completas.

Detalle del Archivo de Configuración de MyBatis

El mybatis-config.xml ofrece múltiples secciones para una configuración granular:

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
   <!-- 1. Propiedades: Carga un archivo .properties externo -->
   <properties resource="jdbc.properties"/>

   <!-- 2. Settings: Configuración global de MyBatis -->
   <settings>
       <!-- Mapea automáticamente nombres de columna con guion bajo a camelCase en Java -->
       <setting name="mapUnderscoreToCamelCase" value="true"/>
       <!-- Habilita la carga perezosa de relaciones -->
       <setting name="lazyLoadingEnabled" value="true"/>
       <!-- Deshabilita la carga perezosa agresiva (para ciertos casos de uso) -->
       <setting name="aggressiveLazyLoading" value="false"/>
   </settings>

   <!-- 3. Type Aliases: Alias para nombres de clases completos -->
   <typeAliases>
       <!-- Define un alias para todas las clases en un paquete. El alias es el nombre de la clase (ignorando mayúsculas/minúsculas) -->
       <package name="com.example.app.model"/>
   </typeAliases>

   <!-- 4. Environments: Define múltiples configuraciones de base de datos -->
   <environments default="entornoProduccion">
       <environment id="entornoProduccion">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <!-- Uso de propiedades definidas en jdbc.properties -->
               <property name="driver" value="${jdbc.driver}"/>
               <property name="url" value="${jdbc.url}"/>
               <property name="username" value="${jdbc.username}"/>
               <property name="password" value="${jdbc.password}"/>
           </dataSource>
       </environment>
   </environments>

   <!-- 5. Mappers: Registra los archivos XML de mapeo -->
   <mappers>
       <!-- Registra todos los mappers en un paquete. Requiere que la interfaz y el XML estén en el mismo paquete y tengan el mismo nombre base. -->
       <package name="com.example.app.mappers"/>
   </mappers>
</configuration>
   

Para la sección <properties>, podemos crear un archivo jdbc.properties en src/main/resources con los detalles de conexión:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mi_aplicacion_db?serverTimezone=UTC
jdbc.username=user_app
jdbc.password=my_secure_pass
   

Operaciones CRUD con MyBatis: Enfocándose en Consultas

Las operaciones de inserción, actualización y eliminación (CRUD) son bastante directas, ya que suelen devolver el número de filas afectadas, similar al ejemplo de inserción visto anteriormente.

Las consultas (SELECT) son un poco más elaboradas. El atributo resultType en una sentencia <select> le indica a MyBatis cómo mapear las columnas de la base de datos a un objeto Java. Si los nombres de las columnas coinciden con las propiedades del POJO (o si mapUnderscoreToCamelCase está activado y las convenciones se respetan), MyBatis puede realizar el mapeo automáticamente.

<!-- Consulta para un solo objeto Usuario por ID -->
<select id="buscarUsuarioPorId" resultType="com.example.app.model.Usuario">
   SELECT id, nombre, contrasena, edad, genero FROM usuarios WHERE id = #{id}
</select>

<!-- Consulta para obtener una lista de objetos Usuario -->
<select id="obtenerTodosLosUsuarios" resultType="Usuario"> <!-- Usando alias de tipo -->
   SELECT id, nombre, contrasena, edad, genero FROM usuarios
</select>
   

Cuando el resultado de una consulta es un único registro, se puede usar directamente la clase del POJO como resultType. Sin embargo, si la consulta espera múltiples registros, la interfaz Mapper debe retornar una List<TuPOJO>. Intentar mapear múltiples resultados a un solo objeto Java resultará en una excepción TooManyResultsException.

En casos donde el mapeo entre columnas y propiedades no es directo o se necesita un mapeo más complejo (por ejemplo, relaciones uno a muchos o muchos a uno), se utiliza el elemento <resultMap> para definir un mapeo personalizado.

Etiquetas: MyBatis java SQL JDBC ORM

Publicado el 8-13 06:16