Simplificando Condições IN com @Select no MyBatis

Ao migrar recentemente do JPA para o MyBatis utilizando configuração sem XML e anotações @Select diretamente nas interfaces, deparei-me com a complexidade na implementação de condições IN.

A abordagem tradicional exige uma sintaxe bastante verbosa:

@Select({"<script>",
         "SELECT *", 
         "FROM artigo",
         "WHERE id IN", 
           "<foreach item='item' index='index' collection='lista'",
             "open='(' separator=',' close=')'>",
             "#{item}",
           "</foreach>",
         "</script>"}) 
List<Artigo> buscarArtigos(@Param("lista") int[] ids);

Esta sintaxe parece pouco intuitiva, especialmente quando comparada com a simplicidade do JPA+Hibernate na escrita de HQL. Apesar de ser um problema comum, encontrei uma solução elegante para simplificar o código.

Inicialmente, minha implementação apresentava esta estrutura:

    @Select(
        "select distinct(ID_USUARIO) from REGISTRO_ACESSO " +
        "where ID_USUARIO IN (#{idsUsuarios}) " +
        "and ID_TENANT = #{idTenant} " +
        "and DATA_CRIACAO between #{dataInicio} and #{dataFim}")
    List<Long> buscarPorPeriodo(
        @Param("idsUsuarios") long[] idsUsuarios, @Param("idTenant") long idTenant,
        @Param("dataInicio") Date dataInicio, @Param("dataFim") Date dataFim
    );

No antanto, esta abordagem resultou em falha de análise. Após pesquisa, identifiquei que a solução residia na criação de um processador sintático personalizado.

Adicionei o seguinte processsador na classe base do Mapper:

public interface MapperBase<T> extends BaseMapper<T> {

    /**
     * Resolve o problema da sintaxe complexa em condições IN do MyBatis
     */
    class ManipuladorInCondicional extends XMLLanguageDriver
            implements LanguageDriver {
        private final Pattern padraoIn = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
        public SqlSource createSqlSource(Configuration configuracao, String script, Class<?> tipoParametro) {
            Matcher combinador = padraoIn.matcher(script);
            if (combinador.find()) {
                script = combinador.replaceAll("(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
            }
            script = "<script>" + script + "</script>";
            return super.createSqlSource(configuracao, script, tipoParametro);
        }
    }


Em seguida, apliquei este processador utilizando a anotação @Lang:

    @Lang(ManipuladorInCondicional.class)
    @Select(
        "select distinct(ID_USUARIO) from REGISTRO_ACESSO " +
        "where ID_USUARIO IN (#{idsUsuarios}) " +
        "and ID_TENANT = #{idTenant} " +
        "and DATA_CRIACAO between #{dataInicio} and #{dataFim}")
    List<Long> buscarPorPeriodo(
        @Param("idsUsuarios") long[] idsUsuarios, @Param("idTenant") long idTenant,
        @Param("dataInicio") Date dataInicio, @Param("dataFim") Date dataFim
    );

Com esta implementação, o código tornou-se funcional e mais legível, resolvendo o problema das condições IN no MyBatis de forma elegante.

Tags: MyBatis SQL in-conditions java Anotações

Publicado em 6-16 02:56 por Thomas