Análise Interna do MyBatis: Funcionamento do MapperRegistry e Binding

O módulo de binding no MyBatis é o responsável por conectar as interfaces de Mapper definidas pelo desenvolvedor com a execução real das instruções SQL. Localizado no pacote org.apache.ibatis.binding, este componente utiliza intensamente o padrão Proxy do Java para permitir que chamadas a métodos de interface sejam traduzidsa em operações de banco de dados.

1. Estrutura do MapperRegistry

O MapperRegistry atua como o repositório central onde todas as interfaces de mapeamento são registradas. Ele é gerenciado pela classe Configuration e mantém um mapa interno relacionando a interface do Mapper à sua respectiva fábrica de proxies.


public class MapperRegistry {
  private final Configuration config;
  // Mapa que associa a interface ao seu gerador de proxy
  private final Map<Class<?>, MapperProxyFactory<?>> mappersRegistrados = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T obterMapper(Class<T> tipo, SqlSession session) {
    final MapperProxyFactory<T> fabrica = (MapperProxyFactory<T>) mappersRegistrados.get(tipo);
    if (fabrica == null) {
      throw new BindingException("O tipo " + tipo + " não foi registrado no MapperRegistry.");
    }
    try {
      return fabrica.criarInstancia(session);
    } catch (Exception e) {
      throw new BindingException("Erro ao instanciar o proxy do mapper: " + e, e);
    }
  }

  public <T> void adicionarMapper(Class<T> tipo) {
    if (tipo.isInterface()) {
      if (mappersRegistrados.containsKey(tipo)) {
        throw new BindingException("Mapper " + tipo + " já existe.");
      }
      boolean processado = false;
      try {
        mappersRegistrados.put(tipo, new MapperProxyFactory<T>(tipo));
        
        // O parser de anotações deve rodar antes para evitar binds automáticos incompletos
        MapperAnnotationBuilder builder = new MapperAnnotationBuilder(config, tipo);
        builder.parse();
        processado = true;
      } finally {
        if (!processado) {
          mappersRegistrados.remove(tipo);
        }
      }
    }
  }
}

O método adicionarMapper garante que apenas interfaces sejam aceitas. Ele armazena uma MapperProxyFactory, que será usada posteriormente para criar instâncias dinâmicas sempre que uma SqlSession solicitar um Mapper.

2. MapperProxyFactory: A Fábrica de Proxies

Esta classe simplifica a criação de instâncias de MapperProxy utilizando a API de Proxy Dinâmico do JDK. Ela também mantém um cache de métodos para otimizar o desempenho.


public class MapperProxyFactory<T> {
  private final Class<T> interfaceMapper;
  private final Map<Method, MapperMethod> cacheDeMetodos = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> interfaceMapper) {
    this.interfaceMapper = interfaceMapper;
  }

  @SuppressWarnings("unchecked")
  protected T instanciarProxy(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(
        interfaceMapper.getClassLoader(), 
        new Class[] { interfaceMapper }, 
        mapperProxy);
  }

  public T criarInstancia(SqlSession sqlSession) {
    final MapperProxy<T> proxyHandler = new MapperProxy<>(sqlSession, interfaceMapper, cacheDeMetodos);
    return instanciarProxy(proxyHandler);
  }
}

3. MapperProxy: O Coração do Desvio de Chamadas

O MapperProxy implementa InvocationHandler. Cada vez que um método da interface Mapper é invocado, o fluxo passa pelo método invoke desta classe. Ela decide se deve executar o método diretamente (caso seja um método herdado de Object) ou se deve processá-lo como uma instrução SQL via MapperMethod.


public class MapperProxy<T> implements InvocationHandler, Serializable {
  private final SqlSession sqlSession;
  private final Class<T> interfaceMapper;
  private final Map<Method, MapperMethod> cacheMetodos;

  public MapperProxy(SqlSession session, Class<T> interface, Map<Method, MapperMethod> cache) {
    this.sqlSession = session;
    this.interfaceMapper = interface;
    this.cacheMetodos = cache;
  }

  @Override
  public Object invoke(Object proxy, Method metodo, Object[] args) throws Throwable {
    if (Object.class.equals(metodo.getDeclaringClass())) {
      return metodo.invoke(this, args);
    }
    final MapperMethod mapperMethod = buscarNoCache(metodo);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod buscarNoCache(Method metodo) {
    return cacheMetodos.computeIfAbsent(metodo, 
        m -> new MapperMethod(interfaceMapper, m, sqlSession.getConfiguration()));
  }
}

4. MapperMethod e a Execução de Comandos SQL

O MapperMethod é onde a lógica de negócio do MyBatis realmente acontece. Ele traduz a chamada do método Java em uma operação de SqlSession (INSERT, UPDATE, DELETE ou SELECT). Ele utiliza duas classes auxiliares internas: SqlCommand (identifica o ID e o tipo do SQL) e MethodSignature (analisa o retorno e os parâmetros do método).

O Fluxo de Execução

O método execute mapeia a intenção do comando para a chamada correta na sessão:


public Object execute(SqlSession session, Object[] args) {
  Object resultado;
  switch (comando.getType()) {
    case INSERT: {
      Object param = assinatura.converterArgs(args);
      resultado = processarCount(session.insert(comando.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = assinatura.converterArgs(args);
      resultado = processarCount(session.update(comando.getName(), param));
      break;
    }
    case DELETE: {
      Object param = assinatura.converterArgs(args);
      resultado = processarCount(session.delete(comando.getName(), param));
      break;
    }
    case SELECT:
      if (assinatura.retornaVoid() && assinatura.temResultHandler()) {
        executarComHandler(session, args);
        resultado = null;
      } else if (assinatura.retornaMuitos()) {
        resultado = executarLista(session, args);
      } else {
        Object param = assinatura.converterArgs(args);
        resultado = session.selectOne(comando.getName(), param);
      }
      break;
    default:
      throw new BindingException("Comando desconhecido: " + comando.getName());
  }
  return resultado;
}

5. Gestão de Parâmetros com MethodSignature

Um dos desafios do MyBatis é converter os argumentos posicionais de um método Java em parâmetros nomeados que o XML do SQL possa entender. A classe MethodSignature resolve isso criando um mapa. Se houver apenas um parâmetro e nenhuma anotação @Param, ele o passa diretamente. Se houver múltiplos, ele gera nomes padrão como param1, param2 ou utiliza os nomes definidos nas anotações.

Além disso, o MyBatis ignora automaticamente parâmetros do tipo RowBounds (usado para paginação em memória) e ResultHandler durante a conversão para o mapa de parâmetros SQL, tratando-os como metadados de execução.

Este design modular permite que o MyBatis seja extremamente flexível, suportando desde interfaces simples até mapeamentos complexos com cache de metadados para garantir que a sobrecarga de reflexão seja minimizada.

Tags: MyBatis java Dynamic Proxy design patterns SQL Session

Publicado em 9-26 05:18