【MyBatis】执行原理(三):获取代理对象(MapperProxy) 源码分析

通过前两篇的分析,我们已经了解了 SqlSessionFactory,SqlSession 底层的逻辑

String resource = "mybatis-config.xml"; 
InputStream inputStream = Resources. getResourceAsStream(resource); 
// build 最终返回了 SqlSessionFactory 接口的默认实现 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// openSession 最终返回SqlSession接口的一个默认实现DefaultSqlSession 
SqlSession session = sqlSessionFactory.openSession(); 

现在我们已经有一个 DefaultSqlSession 了,下一步工作就是找到 Mapper.xml 里面定义的 Statement ID,才能执行对应的SQL语句。

找到StatementID有两种方式:

  1. 硬编码:直接调用session的方法,在参数里面传入Statement ID

    Blog blog = (Blog) session.selectOne("com.my.mapper.BlogMapper.selectBlogById ", 1);
    

    这种方式属于硬编码,我们没办法知道有多少处调用,修改起来也很麻烦。另一个问题是如果参数传入错误,在编译阶段也是不会报错的,不利于预先发现问题。所以在实际开发中不推荐使用。

  2. 约定:在 MyBatis 后期的版本提供了第二种方式,就是定义一个接口,然后再调用Mapper接口的方法。

    由于我们的接口名称跟 Mapper.xml 的 namespace 是对应的,接口的方法跟statement ID也都是对应的,所以根据方法就能找到对应的要执行的SQL。

    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlogById(1); 
    

我们在实际工作中般都是采用的第二种方式。由于 BlogMapper 只是一个接口,所以很容易想到 SqlSession#getMapper() 会给我们生成一个具体的代理对象,并且实现了 BlogMapper 接口。

下面,我们就来分析 Mapper 对象相关源码…

getMapper()

// 1.DefaultSqlSession#getMapper()
// 调用了 Configuration的getMapper()方法。
public <T> T getMapper(Class<T> type) {
  // 传入 this
  return configuration.getMapper(type, this);
}

// 2.Configuration#getMapper()
// 调用了MapperRegistry 的getMapper()方法。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
 }

// 3.MapperRegister#getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	// 获取MapperProxy工厂
    // 在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到了一个Map中。
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 从相应的 mapperProxyFactory 工厂获取具体对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

newInstance()

// MapperProxyFactory.newInstance()
public T newInstance(SqlSession sqlSession) {
	// MapperProxy 是代理类,它实现了 InvocationHandler 接口
	// 构造代理对象时的入参是:SqlSession,mapper接口,方法缓存
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  
// 通过JDK动态代理生成代理对象
protected T newInstance(MapperProxy<T> mapperProxy) { 
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {mapperInterface }, mapperProxy);
  }

到这里我们可以看到已经生成了代理对象。

问题:我们常常使用的JDK动态代理和MyBatis用到的JDK动态代理有什么区别呢?

我们平常使用JDK动态代理时,需要在实现了InvocationHandler的代理类里面传入(组合)一个被代理对象的实现类,让这个被代理对象去执行接口中的方法。
在这里插入图片描述

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	// 执行接口实现实例的该方法
	method.invoke(ProxyImpl, args);
}

而对于 Mybatis 动态代理,它代理的 Mapper 接口并没有实现类,因为所有 Mapper 接口中的方法是都是交给 SqlSession 的 Excutor 去执行,并不需要一个实现类。

因为我们唯一要做的就是根据接口类型+方法的名称找到StatementID。执行 Mapper 接口中方法的是 SqlSession 的执行器 Executor。

MapperProxy 唯一要做的事情就是根据接口类型(namespace)+ 方法名(sqlId)得到 statementID,然后再调用 SqlSession 的 insert()、delete()、update()、selectOne()、selectList() 等方法去执行。下面 MapperProxy 部分源码:


关于invoke的逻辑,及如何执行sql的放在下一篇去说…

=>总结

获得Mapper对象的过程,实质上是获取了一个 MapperProxy 的代理对象。MapperProxy 中有 sqlSession、mapperInterface、methodCache。

在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis是一款优秀的持久层框架,采用了ORM(对象关系映射)的思想,将Java对象和数据库表结构进行映射,使得操作数据库更加方便和简洁。 MyBatis源码原理主要包括以下几个方面: 1. SqlSessionFactory的创建和初始化:SqlSessionFactory是MyBatis的核心接口,它负责创建SqlSession对象SqlSession是与数据库交互的主要接口。SqlSessionFactory的创建是通过XML或Java代码配置文件来实现的,其包括数据源、事务管理器等。SqlSessionFactory的初始化过程是通过解析配置文件,将配置信息加载到Configuration对象,然后创建SqlSessionFactoryBuilder对象,通过它来构建SqlSessionFactory。 2. SqlSession的创建和生命周期管理:SqlSession是与数据库交互的主要接口,它封装了对数据库的操作,包括查询、插入、更新、删除等。SqlSession的创建是通过SqlSessionFactory来实现的,它采用了工厂模式,通过SqlSessionFactory.openSession()方法获取SqlSession对象SqlSession的生命周期管理包括开启事务、提交事务、回滚事务等操作。 3. Mapper接口的代理实现:Mapper接口是MyBatis的另一个核心接口,它定义了数据库操作的方法,例如查询、插入、更新、删除等。Mapper接口的实现是通过动态代理来实现的,MyBatis会根据Mapper接口的定义生成一个代理类,代理类会拦截Mapper接口的方法调用,并将其转化为对SqlSession调用,最终将操作转化为对数据库的操作。 4. SQL语句的解析和执行MyBatis支持通过XML或注解的方式定义SQL语句,通过SqlSession对象调用Mapper接口的方法时,MyBatis会根据Mapper接口的定义和XML或注解文件解析出对应的SQL语句,然后执行SQL语句,并将结果转化为对应的Java对象。 5. 缓存的实现和管理:MyBatis支持一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,用于缓存在同一个SqlSession多次查询同一条记录的结果。二级缓存是Mapper级别的缓存,用于缓存在多个SqlSession多次查询同一条记录的结果。MyBatis的缓存实现是通过Cache接口来实现的,缓存的管理是通过Configuration对象来实现的。 总的来说,MyBatis源码原理涉及到了很多方面,包括配置文件的解析、动态代理SQL语句的解析和执行、缓存等。了解这些原理有助于更好地理解和使用MyBatis

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A minor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值