Unlock MyBatis Mapper Magic with a Custom JDK Dynamic Proxy

This article explains how to create a custom JDK dynamic proxy that automatically maps interface methods to objects, demonstrates the underlying MyBatis mapper implementation, and clarifies why method overloading is prohibited in mapper interfaces.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Unlock MyBatis Mapper Magic with a Custom JDK Dynamic Proxy

1. Custom JDK Dynamic Proxy for Automatic Mapper

Dynamic proxies enhance target methods via interceptor callbacks. The article introduces a "投鞭断流" (stream‑cutting) style proxy that bypasses the original target entirely.

First, define a simple 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 create the mapper interface:

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

Implement a custom InvocationHandler that creates proxy instances and returns a fabricated User object when any mapper method is invoked:

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 toString(), hashCode(), equals()
        return method.invoke(this, args);
      } catch (Throwable t) {
        // ignore
      }
    }
    // "投鞭断流" – directly return a new User
    return new User((Integer) args[0], "zhangsan", 18);
  }
}

Test the proxy:

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());
}

Running the code prints:

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

This demonstrates the low‑level mechanism MyBatis uses for automatic mapper implementation.

2. MyBatis Automatic Mapper Source Analysis

A typical test class obtains a mapper from a SqlSession and calls its methods:

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();
  }
}

The mapper interface looks like:

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

Key MyBatis classes involved:

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

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = 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);
      }
    }
    // 投鞭断流 – delegate to MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  // ...
}
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 show how MyBatis relies on JDK dynamic proxies (the "投鞭断流" technique) to bind interface methods to SQL statements at runtime.

3. Can Mapper Interface Methods Be Overloaded?

Answer: No.

Reason: MyBatis uses the fully‑qualified package+Mapper+method name as a key to locate a unique SQL statement in XML configuration. Overloaded methods would produce identical keys, causing ambiguity, 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.

JavaMyBatisDynamic Proxymapper
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.