SQL Dinâmico no MyBatis: Manipulação Flexível de Consultas

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:

  • if
  • choose (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>

Tags: MyBatis SQL java OGNL Persistence-Framework

Publicado em 7-23 08:07