MyBatis Source Code Analysis: Mapper Proxy Mechanism

In earlier versions of MyBatis, developers could invoke database operations directly through the DefaultSqlSession's selectOne() method using the StatementID defined in Mapper XML files. This approach, while functional, presented significant maintainability challenges. The StatementID was specified as a hardcoded string constant, making refactoring difficult and error-prone. Furthermore, parameter type mismatches would only surface at runtime rather than during compilation, potentially allowing bugs to slip into production environments.

User user = (User) sqlSession.selectOne("com.example.mapper.UserMapper.selectUserById", 1);

Recognizing these limitations, MyBatis introduced a more elegant solution in subsequent releases. The framework began supporting interface-based Mapper definitions, where developers define Java interfaces that correspond to the SQL operations defined in XML files. By aligning the interface name with the XML namespace and matching method signatures with statement IDs, MyBatis creates a type-safe, refactor-friendly approach to database access.

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);

Core Questions

This interface-based approach naturally raises two important implementation questions. First, what type of object does getMapper() actually return, and how is it capable of executing method calls? Second, through what mechanism does MyBatis correlate an interface method invocation with the corresponding SQL statement defined in an XML mapper file?

The getMapper() Method Chain

The getMapper() method implementasion follows a layered delegation pattern across multiple components. When invoked on a DefaultSqlSession instance, the call immediately routes to the Configuration object, wich serves as the central repository for all MyBatis configuration metadata.

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return configuration.getMapper(type, sqlSession);
}

The Configuration class further delegates this request to the MapperRegistry, which maintains the registry of all known Mapper interfaces and their associated factories. The MapperRegistry serves as the central authority for creating proxy instances for all registered Mapper interfaces.

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> factory = knownMappers.get(type);
    if (factory == null) {
        throw new BindingException("Unknown mapper type: " + type);
    }
    try {
        return factory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Failed to create mapper instance", e);
    }
}

During the XML parsing phase, MyBatis registers each discovered Mapper interface along with a corresponding proxy factory instance in an internal map. When getMapper() is called, the framework retrieves the appropriate factory and invokes its newInstance() meethod to generate the proxy object.

Creating the Mapper Proxy

The actual proxy creation occurs through the MapperProxyFactory class. Its newInstance() method performs three key operations: it instantiates a MapperProxy object as the invocation handler, then uses Java's dynamic proxy mechanism to generate the final proxy instance.

public <T> T newInstance(SqlSession sqlSession) {
    MapperProxy<T> handler = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return createProxy(handler);
}

protected <T> T createProxy(MapperProxy<T> handler) {
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class<?>[] { mapperInterface };
    return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
}

The MapperProxy class implements Java's InvocationHandler interface, serving as the central dispatch mechanism for all method invocations on the proxy object. It maintains three critical pieces of state: a reference to the SqlSession for executing database operations, the target Mapper interface class, and a cache of Method objects paired with their invokers.

Why Proxy Pattern and Factory Pattern Matter

The factory pattern here serves a crucial architectural purpose. By decoupling the proxy creation logic from the client code, MyBatis centralizes the complex proxy instantiation process while maintaining flexibility for future extensions. The factory encapsulates all the knowledge required to construct the proxy, including the initialization of the MapperProxy with its required dependencies.

The dynamic proxy approach eliminates the need for developers to write implementation classes for their Mapper interfaces. Traditional JDK dynamic proxy requires an existing interface implementation, but MyBatis' design circumvents this requirement by leveraging the XML mapper configuration as the implementation source. When a method on the proxy is invoked, the MapperProxy intercepts the call, determines the corresponding SQL statement from the interface and method signatures, executes the statement through the SqlSession, and returns the results.

Summary

Obtaining a Mapper object through getMapper() results in a JDK dynamic proxy instance. This proxy class extends Java's Proxy class while implementing the target interface, internally maintaining a reference to a MapperProxy invocation handler. The handler manages the critical mapping between interface methods and XML statement identifiers, enabling MyBatis to execute the appropriate SQL for each method invocation.

Tags: MyBatis java Mapper dynamic-proxy sqlsession

Posted on Mon, 18 May 2026 18:03:21 +0000 by bidnshop