Java教程

Mybatis源码分析

本文主要是介绍Mybatis源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

首先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的缓存的一二级缓存,有兴趣的同学可以更深入的去挖掘一下。


这篇关于Mybatis源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!