關於mybaitis

mybatis啓動流程

一、首先來看看最簡單的mybatis項目啓動過程sql

public static void mybatisTest() throws IOException {
    String resource = "mybatis/mybatis-config.xml";
    //配置文件
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = null;
    try {
        //開啓一個session
        sqlSession = sqlSessionFactory.openSession();
        //獲取mapper
        JobConfigDao jobConfigDao = sqlSession.getMapper(JobConfigDao.class);
        //查詢數據
        List<JobConfig> jobConfigList = jobConfigDao.listConfig();
        System.out.println(jobConfigList);
    } finally {
        if (sqlSession != null)
            sqlSession.close();
    }
}

這個過程主要是SqlSessionFactory 的建立,先獲取配置信息,而後經過SqlSessionFactoryBuilder來建立SqlSessionFactory 。
build方法的核心代碼是:緩存

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 
var5 = this.build(parser.parse());

能夠看到mybatis是經過XMLConfigBuilder來解析配置文件的。來看看XMLConfigBuilder的parse方法:session

public Configuration parse() {
    //XMLConfigBuilder中有一個boolean類型parsed,來標記是否解析過配置文件,解析過以後就設置爲true,防止一樣的配置被重複解析
    if (this.parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    } else {
        this.parsed = true;
        // 解析configuration節點下的配置
        this.parseConfiguration(this.parser.evalNode("/configuration"));
        return this.configuration;
    }
}

private void parseConfiguration(XNode root) {
    try {
        this.propertiesElement(root.evalNode("properties"));
        Properties settings = this.settingsAsProperties(root.evalNode("settings"));
        this.loadCustomVfs(settings);
        this.typeAliasesElement(root.evalNode("typeAliases"));
        this.pluginElement(root.evalNode("plugins"));
        this.objectFactoryElement(root.evalNode("objectFactory"));
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        this.settingsElement(settings);
        this.environmentsElement(root.evalNode("environments"));
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        // 解析mapper
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

能夠看到,在parseConfiguration方法中會將mybatis-config.xml配置下的各類屬性獲取出來一一解析,並映射到相關的屬性上面去。mybatis

mybatis定義的接口,怎麼找到實現的?

經過上面的啓動過程,已經知道mybatis是經過XMLConfigBuilder來解析配置文件的,具體解析是在parseConfiguration方法的中,獲取到mappers配置節點,最後一行this.mapperElement(root.evalNode("mappers"))將節點信息轉交給XMLMapperBuilder來完成對mapper的解析。app

來看看XMLMapperBuilder的parse方法:ide

public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
        this.configurationElement(this.parser.evalNode("/mapper"));
        this.configuration.addLoadedResource(this.resource);
        //經過命名空間綁定mapper
        this.bindMapperForNamespace();
    }

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
}

由於這裏的重點是想知道定義的接口是怎麼實現的,因此直接來看看bindMapperForNamespace方法對如何來實現mapper的綁定:ui

private void bindMapperForNamespace() {
    String namespace = this.builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class boundType = null;

        try {
            //經過反射獲取類類型
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException var4) {
            ;
        }

        //若是當前類尚未綁定到配置的緩存中
        if (boundType != null && !this.configuration.hasMapper(boundType)) {
            this.configuration.addLoadedResource("namespace:" + namespace);
            //經過mybatis的Configuration將mapper註冊到MapperRegistry
            this.configuration.addMapper(boundType);
        }
    }
}

來看看MapperRegistry 的addMapper:this

public <T> void addMapper(Class<T> type) {
    this.mapperRegistry.addMapper(type);
}

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {//先判斷是否爲接口
        if (this.hasMapper(type)) {//若是已經註冊,拋出異常
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            //建立一個代理MapperProxyFactory 並將該mapper緩存到內存的Map中去
            this.knownMappers.put(type, new MapperProxyFactory(type));
            //綁定註解
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                this.knownMappers.remove(type);
            }

        }
    }
}

經過對上面源碼的跟蹤分析,終於知道,mybatis經過MapperProxyFactory爲每一個接口都提供了一個代理實現,而後經過MapperRegistry將mapper註冊到容器中的。spa

那麼在使用的時候又是如何來獲取這個mapper的呢?代理

答案是:反射加上代理

首先經過SqlSession的getMapper方法,mybatis爲SqlSession提供了一個默認的實現DefaultSqlSession,DefaultSqlSession的內部屬性以下:

//Configuration配置
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;

DefaultSqlSession獲取配置,而後配置中獲取MapperRegistry,從MapperRegistry中獲取到mapper代理工廠,再經過MapperProxyFactory的newInstance來建立mapper代理類MapperProxy

總結:對於Mybatis,記住幾個關鍵的處理類:SqlSessionFactoryBuilder建立SqlSessionFactory,SqlSessionFactory中開啓一個SqlSession,經過sqlSession來執行用戶操做;RegisterMapper將mapper註冊到容器中,經過MapperProxyFactory來建立mapper代理,mapper代理最終是經過jdk反射包中Proxy代理類來建立的。

啓動時建立的SqlSessionFactory是DefaultSqlSessionFactory,DefaultSqlSessionFactory中擁有屬性Configuration,Configuration是經過XMLConfigBuilder從配置文件中解析出來的各類配置信息,Configuration中有MapperRegistry,MapperRegistry中用一個final類型的Map存儲了用戶定義Mapper代理實現工廠MapperProxyFactory,Mapper代理實現是MapperProxy。
相關文章
相關標籤/搜索