Mybatis创建Mapper接口实现类

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


在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");
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值