引言 spring-mybatis.xml中配置为:
1 2 3 4 5 6 7 8 9 <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="configLocation" value ="classpath:mybatis-config.xml" /> <property name ="dataSource" ref ="dataSource" /> <property name ="mapperLocations" value ="classpath:mapper/*.xml" /> </bean >
再来看SqlSessionFactoryBean的类继承关系图:
发现它实现了InitializingBean、FactoryBean、ApplicationListener接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface InitializingBean { void afterPropertiesSet () throws Exception; } public interface FactoryBean <T> { @Nullable T getObject () throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton () { return true ; } } @FunctionalInterface public interface ApplicationListener <E extends ApplicationEvent > extends EventListener { void onApplicationEvent (E event) ; }
这里简单看一下InitializingBean的afterPropertiesSet()方法是怎么被调用的,回顾一下Spring中bean通过ClassPathXmlApplicationContext的加载流程:
通过ClassPathXmlApplicationContext的构造函数,调用到其父类AbstractApplicationContext的refresh()方法。
之后调用AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)获取所有的bean定义。
进入AbstractBeanFactory的doGetBean(final String name, @Nullable final Class requiredType,@Nullable final Object[] args, boolean typeCheckOnly)方法。
再进入AbstractAutowireCapableBeanFactory的doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)方法。
在属性填充之后,调用initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)方法。
最后进入invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)方法,在这里调用了InitializingBean的afterPropertiesSet()方法。
为什么要看这个InitializingBean的afterPropertiesSet()方法呢,因为org.mybatis.spring.SqlSessionFactoryBean中有个很重要的方法在afterPropertiesSet()中实现。
1 2 3 4 5 6 7 8 9 @Override public void afterPropertiesSet () throws Exception { notNull(dataSource, "Property 'dataSource' is required" ); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required" ); state((configuration == null && configLocation == null ) || !(configuration != null && configLocation != null ), "Property 'configuration' and 'configLocation' can not specified with together" ); this .sqlSessionFactory = buildSqlSessionFactory(); }
buildSqlSessionFactory方法非常的长,但是很清晰: 创建SqlSessionFactory实例,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 protected SqlSessionFactory buildSqlSessionFactory () throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null ; if (this .configuration != null ) { configuration = this .configuration; if (configuration.getVariables() == null ) { configuration.setVariables(this .configurationProperties); } else if (this .configurationProperties != null ) { configuration.getVariables().putAll(this .configurationProperties); } } else if (this .configLocation != null ) { xmlConfigBuilder = new XMLConfigBuilder (this .configLocation.getInputStream(), null , this .configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration" ); } configuration = new Configuration (); if (this .configurationProperties != null ) { configuration.setVariables(this .configurationProperties); } } if (this .objectFactory != null ) { configuration.setObjectFactory(this .objectFactory); } if (this .objectWrapperFactory != null ) { configuration.setObjectWrapperFactory(this .objectWrapperFactory); } if (this .vfs != null ) { configuration.setVfsImpl(this .vfs); } if (hasLength(this .typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this .typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases" ); } } } if (!isEmpty(this .typeAliases)) { for (Class<?> typeAlias : this .typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'" ); } } } if (!isEmpty(this .plugins)) { for (Interceptor plugin : this .plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'" ); } } } if (hasLength(this .typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this .typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers" ); } } } if (!isEmpty(this .typeHandlers)) { for (TypeHandler<?> typeHandler : this .typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'" ); } } } if (this .databaseIdProvider != null ) { try { configuration.setDatabaseId(this .databaseIdProvider.getDatabaseId(this .dataSource)); } catch (SQLException e) { throw new NestedIOException ("Failed getting a databaseId" , e); } } if (this .cache != null ) { configuration.addCache(this .cache); } if (xmlConfigBuilder != null ) { try { xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed configuration file: '" + this .configLocation + "'" ); } } catch (Exception ex) { throw new NestedIOException ("Failed to parse config resource: " + this .configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this .transactionFactory == null ) { this .transactionFactory = new SpringManagedTransactionFactory (); } configuration.setEnvironment(new Environment (this .environment, this .transactionFactory, this .dataSource)); if (!isEmpty(this .mapperLocations)) { for (Resource mapperLocation : this .mapperLocations) { if (mapperLocation == null ) { continue ; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder (mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException ("Failed to parse mapping resource: '" + mapperLocation + "'" , e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'" ); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found" ); } } return this .sqlSessionFactoryBuilder.build(configuration); } public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory (config); }
这里我只分析几个我感兴趣的部分:
解析mybatis-config.xml配置文件:xmlConfigBuilder.parse();
解析mapper/*.xml下的文件
这里在留一个点:mybatis的事务管理是使用自己的还是委托给spring处理的?
解析mybatis-config.xml配置文件 1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <settings > <setting name ="cacheEnabled" value ="true" /> </settings > <typeAliases > <typeAlias alias ="People" type ="com.demo.mybatis.entity.PeopleEntity" /> </typeAliases > </configuration >
1 2 3 4 5 6 7 8 9 public Configuration parse () { if (parsed) { throw new BuilderException ("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; }
parseConfiguration方法写的也很清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); environmentsElement(root.evalNode("environments" )); databaseIdProviderElement(root.evalNode("databaseIdProvider" )); typeHandlerElement(root.evalNode("typeHandlers" )); mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
这里的配置信息一定要参考MyBatis官方配置文档 。 这里我之分析设置的两项:settings和typeAliases,还有个重要的mappers,等后面解析mapper/*.xml下的文件那一部分一块看。
Properties settings = settingsAsProperties(root.evalNode(“settings”)); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private Properties settingsAsProperties (XNode context) { if (context == null ) { return new Properties (); } Properties props = context.getChildrenAsProperties(); MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException ("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)." ); } } return props; } public Properties getChildrenAsProperties () { Properties properties = new Properties (); for (XNode child : getChildren()) { String name = child.getStringAttribute("name" ); String value = child.getStringAttribute("value" ); if (name != null && value != null ) { properties.setProperty(name, value); } } return properties; }
就比如: 将cacheEnabled和true塞到properties中。
typeAliasesElement(root.evalNode(“typeAliases”)); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void typeAliasesElement (XNode parent) { if (parent != null ) { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name" ); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { String alias = child.getStringAttribute("alias" ); String type = child.getStringAttribute("type" ); try { Class<?> clazz = Resources.classForName(type); if (alias == null ) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException ("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
这的逻辑也相对简单,源码自带注释。
解析mapper/*.xml下的文件 这块要参考MyBatis官方XML映射文件中文文档 。 感觉这块才是MyBatis最强大的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for (Resource mapperLocation : this .mapperLocations) { if (mapperLocation == null ) { continue ; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder (mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException ("Failed to parse mapping resource: '" + mapperLocation + "'" , e); } finally { ErrorContext.instance().reset(); } }
这里有必要简单的贴个PeopleMapper.xml和PeopleMapper.java还有PeopleEntity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.demo.mybatis.mapper.PeopleMapper" > <resultMap id ="BaseResultMap" type ="com.demo.mybatis.entity.PeopleEntity" > <result column ="id" property ="id" jdbcType ="INTEGER" /> <result column ="name" property ="name" jdbcType ="VARCHAR" /> </resultMap > <select id ="query" resultMap ="BaseResultMap" > SELECT id,name from people </select > </mapper >
1 2 3 4 5 6 7 8 9 10 11 public interface PeopleMapper { List<PeopleEntity> query () ; } import lombok.Data;@Data public class PeopleEntity { private int id; private String name; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
上面的代码涉及到三块重点,一个是mapper.xml的解析,另一个是mapper接口与命名空间绑定,还有一个是解析未完成的节点。
解析映射文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void configurationElement (XNode context) { try { String namespace = context.getStringAttribute("namespace" ); if (namespace == null || namespace.equals("" )) { throw new BuilderException ("Mapper's namespace cannot be empty" ); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref" )); cacheElement(context.evalNode("cache" )); parameterMapElement(context.evalNodes("/mapper/parameterMap" )); resultMapElements(context.evalNodes("/mapper/resultMap" )); sqlElement(context.evalNodes("/mapper/sql" )); buildStatementFromContext(context.evalNodes("select|insert|update|delete" )); } catch (Exception e) { throw new BuilderException ("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
代码很清晰,在看源码的时候一定要看着官方文档,就比如parameterMap节点,已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。 在这里我以PeopleMapper.xml为例子进行解析。 首先namespace为:com.demo.mybatis.mapper.PeopleMapper,之后解析resultMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 private void resultMapElements (List<XNode> list) throws Exception { for (XNode resultMapNode : list) { try { resultMapElement(resultMapNode); } catch (IncompleteElementException e) { } } } private ResultMap resultMapElement (XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList()); } private ResultMap resultMapElement (XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); String id = resultMapNode.getStringAttribute("id" , resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute("type" , resultMapNode.getStringAttribute("ofType" , resultMapNode.getStringAttribute("resultType" , resultMapNode.getStringAttribute("javaType" )))); String extend = resultMapNode.getStringAttribute("extends" ); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping" ); Class<?> typeClass = resolveClass(type); Discriminator discriminator = null ; List<ResultMapping> resultMappings = new ArrayList <ResultMapping>(); resultMappings.addAll(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor" .equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator" .equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList <ResultFlag>(); if ("id" .equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver (builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
我这里也不深入分析了,主要目的是了解整体的框架。
Mapper 接口绑定过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void bindMapperForNamespace () { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null ) { Class<?> boundType = null ; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null ) { if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } }
再看configuration.addMapper(boundType),这个Configuration类是非常重要的类。
1 2 3 public <T> void addMapper (Class<T> type) { mapperRegistry.addMapper(type); }
会调用到MapperRegistry的addMapper方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException ("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory <T>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
简单总结: MyBatis对配置文件的解析或者对mapper.java上注解的解析真的很复杂。我这里只为了加深一些MyBatis的使用理解,所以没有太深入的分析,感兴趣的话可以参考官方文档,深入的分析。