En el desarrollo Java con persistencia, MyBatis es una herramienta fundamental. A continuación se explican conceptos esenciales sobre su uso y arquitectura interna.
¿Cuál es la diferencia entre #{} y ${}?
${}realiza una sustitución textual directa del valor proporcionado. Se usa comúnmente para inyectar nombres de columnas, tablas o cláusulas dinámicas como enORDER BY ${columna}. No ofrece protección contra inyección SQL.#{}representa un parámetro preparado (PreparedStatement). MyBatis lo reemplaza por un?y asigna el valor mediante métodos seguros comops.setString(). Soporta acceso a propiedades anidadas mediante reflexión, como#{usuario.nombre}.
Etiquetas adicionales en archivos XML de mapeo
Además de <select>, <insert>, <update> y <delete>, MyBatis soporta:
<resultMap>: define mapeos complejos entre columnas y propiedades.<sql>y<include>: permiten reutilizar fragmentos SQL.<selectKey>: genera valores de clave primaria en bases que no soportan autoincremento.- Etiquetas de SQL dinámico:
<if>,<choose>/<when>/<otherwise>,<where>,<set>,<trim>,<foreach>,<bind>.
Funcionamiento de las interfaces Mapper y sobrecarga de métodos
MyBatis utiliza proxies generados con JDK Dynamic Proxy. Cada método en una interfaz Mapper se resuelve mediante la combinación única de namespace + id, donde el namespace coincide con el nombre completo de la interfaz y el id con el nombre del método.
Aunque técnicamente se pueden definir métodos sobrecargados en la interfaz, todos deben compartir el mismo id en el XML. Esto funciona gracias al contexto de parámetros que MyBatis construye internamente:
public interface PersonaMapper {
Persona buscar();
Persona buscar(@Param("id") Long id);
Persona buscar(@Param("id") Long id, @Param("nombre") String nombre);
}
El archivo XML asociado usa SQL dinámico para adaptarse a los distintos casos:
<select id="buscar" resultMap="PersonaMap">
SELECT id, nombre, edad FROM persona
<where>
<if test="id != null">id = #{id}</if>
<if test="nombre != null and nombre != ''">nombre = #{nombre}</if>
</where>
LIMIT 1
</select>
Internamente, MyBatis encapsula los parámetros en un mapa que incluye tanto los nombres explícitos (como id) como los implícitos (param1, param2, etc.). Si un parámetro referenciado en <if> no existe en el mapa, se lanza una excepción.
Paginación en MyBatis
Existen tres enfoques:
- Paginación en memoria: usando
RowBounds, que filtra resultados tras ejecutar la consulta completa. - Paginación física manual: escribiendo directamente cláusulas
LIMIToROWNUMen el SQL. - Plugins de paginación: interceptan la ejecución SQL y lo reescriben añadiendo la lógica de paginación según el dialecto de la base de datos (por ejemplo, envolviendo la consulta original en una subconsulta con
LIMIT).
Arquitectura de plugins
MyBatis permite interceptar llamadas a cuatro interfaces clave: Executor, StatementHandler, ParameterHandler y ResultSetHandler. Para crear un plugin:
- Implementar
org.apache.ibatis.plugin.Interceptor. - Annotar con
@Interceptsespecificando la interfaz y métodos a interceptar. - Registrar el plugin en el archivo de configuración de MyBatis.
El mecanismo se basa en proxies dinámicos que redirigen las invocaciones al método intercept() del plugin.
Mapeo de resultados
MyBatis soporta dos formas de mapear columnas a propiedades de objetos:
- Usando
<resultMap>: define explícitamente cada correspondencia columna-propiedad. - Usando alias en SQL: al escribir
SELECT nombre_usuario AS nombre, MyBatis asocia automáticamente la columna con la propiedadnombre, ignorando mayúsculas/minúsculas.
Una vez establecido el mapeo, MyBatis crea instancias mediante reflexión y asigna los valores obtenidos del ResultSet.
Consultas relacionales y carga diferida
MyBatis soporta relaciones uno-a-uno y uno-a-muchos mediante:
- Consultas anidadas: múltiples sentencias SQL ejecutadas bajo demanda.
- Joins con desduplicación: una única consulta con
JOIN, donde<id>dentro de<resultMap>identifica registros únicos para evitar duplicados en colecciones.
La carga diferida (lazy loading) está disponible para asociaciones y colecciones. Cuando se accede a una propiedad no cargada, MyBatis usa CGLIB para generar un proxy que ejecuta la consulta faltante justo a tiempo.
Ejecutores (Executors)
MyBatis dispone de tres tipos de ejecutores:
SimpleExecutor: abre y cierra unStatementpor cada operación.ReuseExecutor: reutilizaStatements cacheándolos por SQL.BatchExecutor: acumula operacionesINSERT/UPDATE/DELETEy las envía en lote medianteaddBatch()yexecuteBatch().
El tipo de ejecutor se puede especificar globalmente en la configuración o por sesión al crear un SqlSession.
Otros aspectos relevantes
- Enums: se mapean implementando un
TypeHandlerpersonalizado que convierte entre el tipo Java y el tipo JDBC. - Orden de etiquetas
<sql>: MyBatis realiza dos pasadas de análisis, por lo que los fragmentos referenciados con<include>pueden declararse antes o después de su uso. - Estructura interna: todo el XML se transforma en objetos como
Configuration,MappedStatement,ResultMapyBoundSql. - Semi-automático: a diferencia de Hibernate, MyBatis requiere que el desarrollador escriba el SQL explícitamente, incluso para relaciones, lo que le da mayor control pero menos automatización.