Concepto de SQL Dinámico
El SQL dinámico implica la generación de consultas SQL en tiempo de ejecución según condiciones variables. MyBatis proporciona etiquetas como <if>, <choose>, <where> y <foreach> para simplificar la creación de estas consultas.
Etiquetas de SQL Dinámico en MyBatis
Etiqueta <if>
La etiqueta <if> incluye fragmentos de SQL de forma condicional basándose en expresiones evaluadas.
<select id="buscarClientePorFiltros" parameterType="map" resultType="Cliente">
SELECT * FROM clientes
WHERE 1=1
<if test="apellido != null">
AND apellido = #{apellido}
</if>
<if test="ciudadId != null">
AND ciudad_id = #{ciudadId}
</if>
</select>
Etiquetas <choose>, <when> y <otherwise>
Estas etiquetas permiten seleccionar una opción entre múltiples condiciones, similar a una estructura switch en Java.
<select id="buscarProductoPorPrioridad" parameterType="map" resultType="Producto">
SELECT * FROM productos
WHERE 1=1
<choose>
<when test="categoria != null">
AND categoria = #{categoria}
</when>
<when test="precioMax != null">
AND precio <= #{precioMax}
</when>
<otherwise>
AND estado = 'disponible'
</otherwise>
</choose>
</select>
Etiquetas <trim>, <where> y <set>
Estas etiquetas ajustan dinámicamente la sintaxis SQL para eliminar operadores redundantes como AND o comas.
<select id="buscarEmpleadosPorCriterios" parameterType="map" resultType="Empleado">
SELECT * FROM empleados
<where>
<if test="nombreCompleto != null">
AND nombre_completo LIKE CONCAT('%', #{nombreCompleto}, '%')
</if>
<if test="salarioMin != null">
AND salario >= #{salarioMin}
</if>
</where>
</select>
Etiqueta <foreach>
La etiqueta <foreach> itera sobre colecciones para generar cláusulas IN o instrucciones de inserción masiva.
<select id="buscarOrdenesPorIds" parameterType="list" resultType="Orden">
SELECT * FROM ordenes
WHERE id IN
<foreach item="ordenId" index="idx" collection="listaIds" open="(" separator="," close=")">
#{ordenId}
</foreach>
</select>
Ejemplos Prácticos
Ejemplo 1: Consulta Dinámica de Empleados
Interfaz del Mapper:
public interface EmpleadoMapper {
List<Empleado> buscarPorFiltros(Map<String, Object> filtros);
}
Archivo XML del Mapper:
<mapper namespace="com.ejemplo.mapper.EmpleadoMapper">
<select id="buscarPorFiltros" parameterType="map" resultType="Empleado">
SELECT * FROM empleados
<where>
<if test="nombre != null">
AND nombre = #{nombre}
</if>
<if test="departamento != null">
AND departamento = #{departamento}
</if>
</where>
</select>
</mapper>
Llamada desde el Servicio:
@Service
public class EmpleadoServicio {
@Autowired
private EmpleadoMapper empleadoMapper;
public List<Empleado> obtenerEmpleados(String nombre, String departamento) {
Map<String, Object> parametros = new HashMap<>();
parametros.put("nombre", nombre);
parametros.put("departamento", departamento);
return empleadoMapper.buscarPorFiltros(parametros);
}
}
Ejemplo 2: Inserción Masiva de Productos
Interfaz del Mapper:
public interface ProductoMapper {
void insertarVarios(List<Producto> productos);
}
Archivo XML del Mapper:
<mapper namespace="com.ejemplo.mapper.ProductoMapper">
<insert id="insertarVarios" parameterType="list">
INSERT INTO productos (nombre, precio, stock)
VALUES
<foreach collection="listaProductos" item="producto" separator=",">
(#{producto.nombre}, #{producto.precio}, #{producto.stock})
</foreach>
</insert>
</mapper>
Llamada desde el Servicio:
@Service
public class ProductoServicio {
@Autowired
private ProductoMapper productoMapper;
public void registrarProductos(List<Producto> productos) {
productoMapper.insertarVarios(productos);
}
}
Mejores Prácticas
Utiliza el SQL dinámico con moderación para mantener la legibilidad y mantenibilidad del código. Emplea siempre enlaces de parámetros con #{param} en lugar de concatenación directa para prevenir inyecciones SQL. Realiza pruebas exhaustivas y habilita el registro de MyBatis para depurar las consultas generadas.