O SQL dinâmico é um dos recursos mais poderosos do ecosistema MyBatis. Para desenvolvedores que já lidaram com o JDBC puro, a tarefa de concatenar strings para criar consultas baseadas em filtros opcionais é uma fonte constante de erros, como espaços ausentes ou vírgulas mal posicionadas. O MyBatis resolve esse problema através de uma linguagem de marcação baseada em XML e expressões OGNL (Object-Graph Navigation Language).
Os principais elementos utilizados para construir essas instruções são:
ifchoose(when,otherwise)trim(where,set)foreach
O Elemento if
A tag <if> permite incluir condicionalmente uma parte da consulta SQL baseando-se no valor de um parâmetro.
Uso com Tipos Numéricos
Para campos numéricos, geralmente verifica-se a nulidade ou valores específicos:
<select id="buscarProdutos" resultType="Produto">
SELECT * FROM tb_produtos
<where>
<if test="precoMinimo != null">
AND preco >= #{precoMinimo}
</if>
<if test="categoriaId != null and categoriaId gt 0">
AND fk_categoria = #{categoriaId}
</if>
</where>
</select>
Uso com Strings
Ao trabalhar com Strings, é comum verificar se o campo não é nulo ou uma string vazia. O MyBatis utiliza operadores lógicos como and, or e neq.
<if test="nomeProd != null and nomeProd != ''">
AND nome LIKE CONCAT('%', #{nomeProd}, '%')
</if>
Também é possível invocar métodos da classe String diretamente na expressão test:
<!-- Verificar se começa com um prefixo específico -->
<if test="skuCode != null and skuCode.startsWith('BR')">
AND status = 'NACIONAL'
</if>
<!-- Comparação exata de strings -->
<if test="filtro != null and 'ativo'.equals(filtro)">
AND flag_ativo = 1
</if>
Coleções e Mapas
Para listas (List), podemos verificar o tamanho ou se a coleção está vazia antes de proceder com a lógica SQL:
<if test="listaIds != null and !listaIds.isEmpty()">
AND id IN
<foreach item="item" collection="listaIds" open="(" separator="," close=")">
#{item}
</foreach>
</if>
O Elemento trim
A tag <trim> é altamante customizável e serve para remover ou adicionar prefixos e sufixos de forma inteligente. Ela é a base para o funcionamento das tags <where> e <set>.
Atributos principais:
- prefix: Conteúdo a ser inserido no início do bloco se ele não estiver vazio.
- suffix: Conteúdo a ser inserido no final do bloco.
- prefixOverrides: Remove strings específicas caso elas apareçam no início do bloco gerado.
- suffixOverrides: Remove strings específicas caso elas apareçam no final do bloco gerado.
Exemplo Prático: Insert Dinâmico
Considere uma operação de inserção onde nem todos os campos do objeto podem estar preenchidos. O uso do trim evita erros de sintaxe com vírgulas:
<insert id="inserirRegistro" parameterType="com.exemplo.model.Logs">
INSERT INTO tb_logs
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="idLog != null">
id_log,
</if>
<if test="usuario != null">
nm_usuario,
</if>
<if test="dataCriacao != null">
dt_criacao,
</if>
<if test="descricao != null">
ds_evento,
</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="idLog != null">
#{idLog},
</if>
<if test="usuario != null">
#{usuario},
</if>
<if test="dataCriacao != null">
#{dataCriacao},
</if>
<if test="descricao != null">
#{descricao},
</if>
</trim>
</insert>
Neste cenário, se apenas idLog e usuario forem fornecidos, o MyBatis gerará o SQL INSERT INTO tb_logs (id_log, nm_usuario) VALUES (?, ?). Sem o suffixOverrides=",", o SQL conteria uma vírgula sobrando antes do fechamento dos parênteses, resultando em erro de execução.
Comparação de Tipos em Expressões Test
Um ponto de atenção importante é a comparação de Strings literais. Em alguns contextos OGNL, comparar uma String com um caractere único pode causar confusão de tipos. A prática recomendada para garantir a comparação correta entre tipos não numéricos é converter o literal explicitamente:
<!-- Garante que a comparação seja tratada como String e não como char -->
<if test="statusFiltro == 'A'.toString()">
AND ativo = 1
</if>