Mybatis创建Mapper接口实现类

文章详细阐述了Mybatis如何通过JDK动态代理为Mapper接口创建实现类,解释了创建代理对象的流程,包括MapperProxy的作用和MapperMethod的执行过程,展示了代理对象如何执行Mapper接口中的方法,最终将SQL语句交由SqlSession处理。


在Mybatis的开发过程中,程序员更加关注 Mapper接口中的方法以及 xxxMapper.xml文件的编写。但是我们仅仅只是写了一个方法名和Sql语句,并且接口是不能被实例化的,那么Mybatis是如何通过 Mapper接口来执行对应的Sql语句呢?其实是在运行过程中Mybatis通过动态代理的方式创建了 Mapper接口的实现类。

示例代码

UserDAOMapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.hzb.dao.UserDAO">

    <resultMap id="BaseResultMap" type="edu.hzb.entity.User">
         <result property="id" column="id"/>
         <result property="name" column="name"/>
         <result property="age" column="age"/>
    </resultMap>

    <sql id="BaseColumn">
        id, name, age
    </sql>

    <select id="selectList" resultMap="BaseResultMap" useCache="true">
        select <include refid="BaseColumn"></include>
        from t_user
    </select>
   
</mapper>

UserMapper接口:

public interface UserMapper {
    List<User> selectList();
}

测试代码:

public void test1() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //Mybatis会通过代理设计模式会为UserDAO接口创建代理实现类
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = userMapper.selectList();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vAOP3Jc-1684657692315)(res/Mybatis创建Mapper接口实现类/image-20230521103126185.png)]

可以看到userMapper运行类型是org.apache.ibatis.binding.MapperProxy@8c3b9duserMapper是一个代理对象,那userMapper.selectList()就是被增强的代理方法。

创建代理对象

JDK动态代理

代理模式主要分为JDK动态代理CGlib动态代理,因为UserMapper是一个接口,所以默认会采用JDK动态代理。

JDK动态代理是通过反射机制生成一个实现代理接口的匿名类,要求目标对象必须是一个接口。
CGLIB动态代理则使用的继承机制,对目标类(父类)进行继承,生成一个代理类(子类),从而对父类中的方法增强。

通过JDK动态代理创建代理对象的方法定义:

/**
	ClassLoader: 动态代理类没有对应的.class文件,JVM也就不会为其分配ClassLoader,所以需要借用一个		                      ClassLoader,用来创建代理类的Class对象
	interfaces: 目标对象实现的接口,这里就是UserMapper接口
	InvocationHandler:对目标对象中的方法进行增强,这里就是对UserMapper接口中的方法增强
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

创建代理对象流程

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

Mybatis是如何通过这行代码给UserMapper接口创建实现类,进入到getMapper(UserMapper.class)方法中:

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

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

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //获取Mapper接口代理工厂
    //knowMappers是一个Map,key为Mapper接口,value就是MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //通过工厂创建MapperProxy对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

进入到mapperProxyFactory.newInstance(sqlSession)方法中

public T newInstance(SqlSession sqlSession) {
  //构建MapperProxy对象
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

在了解newInstance(mapperProxy)方法之前需要先知道MapperProxy对象是什么:

//实现了InvocationHandler接口,实现了该接口中的Invoke方法,在创建代理对象时就会把MapperProxy对象作为参数传入
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  //sqlSession,执行sql语句需要用到
  private final SqlSession sqlSession;
  //目标对象实现的接口,这里就是UserMapper接口
  private final Class<T> mapperInterface;
  //MapperMethod只是对Method进一步封装,添加了SqlCommand和MethodSignature两个属性
  private final Map<Method, MapperMethod> methodCache;
}

public class MapperMethod {
  //c封装了Sql语句的namespace.id值和类型
  private final SqlCommand command;
  //封装了方法的返回值和参数
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
}

创建了MapperProxy对象并且知道了MapperProxy是什么之后,接下来分析newInstance(mapperProxy)方法,进入到该方法中:

protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface 	}, mapperProxy);
}

发现这里就是采用JDK动态代理创建代理对象的方法,mapperProxy就作为InvocationHandler参数传入了。

执行代理方法

List<User> users = userMapper.selectList();

这里的userMapper是代理对象,类型是MapperProxy,而MapperProxy实现了InvocationHandler接口,就一定会实现其中的invoke(Object proxy, Method method, Object[] args)方法。所以最后userMapper调用的其实是invoke方法。进入到该方法中:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    //不会对Object类中的方法进行代理增强
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {
      //也不会对jdk8中的默认方法进行代理增强
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  //只会对Mapper接口中的方法代理
  //获取要执行的方法(MapperMethod只是对Method进行了一层包装)
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //执行Method
  return mapperMethod.execute(sqlSession, args);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z13vGAd0-1684657692316)(res/Mybatis创建Mapper接口实现类/image-20230521154328909.png)]

进入到mapperMethod.execute(sqlSession, args)方法中,就非常直观了:

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
     //执行插入方法
    Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      //执行修改方法
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      //执行删除方法
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      //执行查询方法
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName() 
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

根据这段代码就会发现,不管是增删改查哪一个方法,最后都是交给SqlSession来执行,所以其实下面的代码达到的效果是一样的:

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> users = sqlSession.selectList("edu.hzb.dao.UserDAO.selectList");
<think>好的,用户想了解MyBatisMapper接口的实现原理,特别是动态代理和SQL映射的部分。我需要结合之前提供的引用内容来整理回答。 首先,根据引用[2]和[3],Mapper接口没有实现类MyBatis通过动态代理生成代理对象。这点是关键。用户可能疑惑为什么接口没实现却能调用方法,动态代理就是答案。需要解释动态代理的作用,比如拦截方法调用。 接下来,引用[1]提到MapperProxy类,这是核心类,负责处理代理逻辑。应该详细说明MapperProxy如何拦截方法调用,将其转化为对应的SQL操作。比如,当调用selectAll()方法时,MapperProxy会找到对应的SQL语句并执行。 然后,引用[4]中的示例接口和映射文件需要关联起来。说明MyBatis如何将接口方法与XML中的SQL语句绑定,可能是通过方法名和命名空间的匹配。需要提到接口的全限定名和XML的namespace必须一致,方法名对应SQL的id。 另外,要提到SqlSession的作用,比如SqlSession.getMapper()方法触发代理对象的创建,这里可能需要结合引用[3]中的源码分析部分。 还需要注意动态代理的“非常规”用法,这里指的是不同于常见的代理方式,MyBatis并没有提前编写实现类,而是在运行时动态生成。这部分可以引用引用[2]的内容。 最后,总结整个流程,从调用接口方法到代理对象处理,再到执行SQL,确保用户理解整个过程。同时,生成的相关问题需要覆盖动态代理机制、接口与XML的绑定、性能优化等可能延伸的方向。 检查是否有遗漏点,比如SQL映射的解析过程,或者配置文件的加载机制,但根据用户的问题,可能不需要太深入,保持回答在原理层面即可。</think>### MyBatis Mapper接口实现原理分步解析 #### 1. 动态代理机制 MyBatis通过JDK动态代理为Mapper接口生成代理对象(核心类为$MapperProxy$)。当调用接口方法时,$MapperProxy$会拦截方法调用,并根据方法名和参数解析对应的SQL语句[^2]。 ```java // 简化的代理调用逻辑 public class MapperProxy implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) { // 1. 解析方法对应的SQL命令 // 2. 执行数据库操作 // 3. 返回处理结果 } } ``` #### 2. SQL映射绑定 接口方法与XML映射文件的绑定通过以下方式实现: - **命名空间对应**:XML的`namespace`必须与Mapper接口全限定名一致 - **方法签名匹配**:接口方法名对应XML中SQL语句的`id` - **参数映射**:通过`@Param`注解或参数顺序绑定参数 例如引用[4]中的接口方法: ```xml <!-- XML映射文件 --> <mapper namespace="com.example.UserMapper"> <select id="selectAll" resultType="User"> SELECT * FROM users </select> </mapper> ``` #### 3. 执行流程 1. 通过`SqlSession.getMapper()`获取代理对象 2. 调用接口方法时触发$MapperProxy$的`invoke()`方法 3. 通过$MapperMethod$解析SQL类型(SELECT/UPDATE等) 4. 使用$SqlSession$执行具体数据库操作 5. 结果集通过$TypeHandler$进行类型转换 #### 4. 核心组件关系 $$ \begin{array}{ccc} \text{Mapper接口} & \xrightarrow{\text{动态代理}} & \text{MapperProxy} \\ & & \downarrow \\ \text{XML映射文件} & \xleftarrow{\text{元数据解析}} & \text{Configuration} \end{array} $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值