MyBatis启动:MapperStatement创建
参考:http://blog.csdn.net/ashan_li/article/details/50351080
MappedStatement说明
一个MappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条SQL语句。其属性有
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 | //节点中的id属性加要命名空间 private String id; //直接从节点属性中取 private Integer fetchSize; //直接从节点属性中取 private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; //对应一条SQL语句 private SqlSource sqlSource; //每条语句都对就一个缓存,如果有的话。 private Cache cache; //这个已经过时了 private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; //SQL的类型,select/update/insert/detete private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; //是否有内映射 private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; |
Mapper是接口,用来声明持久层的方法,而Mapper配置对应的XML,决定了方法的执行的内容,决定持久层方法的行为。在MyBatis启 动时,会解析这些包含SQL的XML文件,并将其包装成为MapperStatement对象,并将MapperStatement注册到全局的 configuration对象上,接下来就深入的了解代码的实现。
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 | 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 { String resource = child.getStringAttribute( "resource" ); String url = child.getStringAttribute( "url" ); String mapperClass = child.getStringAttribute( "class" ); 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(); } 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." ); } } } } } |
从 源码中就可以看出,配置Mapper时,可以配置package熟悉,注册包下所有的接口。还可以从资源中比如硬盘上,网络中,去加载XML文件。注册过 程是通过注册器MapperRegistry来完成的。注册的容器是一个map,Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();。
key是mapper的接口完整类名,value是mapper的代理工厂。注册完成后,还要做解析XML文件操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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)); // 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); } } } } |
下面 是解析的代码
MyBatis通过替换mapper完整类名中的“.”,替换成为“/”,然后加上后缀“.xml”,拼成XML资源路径,然后判断是否已加载过XML,没有的话加载XML文件,然后使用xmlMapperBuilder建造者解析XML中的元素。
resource是创建建造者的构造参数,type.getClass(),就是mapper的类型。判断然后还没有加载mapper,就开始解析XML文件中的mapper节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute( "namespace" ); if (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. Cause: " + e, e); } } |
解 析时,先设置命名空间。然后解析cache-ref元素,可以使用其他命名空间的的缓存。在configuration对象上有一个 cacheRefMap用来维护引用缓存的关系。并且引用其他命名空间的引用指向助手类的currCache属性上。如果被指向的命名空间还未加载,则抛 出异常,并且往configuration对象上添加未处理的缓存引用chcheRef。
解析缓存元素,可以使用type属性配置自定义的缓存,否则使用默认 的PERPETUAL。然后用别名注册器注册缓存类。接下来注册缓存的回收算法,缓存大小,过期时间,是否只读等属性。然后由助手类通过反射创建一个具体 的Cache对象。然后注册到configuration全局对象上。
下一步是解析parameterMap,新版中已经不推荐配置这个属性了,属于老方法。
参数Map映射已经被淘汰,但是结果集映射还很有用。接下来就是解析 resultMap。解析resultMap的元素比较多,解析完成后,还会根据解析到的映射关系创建一个结果处理器对象 resultMapResolver,后面对数据库操作时,用来处理列和属性的类型转换。
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 | 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 { ArrayList<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; } } |
解析来继续解析SQL片段,用来复用的SQL。助手类会将SQL片段的ID前面加上当前命名空间和一个点,用来和其他命名空间区别开。然后将SQL片段加载到configuration全局对象的sqlFragments对象上保存。
1 2 3 4 5 6 7 8 | private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception { for (XNode context : list) { String databaseId = context.getStringAttribute( "databaseId" ); String id = context.getStringAttribute( "id" ); id = builderAssistant.applyCurrentNamespace(id, false ); if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context); } } |
最后是重头戏,解析增删改查节点,创建Statement对象。同样是通过建造者模式来创建语句对象,建造者的构造参数包括全局配置信息,当前命名空间助手,XML配置信息和数据库ID。
首先还是解析XML文件的各个属性,然后处理<include>和<selectKey>片段。根据include标签中的refid到全局配置中取对应的SQL片段。根据selectKey的配置信息,创建一个MapperStatement,并且添加到全局配置中,然后移除selectKey节点。
接下来的操作,也是根据配置的属性,然后通过建造者创建mappedStatement对象。并添加到configuration全局对象上。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了