首先mybatis 有两个关键的类SqlSessionFactoryBean和MapperScannerConfigurer
先简单的说一下它们的作用:
SqlSessionFactoryBean :根据mapper.xml生成代理对象,和创建mapperstatement对象
MapperScannerConfigurer:扫描mapper.java文件生成实例注入spring容器。
目录
SqlSessionFactoryBean 生产代理对象
SqlSessionfactoryBean 生成 mapperStatement 对象
MapperScannerConfigurer 扫描注入spring
接下来再详细看看它们是如何和做到的:
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); } }
生成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,
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的缓存的一二级缓存,有兴趣的同学可以更深入的去挖掘一下。