Mybatis中sqlSession.getMapper背后的原理

在通过MyBatis操作数据库之前我们一定先通过Session对象获取指定Mappper接口的代理对象。如下代码所示:

public class UserMapper{
  @Select(value="SELECT * FROM user")
  public List<User> findAll();
}

public static void main(String [] args){

   Configuration configuration = new Configuration();
   configuration.addMapper(UserMapper.class);
   SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);

   // 获取SqlSession对象
   try(SqlSession sqlSession = sqlSessionFactory.openSession();){
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.findAll();
       }catch(Exception e){
       //TODO 异常信息
       }
}

调用链

1.DefaultSqlSession.调用getMapper方法 -> 2.Configuration.调用getMapper方法 -> 3.MapperRegistry调用getMapper方法
                                                                                        |
                                                                                        |
  |-------------------------------------------------------------------------------------|
  |
  |
  |-->4.判断knownMappers缓存是否已经解析过指定的Mapper接口,没解析过抛错(在调用addMapper方法时就将接口添加到缓存中并对接口中的方法进行解析).解析过创建代理对象
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

        if (Objects.isNull(mapperProxyFactory)){
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }

        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
  |
  |
  |-->  5.创建代理对象,MapperProxy实现了InvocationHandler接口,使用JDK动态代理创建
        public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
        protected T newInstance(MapperProxy<T> mapperProxy) {
        // 用JDK自带的动态代理生成映射器
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

注意第5代逻辑:

   // 已解析过的Mapper接口及对应的代理工厂缓存
   private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();


  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (Objects.isNull(mapperProxyFactory)) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }

    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

先从缓存中获取接口对应的代理工厂,并判断是否存储代理工厂。不存在时抛错,表示此接口没有注册过无法创建代理对象.
何时向缓存中添加接口及对应的代理工厂呢?如下代码:

  public <T> void addMapper(Class<T> type) {
 // mapper必须是接口!才会添加
 if (type.isInterface()) {
   if ( knownMappers.containsKey((type)) {
     // 如果重复添加了,报错
     throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
   }

   boolean loadCompleted = false;

   try {
     // 将mapper接口放入缓存并为其创建独立的mapper接口代理对象工厂
     // 每个Mapper接口有一个独立的代理工厂
     knownMappers.put(type, new MapperProxyFactory<T>(type));

     // 在运行分析器之前添加类型非常重要
     // 否则,绑定可能会由
     // 映射器解析器。如果类型已知,则不会尝试。
     MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
     // 解析mapper接口及相应的mapper.xml文件
     parser.parse();
     loadCompleted = true;
   } finally {
     // 如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
     if (!loadCompleted) {
       knownMappers.remove(type);
     }
   }
 }
}

在注册Mapper 接口时,会向缓存中添加接口对应的代理工厂,以便后面通过接口获取代理对象调用方法。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值