• Mybatis源码分析之Mapper的创建和获取


    Mybatis我们一般都是和Spring一起使用的,它们是怎么融合到一起的,又各自发挥了什么作用?

    就拿这个Mapper来说,我们定义了一个接口,声明了一个方法,然后对应的xml写了这个sql语句, 它怎么就执行成功了?这家伙是怎么实现的,带着这个好奇心,我一步步跟踪,慢慢揭开了它的面纱。

    一、初始化时的埋点

    MapperFactoryBean的父类SqlSessionDaoSupport中setSqlSessionFactory方法构建了一个sqlSession:

    public abstract class SqlSessionDaoSupport extends DaoSupport {
    
       private SqlSession sqlSession;
    
       private boolean externalSqlSession;
    
       public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
          if (!this.externalSqlSession) {
              this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
          }
       }
    
       //......
    }

    将会调用这个构造器:

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
          PersistenceExceptionTranslator exceptionTranslator) {
    
        notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        notNull(executorType, "Property 'executorType' is required");
    
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());
    }

    产生一个sqlSession代理,SqlSessionTemplate的内部类:

    private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          final SqlSession sqlSession = getSqlSession(
              SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType,
              SqlSessionTemplate.this.exceptionTranslator);
          try {
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
              // force commit even on non-dirty sessions because some databases require
              // a commit/rollback before calling close()
              sqlSession.commit(true);
            }
            return result;
          } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
              Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
              if (translated != null) {
                unwrapped = translated;
              }
            }
            throw unwrapped;
          } finally {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          }
        }
    }

    二、获取Mapper

    MapperFactoryBean是MapperScannerConfigurer在扫描包后往每个Mapper的beanDefine中添加给BeanClass属性的:

    definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
    definition.setBeanClass(MapperFactoryBean.class);
    
    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    那么当我们获取每个Mapper都会走MapperFactoryBean:

     public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
     }

    这个就是在前面提到的SqlSessionTemplate中根据Class类型来获取:

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

    Configuration#getMapper:

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

    MapperRegistry#getMapper:

     public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        if (!knownMappers.contains(type))
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        try {
          return MapperProxy.newMapperProxy(type, sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
     }

    public class MapperProxy implements InvocationHandler, Serializable

     public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
        ClassLoader classLoader = mapperInterface.getClassLoader();
        Class<?>[] interfaces = new Class[]{mapperInterface};
        MapperProxy proxy = new MapperProxy(sqlSession);
        return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
     }

    通过上面一系列方法,获取的是一个MapperProxy。

    虽然我们只定义了接口没有实现类,但纵观这个dao层,做的都是和数据库打交道的事,唯一不同的是sql语句不同,为了便于管理,将所有的sql写在配置文件中,然后根据配置的规则和相应接口生成代理类。

    走到这里,共经过了两次代理:

    • 第一次是在SqlSessionTemplate中,持有一个内部类SqlSessionInterceptor,将所有基于SqlSession的操作转移给DefaultSqlSession。

    • 第二次是针对Mapper的代理,为接口生成代理类。这个代理类持有了上面的SqlSessionTemplate(也间接持有了DefaultSqlSession)。

    第一次可以说是为了融入Spring而做的代理,让每个Mapper在创建之初就自然而然地持有 了一个SqlSession,后面的操作就是水到渠成。第二次的代理是必然的,根据Mybatis的设计,接口和Xml配置组合的方式,框架在背后为我们生成了代理类,这才符合Java规范嘛。

    三、方法调用

    抛开Spring的调用栈,从service层来看,例如下面的某个service的某个方法:

    @Autowired
    private XxMapper xxMapper;
    
    @Override
    public List<ComResVo> getListByXxId(Integer xxId) {
        if(null == xxId){
            return null;
        }
        return this.xxMapper.selectXxListById(xxId);
    }

    mapper作为属性注入到service中,当时通过MapperFactoryBean的getObject方法获取的就是一个代理类,这个时候调用就会转移到MapperProxy的invoke方法:

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
        final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
        final Object result = mapperMethod.execute(args);
        if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
          throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
     }

    MapperMethod#execute

    public Object execute(Object[] args) {
        Object result = null;
        if (SqlCommandType.INSERT == type) {
            Object param = getParam(args);
            result = sqlSession.insert(commandName, param);
        } else if (SqlCommandType.UPDATE == type) {
            Object param = getParam(args);
            result = sqlSession.update(commandName, param);
        } else if (SqlCommandType.DELETE == type) {
            Object param = getParam(args);
            result = sqlSession.delete(commandName, param);
        } else if (SqlCommandType.SELECT == type) {
            if (returnsVoid && resultHandlerIndex != null) {
                executeWithResultHandler(args);
            } else if (returnsMany) {
                result = executeForMany(args);
            } else if (returnsMap) {
                result = executeForMap(args);
            } else {
                Object param = getParam(args);
                result = sqlSession.selectOne(commandName, param);
            }
        } else {
            throw new BindingException("Unknown execution method for: " + commandName);
        }
        return result;
    }

    execute方法会根据方法类型选择对应的sqlSession方法,在这里至少把增删改查给区分开了,查询方法还给细化了。而传入的sqlSession是一个SqlSessionTemplate,它相关的调用又会转移至它持有的一个sqlSessionProxy(DefaultSqlSession)。

    DefaultSqlSession持有给定的Executor,将所有方法最终绑定到Executor的query和update方法。

    然后就是分别执行doQuery和doUpdate方法,构造StatementHandler,doQuery方法还要传入一个ResultHandler,处理返回的结果集。

    从上面的分析也大致知道了整个流程,对Mybatis的处理方式也有了一定的了解。如果叫我写这么一个框架,我会怎么写?

    我想最好的方法就是先不看这个源码,把它的功能全部搞懂,这个可以叫需求分析了,看看它实现了哪些功能,智能到什么程度,然后我自己去实现,这个过程可能会遇到很多问题,搞不出来可以适当参考下,全部搞完还要对比下,看看人家设计的高明之处。

    今天貌似是愚人节,节日快乐!

  • 相关阅读:
    《机电传动控制》第五,六周笔记
    《机电传动控制》第四周作业
    《机电传动控制》第三周作业
    《机电传动控制》第二周笔记
    《机电传动控制》第一周笔记
    《机电传动控制》----直流电机调速仿真作业
    《机电传动控制》----学习笔记八
    《机电传动控制》----学习笔记七
    《机电传动控制》----学习笔记六
    《机电传动控制》----学习笔记五
  • 原文地址:https://www.cnblogs.com/lucare/p/9312621.html
Copyright © 2020-2023  润新知