How Dynamic Proxies Power MyBatis Auto‑Mapper: A Deep Dive into “投鞭断流”

This article explains how Java’s dynamic proxy mechanism, dubbed “投鞭断流”, can be used to implement MyBatis’s automatic mapper functionality, providing step‑by‑step code examples for custom InvocationHandler, proxy creation, and discusses why method overloading is prohibited in mapper interfaces.

Programmer DD
Programmer DD
Programmer DD
How Dynamic Proxies Power MyBatis Auto‑Mapper: A Deep Dive into “投鞭断流”

Dynamic proxies work by intercepting method calls and enhancing the target method. Beyond simple enhancement, the "投鞭断流" approach can even bypass the target entirely, creating a sci‑fi‑like mode.

1. Custom JDK Dynamic Proxy for Automatic Mapper

First, define a POJO:

public class User {
    private Integer id;
    private String name;
    private int age;
    public User(Integer id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    // getter setter
}

Then define the mapper interface:

public interface UserMapper {
    User getUserById(Integer id);
}

Create an InvocationHandler that implements the "投鞭断流" logic:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MapperProxy implements InvocationHandler {
    @SuppressWarnings("unchecked")
    public <T> T newInstance(Class<T> clz) {
        return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                // methods like hashCode(), toString(), equals()
                return method.invoke(this, args);
            } catch (Throwable t) {
                // ignore
            }
        }
        // 投鞭断流: directly return a User instance
        return new User((Integer) args[0], "zhangsan", 18);
    }
}

Test code:

public static void main(String[] args) {
    MapperProxy proxy = new MapperProxy();
    UserMapper mapper = proxy.newInstance(UserMapper.class);
    User user = mapper.getUserById(1001);
    System.out.println("ID:" + user.getId());
    System.out.println("Name:" + user.getName());
    System.out.println("Age:" + user.getAge());
    System.out.println(mapper.toString());
}

Output:

ID:1001
Name:zhangsan
Age:18
x.y.MapperProxy@6bc7c054

This demonstrates the underlying principle of MyBatis’s automatic mapper implementation.

2. MyBatis Automatic Mapper Source Analysis

Example test class:

public static void main(String[] args) {
    SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
    try {
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = studentMapper.findAllStudents();
        for (Student student : students) {
            System.out.println(student);
        }
    } finally {
        sqlSession.close();
    }
}

Mapper interface example:

public interface StudentMapper {
    List<Student> findAllStudents();
    Student findStudentById(Integer id);
    void insertStudent(Student student);
}

Key parts of org.apache.ibatis.binding.MapperProxy:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    // ...
}

Key parts of org.apache.ibatis.binding.MapperProxyFactory:

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
}

These snippets illustrate how MyBatis leverages dynamic proxies with the "投鞭断流" technique.

3. Can Mapper Interface Methods Be Overloaded?

Example of attempted overload:

public User getUserById(Integer id);
public User getUserById(Integer id, String name);

Answer: No. MyBatis uses the fully qualified name package+Mapper+method as a key to locate a unique SQL statement. Overloaded methods would cause key collisions, so MyBatis forbids method overloading in mapper interfaces.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javabackend-developmentMyBatisDynamic Proxymapper
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.