ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Mybatis源码分析

2021-08-03 14:02:46  阅读:207  来源: 互联网

标签:分析 mapper 对象 namespace 源码 Mybatis new configuration public


首先mybatis 有两个关键的类SqlSessionFactoryBean和MapperScannerConfigurer

先简单的说一下它们的作用:

        SqlSessionFactoryBean :根据mapper.xml生成代理对象,和创建mapperstatement对象

        MapperScannerConfigurer:扫描mapper.java文件生成实例注入spring容器。

目录

         SqlSessionFactoryBean 生产代理对象

SqlSessionfactoryBean 生成 mapperStatement  对象 

MapperScannerConfigurer 扫描注入spring

接下来再详细看看它们是如何和做到的:

        SqlSessionFactoryBean 生产代理对象

        SqlSessionFactoryBean实现了 InitializingBean 接口并重写了afterPropertiesSet()方法,意思就是,在spring初始化的某个阶段会调用此方法,去构建SqlSessionFactory。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

 -------------------------------------------------------------------------------------------------------------------------------

然后再来说说整个buildSqlSessionFactionry()方法的逻辑,就是根据全局配置文件,找到对象的Mapper.xml文件,并根据namespace生产代理对象,底层原理就是动态代理及反射机制。根据spring注入的configLocation找到全局配置文件并生成configuration对象。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:context/sql-config.xml" />
		<property name="dataSource" ref="DataSource" />
	</bean>
 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        XMLConfigBuilder xmlConfigBuilder = null;
        Configuration configuration;
        //根据configuration获取configuration对象
        if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
            }

            configuration = new Configuration();
            configuration.setVariables(this.configurationProperties);
        }

xmlConfigBuilder.parse() 解析configuration对象。

if (xmlConfigBuilder != null) {
    try {
        //解析configuration对象
        xmlConfigBuilder.parse();
        if (logger.isDebugEnabled()) {
            logger.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
    } catch (Exception var23) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var23);
    } finally {
        ErrorContext.instance().reset();
    }
}

解析configuration节点。

public Configuration parse() {
        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;
        }
    }

 解析mapper节点。

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.settingsElement(root.evalNode("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);
        }
    }
解析mapper得到namespace。
public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace(); //解析namespace
        }

        this.parsePendingResultMaps();
        this.parsePendingChacheRefs();
        this.parsePendingStatements(); //解析sql语句节点
    }

根据mapper.xml里面的namespace路径拿到类对象(反射机制)

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

            try {
                boundType = Resources.classForName(namespace); //解析根据namespace拿到类对象
            } catch (ClassNotFoundException var4) {
                ;
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);//添加到configuration的map里
            }
        }

    }

addMapper方法,添加的是proxy对象。添加到configuration对象 knowMappers(它是一个hashmap)里面

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 {
                this.knownMappers.put(type, new MapperProxyFactory(type));//添加的是mapperpoxyfactory对象。
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

我们再来看看这个MapperProxyFactory类,前面说了mybatis创建代理对象用的是动态代理可以看到 newInstance()方法,代理逻辑就是对sqlsession的api调用,sqlsession底层就是对excutor对象的调用,此处就不展开说了。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) { 
        //动态代理
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            //调用sqlsesiion的api 去执行sql
            return mapperMethod.execute(this.sqlSession, args); 
        }
    }

SqlSessionfactoryBean 生成 mapperStatement  对象 

生成mapper对象的关键代码parsePendingStatements(),它会去扫描mapper.xml文件下面的sql标签节点去解析

private void parsePendingStatements() {
        Collection<XMLStatementBuilder> incompleteStatements = this.configuration.getIncompleteStatements();
        synchronized(incompleteStatements) {
            Iterator iter = incompleteStatements.iterator();

            while(iter.hasNext()) {
                try {
                    ((XMLStatementBuilder)iter.next()).parseStatementNode(); //解析节点
                    iter.remove();
                } catch (IncompleteElementException var6) {
                    ;
                }
            }

        }
    }

根据mapper的namespace 加sqlid拼接key 并添加到configuration的mapperstatement里,由于key是namespace + sqlid 所以,mybatis不存在方法重载。

//代码已删减
public void parseStatementNode() {
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        //databaseIdMatchesCurrent()方法 ,拼接key 
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            this.context, parameterTypeClass);
            String resultSets = this.context.getStringAttribute("resultSets");
            String keyProperty = this.context.getStringAttribute("keyProperty");
            String keyColumn = this.context.getStringAttribute("keyColumn");
            String keyStatementId = id + "!selectKey";  
            keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
            //放进configuration的mapperstatement里
            this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }

MapperScannerConfigurer 扫描注入spring

接下来我们说说MapperScannerConfigurer,

MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor,也就是说在注册beandifinition的时候会调用 postProcessBeanDefinitionRegistry()方法。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

spring配置

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.*.mapper" />
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

//扫描在spring注入的包下面mapper.java类

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.registerFilters();
        //扫描在spring注入的包
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n")); 
    }

然后看看scan方法,它做了哪些事情,设置bean的class对象,这里设置了MapperFactoryBean对象,此对象实现了FactoryBean类,重写了getObject();

GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
                }

                definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
                //关键:
                //设置bean的class对象,这里设置了MapperFactoryBean对象,此对象实现了FactoryBean类,重写了getObject();
                definition.setBeanClass(MapperFactoryBean.class);
                definition.getPropertyValues().add("addToConfig", this.addToConfig);
                boolean explicitFactoryUsed = false;
                //注入sqlsessionfactory
                if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                    definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                    explicitFactoryUsed = true;
                } else if (this.sqlSessionFactory != null) {
                    definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                    explicitFactoryUsed = true;
                }

我们来看看getObject()方法

public T getObject() throws Exception {
        //调用sqlsession拿去mapper
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

最终它会去configuration对象里面的knownMappers拿到MapperProxyFactory 并调用 newInstance 方法实例化这个代理对象。这里对newInstance就是上面SqlSessionFactoryBean说的动态代理创建代理对象,然后放进spring容器里。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //实例化代理对象。
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
总结:
SqlSessionFactoryBean 扫描全局文件,根据全局文件里的配置找到mapper.xml,然后根据mapper.xml的namespace 创建代理对象,根据mapper.xml的sql节点创建mapperstatement对象,两者都保存在configuration的hashmap里。
MapperScannerConfigurer 根据配置扫描mapper.java类,并对它们的beandifition进行修改,将BeanClass换成了MapperFactoryBean,然后这样就可以获得代理对象,并注入spring容器。
  这里由于篇幅的关系,没有展开讲executor底层、动态sql、还有mybatis的缓存的一二级缓存,有兴趣的同学可以更深入的去挖掘一下。


标签:分析,mapper,对象,namespace,源码,Mybatis,new,configuration,public
来源: https://blog.csdn.net/u014353123/article/details/119343133

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有