Desenvolvimento de DAO Tradicional e Uso de Proxy Mapper no MyBatis

Componentes Fundamentais do MyBatis

Antes de explorar as estratégias de acesso a dados, é crucial compreender o ciclo de vida e a responsabilidade dos objetos centrais do framework:

  • SqlSessionFactoryBuilder: Atua como uma classe utilitária para construir a fábrica de sessões. Não requer gerenciamento via Singleton; deve ser instanciada, utilizada para criar a fábrica e, em seguida, descartada.
  • SqlSessionFactory: Responsável por gerar instâncias de SqlSession. Recomenda-se fortemente o uso do padrão Singleton para este objeto em toda a aplicação. Em ambientes integrados com Spring, o próprio container gerencia seu ciclo de vida como um bean único.
  • SqlSession: Interface principal para interação com o banco de dados, contendo métodos de execução de comandos SQL. Como possui atributos de estado interno, não é thread-safe. A melhor prática arquitetural é instnaciá-la como variável local dentro de métodos de serviço e fechá-la imediatamente após a execução.

Abordagem de DAO Tradicional

No modelo tradicional, o desenvolvedor é responsável por definir a interface de acesso a dados e prover uma implemantação manual, lidando explicitamente com a infraestrutura do MyBatis.

Definição da Interface


package com.sistema.persistencia.dao;

import java.util.List;
import com.sistema.modelo.Cliente;

public interface ClienteDao {
    Cliente buscarPorId(int id) throws Exception;
    List<Cliente> buscarPorNome(String nome) throws Exception;
    void cadastrar(Cliente cliente) throws Exception;
    void excluir(int id) throws Exception;
}

Implementação Manual


package com.sistema.persistencia.dao.impl;

import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import com.sistema.modelo.Cliente;
import com.sistema.persistencia.dao.ClienteDao;

public class ClienteDaoImpl implements ClienteDao {
    
    private final SqlSessionFactory sessionFactory;

    // Injeção de dependência via construtor
    public ClienteDaoImpl(SqlSessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    public Cliente buscarPorId(int id) throws Exception {
        try (SqlSession session = sessionFactory.openSession()) {
            // Hardcoding do identificador do statement
            return session.selectOne("statements.ClienteMapper.buscarPorId", id);
        }
    }

    @Override
    public List<Cliente> buscarPorNome(String nome) throws Exception {
        try (SqlSession session = sessionFactory.openSession()) {
            return session.selectList("statements.ClienteMapper.buscarPorNome", nome);
        }
    }

    @Override
    public void cadastrar(Cliente cliente) throws Exception {
        try (SqlSession session = sessionFactory.openSession()) {
            session.insert("statements.ClienteMapper.cadastrar", cliente);
            session.commit();
        }
    }

    @Override
    public void excluir(int id) throws Exception {
        try (SqlSession session = sessionFactory.openSession()) {
            session.delete("statements.ClienteMapper.excluir", id);
            session.commit();
        }
    }
}

Limitações do Modelo Tradicional

  1. Código Boilerplate: Abertura, commit e fechamento de sessões são repetidos exaustivamente em cada método.
  2. Acoplamento por Strings: Os identificadores dos mapeamentos XML são passados como strings fixas, dificultando a refatoração e a manutenção.
  3. Falta de Segurança de Tipos: Como os métodos da SqlSession aceitam Object como parâmetro, erros de tipagem nos argumentos não são detectados pelo compilador, gerando falhas apenas em tempo de execução.

Desenvolvimento com Proxy de Interface Mapper

Para mitigar os problemas do DAO tradicional, o MyBatis introduziu o mecanismo de proxy dinâmico. O framework gera auotmaticamente a implementação da interface em tempo de execução. Para que a injeção do proxy funcione, quatro regras de contrato devem ser estritamente obedecidas:

  1. O atributo namespace no arquivo XML de mapeamento deve ser idêntico ao caminho completo (fully qualified name) da interface Java.
  2. O nome do método declarado na interface deve corresponder exatamente ao id da tag SQL no XML.
  3. O tipo do parâmetro de entrada do método Java deve alinhar-se com o parameterType definido no XML.
  4. O tipo de retorno do método Java deve coincidir com o resultType (ou resultMap) especificado.

Interface Mapper


package com.sistema.persistencia.mapper;

import java.util.List;
import com.sistema.modelo.Cliente;

public interface ClienteMapper {
    Cliente buscarPorId(int id) throws Exception;
    List<Cliente> buscarPorNome(String nome) throws Exception;
    void cadastrar(Cliente cliente) throws Exception;
    void excluir(int id) throws Exception;
}

Arquivo de Mapeamento XML


<?xml version="1.0" encoding="UTF-8" ?>


<mapper namespace="com.sistema.persistencia.mapper.ClienteMapper">
    
    <select id="buscarPorId" parameterType="int" resultType="com.sistema.modelo.Cliente">
        SELECT id, nome_completo, email, data_registro 
        FROM clientes 
        WHERE id = #{id}
    </select>
    
    <select id="buscarPorNome" parameterType="string" resultType="com.sistema.modelo.Cliente">
        SELECT id, nome_completo, email, data_registro 
        FROM clientes 
        WHERE nome_completo LIKE CONCAT('%', #{nome}, '%')
    </select>
    
    <insert id="cadastrar" parameterType="com.sistema.modelo.Cliente">
        <selectKey keyProperty="id" order="AFTER" resultType="int">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO clientes (nome_completo, email, data_registro) 
        VALUES (#{nomeCompleto}, #{email}, #{dataRegistro})
    </insert>
    
    <delete id="excluir" parameterType="int">
        DELETE FROM clientes WHERE id = #{id}
    </delete>

</mapper>

O arquivo XML deve ser registrado na configuração global do MyBatis:


<mappers>
    <mapper resource="mapper/ClienteMapper.xml"/>
</mappers>

Testando o Proxy Mapper


package com.sistema.persistencia.mapper;

import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.sistema.modelo.Cliente;

public class ClienteMapperTest {

    private SqlSessionFactory sessionFactory;

    @BeforeEach
    public void configurarAmbiente() throws Exception {
        String config = "mybatis-config.xml";
        InputStream stream = Resources.getResourceAsStream(config);
        sessionFactory = new SqlSessionFactoryBuilder().build(stream);
    }
    
    @Test
    public void validarBuscaPorId() throws Exception {
        try (SqlSession session = sessionFactory.openSession()) {
            // O MyBatis gera a implementação da interface dinamicamente
            ClienteMapper mapper = session.getMapper(ClienteMapper.class);
            
            Cliente cliente = mapper.buscarPorId(10);
            System.out.println("Cliente encontrado: " + cliente.getNomeCompleto());
        }
    }
    
    @Test
    public void validarBuscaPorNome() throws Exception {
        try (SqlSession session = sessionFactory.openSession()) {
            ClienteMapper mapper = session.getMapper(ClienteMapper.class);
            
            List<Cliente> resultados = mapper.buscarPorNome("Silva");
            resultados.forEach(c -> System.out.println(c.getEmail()));
        }
    }
}

Comportamento Interno e Boas Práticas

Resolução de Consultas (Select):
O proxy gerado pelo MyBatis analisa a assinatura do método na interface para determinar a operação interna. Se o tipo de retorno for um objeto único (POJO), o framework roteia a chamada para o método selectOne da SqlSession. Caso o retorno seja uma coleção (como List ou Set), a execução é delegada ao selectList.

Gerenciamento de Múltiplos Parâmetros:
Por padrão, os métodos da interface Mapper aceitam apenas um parâmetro. Quando uma consulta exige múltiplos filtros, a solução arquitetural padrão é encapsular esses critérios em um objeto de transferência (DTO/POJO) ou utilizar um Map<String, Object>.
Nota de Design: Embora a camada de persistência possa depender de objetos de consulta específicos, é altamente recomendável que a camada de Serviço (Service) mantenha uma assinatura baseada em tipos primitivos ou objetos de domínio genéricos, convertendo-os para o DTO do Mapper internamente. Isso evita o acoplamento da lógica de negócios às estruturas do framework de persistência.

Tags: MyBatis DAO java persistência SqlSession

Publicado em 6-17 23:21