MyBatis的执行原理分为如下四步:
- ① 获取SqlSessionFactory
- ② 获取SqlSession
- ③ 获取mapper的代理对象
- ④ sqlsession进行具体CRUD过程
这里又分为三种场景:
① 无spring环境
自己手动根据mybatis配置文件获取SqlSessionFactory和SqlSession
② 传统ssm环境
通常在applicationContext.xml中注册了SqlSessionFactory和SqlSession
③ SpringBoot环境
SpringBoot做了自动化配置,当然你也可以自定义注册
这里咱们从第一种场景(无spring)进行分析。
【1】抽象类BaseBuilder与子类
BaseBuilder 有如下子类:
XMLConfigBuilder
用来解析mybatis全局配置,初步获取Configuration实例
XMLMapperBuilder
解析mapper.xml文件,每个XMLMapperBuilder实例有成员实例MapperBuilderAssistant
MapperBuilderAssistant
XMLStatementBuilder
解析mapper.xml中每个select|insert|update|delete结点
有成员属性MapperBuilderAssistant builderAssistant
XMLScriptBuilder
XMLScriptBuilder和SqlSourceBuilder获取sqlsource片段
SqlSourceBuilder
这里需要注意的是,上述子类在实例化时,会显示调用父类有参构造方法super(configuration);
来为当前子类实例拥有的来自于父类的成员属性赋值。如下图所示:
① 抽象父类BaseBuilder
如下所示,有属性configuration、typeAliasRegistry、typeHandlerRegistry,上述子类都有这三个成员属性。在创建configuration实例时,会进行初始化。
public abstract class BaseBuilder { protected final Configuration configuration; protected final TypeAliasRegistry typeAliasRegistry; protected final TypeHandlerRegistry typeHandlerRegistry; public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); } //... }
typeAliasRegistry维护了一个mapprivate final Map<String, Class<?>> TYPE_ALIASES = new HashMap<>();,里面放了类型别名与class类型。如(string,class java.lang.String),参考其源码可发现在其实例化的时候就注册了值。
typeHandlerRegistry维护了了六个final Map
,同样在实例化时就进行了Map初始化。
public final class TypeHandlerRegistry { private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>(); private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>(); private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class; public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(int.class, new IntegerTypeHandler()); register(JdbcType.INTEGER, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler()); register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler()); register(float.class, new FloatTypeHandler()); register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler()); register(double.class, new DoubleTypeHandler()); register(JdbcType.DOUBLE, new DoubleTypeHandler()); register(Reader.class, new ClobReaderTypeHandler()); register(String.class, new StringTypeHandler()); register(String.class, JdbcType.CHAR, new StringTypeHandler()); register(String.class, JdbcType.CLOB, new ClobTypeHandler()); register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCLOB, new NClobTypeHandler()); register(JdbcType.CHAR, new StringTypeHandler()); register(JdbcType.VARCHAR, new StringTypeHandler()); register(JdbcType.CLOB, new ClobTypeHandler()); register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler()); register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler()); register(JdbcType.REAL, new BigDecimalTypeHandler()); register(JdbcType.DECIMAL, new BigDecimalTypeHandler()); register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(InputStream.class, new BlobInputStreamTypeHandler()); register(Byte[].class, new ByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler()); register(byte[].class, new ByteArrayTypeHandler()); register(byte[].class, JdbcType.BLOB, new BlobTypeHandler()); register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER); register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler()); register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); register(JdbcType.TIMESTAMP, new DateTypeHandler()); register(JdbcType.DATE, new DateOnlyTypeHandler()); register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler()); register(java.sql.Time.class, new SqlTimeTypeHandler()); register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler()); register(Instant.class, InstantTypeHandler.class); register(LocalDateTime.class, LocalDateTimeTypeHandler.class); register(LocalDate.class, LocalDateTypeHandler.class); register(LocalTime.class, LocalTimeTypeHandler.class); register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class); register(OffsetTime.class, OffsetTimeTypeHandler.class); register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class); register(Month.class, MonthTypeHandler.class); register(Year.class, YearTypeHandler.class); register(YearMonth.class, YearMonthTypeHandler.class); register(JapaneseDate.class, JapaneseDateTypeHandler.class); // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); } //... }
② final修饰符
为什么这里提到final修饰符呢?跟踪源码会看到上面提到的BaseBuilder的几个子类,包括configuration类中很多成员都是final。
final修饰符只能够作用在类、方法和变量上。它的作用是所有被final修饰的内容不能被改变。
- final类不能被继承。
- final变量一旦被赋值就不能被改变。
- final方法不能被重写(可以被重载)。
- final变量允许在构造函数中对其进行赋值
注意:如果一个final变量是对象的引用,这意味着该引用的值是一定不会改变的,但是对象的成员值可以改变。
【2】源码分析
源码分析时序图如下所示:
① SqlSessionFactoryBuilder
SqlSessionFactoryBuilder提供了诸多重载方法build来创建DefaultSqlSessionFactory。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
② XMLConfigBuilder
这个类主要作用如下:
- ① 创建configuration实例,进行初步初始化
- ② 解析mybatis全局配置,对configuration做进一步赋值
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { // issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 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全局配置的同学应该对上面代码很容易理解,解析mybatis的全局配置,为configuration实例属性赋值。
解释如下:
解析properties结点
解析settings结点
解析typeAliases结点
解析plugins结点
解析objectFactory结点
解析objectWrapperFactory结点
解析reflectorFactory结点
根据settings结点为configuration设置对应的成员变量
解析environments结点
解析databaseIdProvider结点
解析typeHandlers结点
解析mappers结点-这个也是我们着重关注的
从 mapperElement(root.evalNode(“mappers”))方法中会根据映射器mappers的配置类型进行不同的处理。代码如下所示:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //解析包 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //获取resource url 或者完全限定类名 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); //解析resource if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); //解析url } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); //解析类 } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
③ XMLMapperBuilder
每一个mapper资源都会创建一个XMLMapperBuilder
实例,而每一个XMLMapperBuilder
实例都会有一个私有的成员实例MapperBuilderAssistant
。
这里从其构造函数即可看出:
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; }
核心方法parse
源码如下:
publ public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } //下面代码可以理解为mybatis"后手",对前面不能确定的ResultMap、ChacheRef、Statement再次处理 parsePendingResultMaps(); //若是在某个地方看到了parsePendingChacheRefs(),也是正确的。这是mybatis框架开发工程师写错单词了;不过已经修正 parsePendingCacheRefs(); parsePendingStatements(); }
代码解释如下
① 判断当前资源是否被加载。通过判断configuration实例的成员属性Set<String> loadedResources = new HashSet<>()是否contains当前resource来判断
② 如果没有加载,执行以下步骤
③ 解析mapper结点
④ 将当前resource放到configuration实例中Set<String> loadedResources = new HashSet<>()属性里
⑤ bindMapperForNamespace();
判断当前resource对应的namespace是否已经在configuration实例的MapperRegistry成员实例的knownMappers集合中。Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>()
如果不在,则执行以下步骤
将"namespace:" + namespace添加到configuration实例的成员loadedResources中
configuration.addMapper(boundType);
knownMappers.put(type, new MapperProxyFactory<>(type))
创建MapperAnnotationBuilder实例,解析命名空间对应的mapper接口(也就是解析接口上面的注解)
⑥ 尝试处理待解决的ResultMaps
⑦ 尝试处理待解决的CacheRefs
⑧ 尝试处理待解决的Statements
核心方法configurationElement
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { 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); } }
代码解释如下:
① 获取namespace
② 为当前mapper的builderAssistant(MapperBuilderAssistant实例,是XMLMapperBuilder成员变量)设置namespace
③ 解析cache-ref结点,处理缓存引用
④ 解析cache结点,为当前namespace创建缓存实例
⑤ 解析parameterMap结点,参数映射
⑥ 解析resultMap结点,结果映射
⑦ 解析sql结点,SQL片段
⑧ 解析select|insert|update|delete结点,这里最终会创建一个个MappedStatement放入configuration实例的Map<String, MappedStatement> mappedStatements中
核心方法parameterMapElement
参数映射解析
private void parameterMapElement(List<XNode> list) { for (XNode parameterMapNode : list) { String id = parameterMapNode.getStringAttribute("id"); String type = parameterMapNode.getStringAttribute("type"); Class<?> parameterClass = resolveClass(type); List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter"); List<ParameterMapping> parameterMappings = new ArrayList<>(); for (XNode parameterNode : parameterNodes) { String property = parameterNode.getStringAttribute("property"); String javaType = parameterNode.getStringAttribute("javaType"); String jdbcType = parameterNode.getStringAttribute("jdbcType"); String resultMap = parameterNode.getStringAttribute("resultMap"); String mode = parameterNode.getStringAttribute("mode"); String typeHandler = parameterNode.getStringAttribute("typeHandler"); Integer numericScale = parameterNode.getIntAttribute("numericScale"); ParameterMode modeEnum = resolveParameterMode(mode); Class<?> javaTypeClass = resolveClass(javaType); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale); parameterMappings.add(parameterMapping); } builderAssistant.addParameterMap(id, parameterClass, parameterMappings); } }
一个parameterMap结点格式可能如下:
<parameterMap type="com.mybatis.bean.Employee" id="employeeParamMap"> <parameter property="id" javaType="java.lang.Long" jdbcType="BIGINT" typeHandler="org.apache.ibatis.type.LongTypeHandler" mode="IN" resultMap="" scale=""/> <parameter property="lastName" javaType="java.lang.String" jdbcType="VARCHAR" typeHandler="org.apache.ibatis.type.StringTypeHandler" mode="IN" resultMap="" scale=""/> </parameterMap>
代码解释如下:
① 遍历循环每一个parameterMap结点
② 解析parameterMap结点的id 、type属性并遍历循环其子结点parameter
③ 解析每一个子结点parameter的属性,封装为一个ParameterMapping 实例对象,放到list集合List<ParameterMapping> parameterMappings中
④ 当前调用parameterMap结点解析完,调用 builderAssistant.addParameterMap(id, parameterClass, parameterMappings);。
⑤ new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();获取parameterMap实例对象然后放入configuration实例的成员属性Map<String, ParameterMap> parameterMaps集合中。放入格式为:namespace+'.'+id=parameterMap
public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) { id = applyCurrentNamespace(id, false); ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build(); configuration.addParameterMap(parameterMap); return parameterMap; }
- ⑥ 重复执行①-⑤
这里引申看一下ParameterMap类。这个类比较有意思,其有三个成员属性以及一个静态嵌套类Builder
(注意这里我们没有称之为内部类)。
//... public class ParameterMap { private String id; private Class<?> type; private List<ParameterMapping> parameterMappings; private ParameterMap() { } public static class Builder { private ParameterMap parameterMap = new ParameterMap(); //为parameterMap 实例属性赋值 public Builder(Configuration configuration, String id, Class<?> type, List<ParameterMapping> parameterMappings) { parameterMap.id = id; parameterMap.type = type; parameterMap.parameterMappings = parameterMappings; } public Class<?> type() { return parameterMap.type; } public ParameterMap build() { //lock down collections parameterMap.parameterMappings = Collections.unmodifiableList(parameterMap.parameterMappings); return parameterMap; } } //... }
内部类的实例化对象需要绑定一个外围类的实例化对象,而静态嵌套类的实例化对象不能也无法绑定外围类的实例化对象。
此外MappedStatement、ResultMap也有各自的Builder来创建mappedStatement与resultMap实例对象。
核心方法buildStatementFromContext
private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }
代码解释如下:
① 判断是否配置了DatabaseId,如果配置了则执行②,否则执行③;
② 执行buildStatementFromContext(list, configuration.getDatabaseId());
③ 执行buildStatementFromContext(list, null);
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
代码解释如下:
①循环遍历select|insert|update|delete结点
② 为每个结点创建XMLStatementBuilder 然后解析
③ 如果当前结点解析过程中报了异常,则将当前statementParser放入configuration实例的Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();属性中,然后留给mybatis的“后手”parsePendingResultMaps解决
④ XMLStatementBuilder
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
代码解释如下:
① 获取当前结点配置的 id和databaseId属性
② databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)根据全局配置的requiredDatabaseId和当前结点的databaseId判断是否解析当前结点
③ 如果configuration配置了requiredDatabaseId且requiredDatabaseId.equals(databaseId)则进行解析否则不解析当前结点
④ 如果configuration没有配置requiredDatabaseId但是当前结点配置了databaseId,不解析当前结点直接返回
⑤ 如果configuration已经有了当前id对应的MappedStatement,则不解析当前结点直接返回
⑥ 解析SqlCommandType与isSelect、flushCache、useCache、resultOrdered
⑦ 解析<include/>结点、parameterType、<selectKey/>、keyGenerator最后获取sqlSource
⑧ 解析StatementType,默认是PREPARED
⑨ 解析fetchSize、timeout、parameterMap、resultType、resultMap、resultSetType、keyProperty、keyColumn、resultSets
⑩ builderAssistant.addMappedStatement获取MappedStatement并放入configuration的protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>集合中
SqlSource有哪些信息?
顾名思义,包含了当前节点解析的SQL语句、参数映射以及全局配置实例configuration。
BoundSql
与SqlSource
紧密相关的是BoundSql
。与SqlSource
不同的是,BoundSql
多了参数值对象(可能是单个值,也可能是map)。
BoundSql主要属性和构造函数如下:
public class BoundSql { private String sql; private List<ParameterMapping> parameterMappings; private Object parameterObject; private Map<String, Object> additionalParameters; private MetaObject metaParameters; public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) { this.sql = sql; this.parameterMappings = parameterMappings; this.parameterObject = parameterObject; this.additionalParameters = new HashMap<String, Object>(); this.metaParameters = configuration.newMetaObject(additionalParameters); } //... }
一个BoundSql实例可能如下:
⑤ MapperBuilderAssistant
public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize, Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType, boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty, String keyColumn,String databaseId,LanguageDriver lang,String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }
代码解释如下:
① applyCurrentNamespace给ID加上namespace
② new MappedStatement.Builder创建一个MappedStatement实例,先在构造方法中进行初步复制,然后使用方法参数给MappedStatement实例进行进一步赋值,注意哦,这里同样设置了结点的二级缓存引用cache(currentCache)
③ 尝试获取ParameterMap ,如果不为null,则更新MappedStatement实例的parameterMap属性值
④ 最后得到的MappedStatement 实例,放到configuration实例的Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>,会放入两组对象,如下所示:
{com.mybatis.dao.EmployeeMapper.getEmpByIds=org.apache.ibatis.mapping.MappedStatement@279fedbd, getEmpByIds=org.apache.ibatis.mapping.MappedStatement@279fedbd}
如下图所示,MappedStatement 实例可能如下:
⑥ 解析完mapper结点后的动作
再回头看下方法:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
核心方法bindMapperForNamespace
XMLMapperBuilder
的bindMapperForNamespace
如下:
private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { // ignore, bound type is not required } if (boundType != null && !configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } }
代码解释如下:
① 获取builderAssistant的namespace,如果为null直接结束;
② 解析namespace对应的Class类型boundType ;
③ 如果boundType 不为null并且configuration实例的MapperRegistry mapperRegistry = new MapperRegistry(this);成员实例的private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();中没有boundType,执行如下步骤:
④ 以"namespace:"+namespace为值放入configuration实例的Set<String> loadedResources = new HashSet<>();成员属性中
⑤ mapperRegistry.addMapper(type);
核心方法MapperRegistry.addMapper(Class type)
方法源码如下:
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<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
代码解释如下:
① 判断当前type是否为接口,如果不是直接结束;
② 判断Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>()是否已经有了type,如果是,则抛出异常;
③ 为当前接口创建MapperProxyFactory实例,然后放入knownMappers 中-knownMappers.put(type, new MapperProxyFactory<>(type));
{interface com.mybatis.dao.EmployeeMapper=org.apache.ibatis.binding.MapperProxyFactory@6913c1fb}
④ 为当前接口创建MapperAnnotationBuilder,然后进行解析(mapper接口上面是可以加注解的)
至于后面的parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();,则是mybatis提供的后手|二次解析,获取configuration实例的如下属性,然后遍历进行二次处理。
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>(); protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>(); protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();