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.