天天看點

MyBatis源碼解析之SqlSessionFactory的建構

mybatis他是一個基于jdbc封裝好的一個持久層架構。

在使用mybatis的時候,我們需要建立一個sqlSessionFactory與sqlSession,然後使用sqlSession建立出我們的代理對象,由代理對象去執行目标方法,拿到最終的執行結果。

因為我們這一塊的所有的mapper都是基于接口的,是以mybatis使用jdk的動态代理來建立代理對象。

首先看一下官方文檔對于這幾個名字的解釋

SqlSessionFactoryBuilder

這個類可以被執行個體化、使用和丢棄,一旦建立了 SqlSessionFactory,就不再需要它了。 是以 SqlSessionFactoryBuilder 執行個體的最佳作用域是方法作用域(也就是局部方法變量)。 你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 執行個體,但最好還是不要一直保留着它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被建立就應該在應用的運作期間一直存在,沒有任何理由丢棄它或重新建立另一個執行個體。 使用 SqlSessionFactory 的最佳實踐是在應用運作期間不要重複建立多次,多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”。是以 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜态單例模式。

SqlSession

每個線程都應該有它自己的 SqlSession 執行個體。SqlSession 的執行個體不是線程安全的,是以是不能被共享的,是以它的最佳的作用域是請求或方法作用域。 絕對不能将 SqlSession 執行個體的引用放在一個類的靜态域,甚至一個類的執行個體變量也不行。 也絕不能将 SqlSession 執行個體的引用放在任何類型的托管作用域中,比如 Servlet 架構中的 HttpSession。 如果你現在正在使用一種 Web 架構,考慮将 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以打開一個 SqlSession,傳回一個響應後,就關閉它。 這個關閉操作很重要,為了確定每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。

可能我們剛接觸到的都是mybatis去加載xml檔案,然後去建立sqlSessionFactory。而xml檔案最終的解析結果就是建立一個Configuration對象,在這個對象裡面封裝了各種配置資訊。

我們這裡直接建立Configuration對象,在他裡面直接去配置各種資訊就好。

SqlSessionFactory的建立

public static SqlSessionFactory getFactory(){
		//擷取資料源
        DataSource dataSource = getDataSource();
        //建立一個事務工廠
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        //初始化一個環境對象
        Environment environment = new Environment("development", transactionFactory, dataSource);
        //初始化配置  ----如果我們需要解析xml檔案的話,xml檔案最後解析完之後,就是生成的這個對象
        Configuration configuration = new Configuration(environment);
        //将我們的mapper檔案收集起來
        configuration.addMapper(Mapper.class);
        //傳回sqlSessionFactory
        return new SqlSessionFactoryBuilder().build(configuration);
    }
           

mapper檔案的收集

mybatis在初始化sqlSessionFactory的時候,會将我們所有的與mysql有關的dao檔案都收集起來,并且做一些處理,其中就包括了關于緩存的處理,dao與xml檔案的綁定等。

我們就來簡單分析下這一塊是怎麼處理的

Configuration    757  line

public <T> void addMapper(Class<T> type) {
        //将mapper類型儲存在了mapperRegistry中
        //mapperRegistry是建構configuration的時候建立的一個對象
        //見名隻其意  mapper的注冊器
        this.mapperRegistry.addMapper(type);
    }
           

mapper的注冊

MapperRegistry   45  line

public <T> void addMapper(Class<T> type) {
		//判斷傳入的mapper是不是接口類型   不是接口不做處理
        if (type.isInterface()) {
        	//如果這個mapper已經注冊過,直接抛異常
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
			//标記處理還未完成
            boolean loadCompleted = false;

            try {
            	//将mapper包裝成MapperProxyFactory,然後放在knownMappers這個map中
                this.knownMappers.put(type, new MapperProxyFactory(type));
                //初始化了一個解析器  this.config就是我們的Configuration對象
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                //1、解析~~~
                parser.parse();
                //标記處理成功
                loadCompleted = true;
            } finally {
            	//如果解析異常,将mapper移出map
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }
           

1、解析

在将mapper包裝之後,初始化了一個解析器,然後去解析處理緩存、mapper檔案與xml檔案的綁定

public void parse() {
        String resource = this.type.toString();
        //判斷這個mepper資源還沒加載的話,再來進入if去處理
        if (!this.configuration.isResourceLoaded(resource)) {
        	//2、xml檔案的加載
            this.loadXmlResource();
            //将mapper加入set集合,标記這個mapper資源已經處理過
            this.configuration.addLoadedResource(resource);
            //設定名稱空間  this.type.getName=package.ClassName(也就是入參為:mybatis.Mapper)
            //加載xml檔案的時候,如果有xml檔案,那麼就會設定這個  沒有xml檔案,則這一步設定名稱空間
            this.assistant.setCurrentNamespace(this.type.getName());
            //3、緩存的處理
            this.parseCache();
            this.parseCacheRef();
            //拿到所有方法
            Method[] var2 = this.type.getMethods();
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                Method method = var2[var4];
                //method有對應的statement, 因為接口的所有方法都會被解析綁定成一個statement
                if (this.canHaveStatement(method)) {
                    if (this.getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null) {
                        this.parseResultMap(method);
                    }

                    try {
                        this.parseStatement(method);
                    } catch (IncompleteElementException var7) {
                        this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                    }
                }
            }
        }

        this.parsePendingMethods();
    }
           

2、xml檔案的加載

private void loadXmlResource() {
		//已經加載過,則不處理
        if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
            //擷取xml檔案路徑,預設的就是classpath/packageName/MapplerName.xml
            //也就是.mybatis/Mapper
            String xmlResource = this.type.getName().replace('.', '/') + ".xml";
            //擷取資源流
            InputStream inputStream = this.type.getResourceAsStream("/" + xmlResource);
            if (inputStream == null) {
                try {
                    inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
                } catch (IOException var4) {
                }
            }

            if (inputStream != null) {
            	//初始化一個Xml解析器
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
                //解析xml
                xmlParser.parse();
            }
        }
    }
           

解析Mapper.xml檔案

XMLMapperBuilder    77  line

public void parse() {
		//再次判斷是否已經加載過  這一塊是入參  mybatis/Mapper.xml
        if (!this.configuration.isResourceLoaded(this.resource)) {
        	//配置解析之後的xml
            this.configurationElement(this.parser.evalNode("/mapper"));
            //xml檔案加入set,标記這個xml檔案已經處理過了
            this.configuration.addLoadedResource(this.resource);
            //綁定nameSpace  具體看後面
            this.bindMapperForNamespace();
        }

		//下面這三個 是幹嘛的?  後面分析  暫時還不清楚
        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }

//配置解析之後的xml
private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.isEmpty()) {
                this.builderAssistant.setCurrentNamespace(namespace);
                //處理标簽  ~~~~~~
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
				//處理四大标簽  建構statement  針對每一條sql都會建構好一個statement
				//直接看流程圖吧              
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
        }
    }
           

3、緩存的加載~~~ mapper接口中的方法的加載~~~

解析緩存無非就是建構一個Cache對象,将其與nameSpace綁定在Configuration的map中而已

mapper中方法的加載也就是解析這個方法,建構一個MappedStatement,然後與nameSpace+id綁定在Configuration中。

這裡需要注意的是他是放在map中的,key為nameSpace+id 也就是nameSpace+方法名,那麼如果xml中有這個方法的sql,但是mapper中也标注了@Select等注解,那麼方法上的會替換掉xml中的。因為都是直接調用map.put,而方法是後面解析再去設定的。

這裡看一下大概的流程圖。 細節問題都在每一步中去處理的。

MyBatis源碼解析之SqlSessionFactory的建構

至于每一步細節處理都得去打斷點 去推敲代碼 這裡就隻看流程就行了,有問題,再去找具體的出問題的代碼分析就好

繼續閱讀