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.