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,而方法是后面解析再去设置的。

这里看一下大概的流程图。 细节问题都在每一步中去处理的。

在这里插入图片描述
至于每一步细节处理都得去打断点 去推敲代码 这里就只看流程就行了,有问题,再去找具体的出问题的代码分析就好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值