mybatis源码阅读(三):mybatis初始化(下)mapper解析
轉載自?mybatis源碼閱讀(三):mybatis初始化(下)mapper解析
MyBatis 的真正強大在于它的映射語句,也是它的魔力所在。由于它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼。MyBatis 就是針對 SQL 構建的,并且比普通的方法做的更好。
SQL 映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):
- cache?– 給定命名空間的緩存配置。
- cache-ref?– 其他命名空間緩存配置的引用。
- resultMap?– 是最復雜也是最強大的元素,用來描述如何從數(shù)據(jù)庫結果集中來加載對象。
- parameterMap?– 已廢棄!老式風格的參數(shù)映射。內聯(lián)參數(shù)是首選,這個元素可能在將來被移除,這里不會記錄。
- sql?– 可被其他語句引用的可重用語句塊。
- insert?– 映射插入語句
- update?– 映射更新語句
- delete?– 映射刪除語句
- select?– 映射查詢語句
對每個標簽的屬性以及作用,這里不做解釋,?可以參考官方文檔:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
上一篇文章介紹了mybatis配置文件解析mappers節(jié)點的源碼中有如下語句,從這里得到mapper映射文件時通過XMLMapperBuilder解析的。
一、XMLMapperBuilder
//mapper映射文件都是通過XMLMapperBuilder解析XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse(); //解析mapper文件 public void parse() {// 判斷是否已經加載過改映射文件if (!configuration.isResourceLoaded(resource)) {// 處理mapper節(jié)點configurationElement(parser.evalNode("/mapper"));// 將resource添加到configuration的loadedResources集合中保存 它是HashSet<String>configuration.addLoadedResource(resource);//注冊mapper接口bindMapperForNamespace();}// 處理解析失敗的resultMap節(jié)點parsePendingResultMaps();// 處理解析失敗的cache-ref節(jié)點parsePendingCacheRefs();// 處理解析失敗的sql節(jié)點parsePendingStatements(); } 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);// 解析cache-ref節(jié)點cacheRefElement(context.evalNode("cache-ref"));// 解析cache節(jié)點cacheElement(context.evalNode("cache"));// 解析parameterMap節(jié)點,這個已經被廢棄,不推薦使用parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 解析resultMap節(jié)點resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析sql節(jié)點sqlElement(context.evalNodes("/mapper/sql"));// 解析statement 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);} }1.cache節(jié)點
????????它通過調用CacheBuilder的相應方法完成cache的創(chuàng)建。每個cache內部都有一個唯一的ID,這個id的值就是namespace。創(chuàng)建好的cache對象存入configuration的cache緩存中(該緩存以cache的ID屬性即namespace為key,這里再次體現(xiàn)了mybatis的namespace的強大用處)。
/*** cache- 配置本定命名空間的緩存。* type- cache實現(xiàn)類,默認為PERPETUAL,可以使用自定義的cache實現(xiàn)類(別名或完整類名皆可)* eviction- 回收算法,默認為LRU,可選的算法有:* LRU– 最近最少使用的:移除最長時間不被使用的對象。* FIFO– 先進先出:按對象進入緩存的順序來移除它們。* SOFT– 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。* WEAK– 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。* flushInterval- 刷新間隔,默認為1個小時,單位毫秒* size- 緩存大小,默認大小1024,單位為引用數(shù)* readOnly- 只讀* @param context* @throws Exception*/ private void cacheElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size");boolean readWrite = !context.getBooleanAttribute("readOnly", false);boolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);} } public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class)).addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();configuration.addCache(cache);currentCache = cache;return cache; }2.cache-ref節(jié)點
????????cacheRefElement方法負責解析cache-ref元素,它通過調用CacheRefResolver的相應方法完成cache的引用。創(chuàng)建好的cache-ref引用關系存入configuration的cacheRefMap緩存中。
/*** cache-ref–從其他命名空間引用緩存配置。* 如果你不想定義自己的cache,可以使用cache-ref引用別的cache。* 因為每個cache都以namespace為id,* 所以cache-ref只需要配置一個namespace屬性就可以了。* 需要注意的是,如果cache-ref和cache都配置了,以cache為準。* @param context*/ private void cacheRefElement(XNode context) {if (context != null) {configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));try {cacheRefResolver.resolveCacheRef();} catch (IncompleteElementException e) {configuration.addIncompleteCacheRef(cacheRefResolver);}} }3.resultMap節(jié)點
????????resultMapElement方法負責解析resultMap元素,它通過調用ResultMapResolver的相應方法完成resultMap的解析。resultMap節(jié)點下除了discriminator子節(jié)點的其他子節(jié)點都會解析成對應的ResultMapping對象,而每個<resultMap>節(jié)點都會被解析成一個ResultMap對象,創(chuàng)建好的resultMap存入configuration的resultMaps緩存中(該緩存以namespace+resultMap的id為key,這里再次體現(xiàn)了mybatis的namespace的強大用處)。
private void resultMapElements(List<XNode> list) throws Exception {for (XNode resultMapNode : list) {try {resultMapElement(resultMapNode);} catch (IncompleteElementException e) {// ignore, it will be retried}} }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;} }4.sql節(jié)點解析
sql節(jié)點用來定義可重用的sql語句片段, sqlElement方法負責解析sql元素。id屬性用于區(qū)分不同的sql元素,在同一個mapper配置文件中可以配置多個sql元素。
private void sqlElement(List<XNode> list) throws Exception {if (configuration.getDatabaseId() != null) {sqlElement(list, configuration.getDatabaseId());}sqlElement(list, null); }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中保存,其實 構造函數(shù)中可以看到該字段指向了configuration的sqlFragments集合中sqlFragments.put(id, context);}} }private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {if (requiredDatabaseId != null) {if (!requiredDatabaseId.equals(databaseId)) {return false;}} else {if (databaseId != null) {return false;}// skip this fragment if there is a previous one with a not null databaseIdif (this.sqlFragments.containsKey(id)) {XNode context = this.sqlFragments.get(id);if (context.getStringAttribute("databaseId") != null) {return false;}}}return true; }二、XMLStatementBuilder
映射配置文件中還有一類比較重要的節(jié)點需要解析,其實就是select|insert|update|delete 節(jié)點,這些節(jié)點主要用于定義SQL語句,他們不在由XMLMapperBuilder進行解析,而是由XMLStatementBuilder負責進行解析,每個節(jié)點會被解析成MappedStatement對象并存入到configuration對象中去。在這個方法內有幾個重要的步驟,理解他們對正確的配置statement元素很有幫助。
1.MappedStatement
MappedStatement包含了這些節(jié)點的很多屬性,其中比較重要的如下:
private String resource;//節(jié)點中的id 包括命名空間 private SqlSource sqlSource;//SqlSource對象,對應一條SQL語句 private SqlCommandType sqlCommandType;//SQL的類型,insert,delete,select,update解析過程代碼如下:
public void parseStatementNode() {// 獲取sql節(jié)點的id以及databaseId如果和當前不匹配不加載改節(jié)點,// 如果存在id相同且databaseId不為空的節(jié)點也不在加載改節(jié)點String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 獲取節(jié)點的多種屬性Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);// 根據(jù)節(jié)點的名稱設置sqlCommandType的類型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);// 在解析SQL語句之前先處理include節(jié)點XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 處理selectKey節(jié)點processSelectKeyNodes(id, parameterTypeClass, langDriver);// 完成節(jié)點的解析 該部分是核心SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 獲取resultSets keyProperty keyColumn三個屬性String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;// 獲取selectKey節(jié)點對應的selectKeyGenerator的idString 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;}// 通過MapperBuilderAssistant創(chuàng)建MappedStatement對象,// 并添加到configuration.mappedStatements集合中保存builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }2、解析include節(jié)點
在解析statement節(jié)點之前首先通過XMLIncludeTransformer解析include節(jié)點改過程會將include節(jié)點替換<sql>節(jié)點中定義的sql片段,并將其中的${xx}占位符換成真實的參數(shù),
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {if (source.getNodeName().equals("include")) { // ---(2)處理include節(jié)點// 查找refid屬性指向的<sql>,返回的是深克隆的Node對象Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);Properties toIncludeContext = getVariablesContext(source, variablesContext);//遞歸處理include節(jié)點applyIncludes(toInclude, toIncludeContext, true);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 將<include>節(jié)點替換<sql>節(jié)點source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 將<sql>節(jié)點的子節(jié)點添加到<sql>節(jié)點的前面toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 替換后刪除<sql>節(jié)點toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {// ---(1)if (included && !variablesContext.isEmpty()) {// replace variables in attribute valuesNamedNodeMap attributes = source.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {// 遍歷當前sql的子節(jié)點Node attr = attributes.item(i);attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));}}NodeList children = source.getChildNodes();for (int i = 0; i < children.getLength(); i++) {applyIncludes(children.item(i), variablesContext, included);}} else if (included && source.getNodeType() == Node.TEXT_NODE&& !variablesContext.isEmpty()) {// ---(3)// replace variables in text node 替換對應的占位符source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));} }3、解析selectKey節(jié)點
在insert,update節(jié)點中可以定義selectKey節(jié)點來解決主鍵自增問題。
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {String resultType = nodeToHandle.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));String keyProperty = nodeToHandle.getStringAttribute("keyProperty");String keyColumn = nodeToHandle.getStringAttribute("keyColumn");boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));//defaultsboolean useCache = false;boolean resultOrdered = false;KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;Integer fetchSize = null;Integer timeout = null;boolean flushCache = false;String parameterMap = null;String resultMap = null;ResultSetType resultSetTypeEnum = null;// 生成SqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);// selectKey節(jié)點中只能配置select語句SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 創(chuàng)建MappedStatement對象,并添加到configuration的mappedStatements集合中保存builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);id = builderAssistant.applyCurrentNamespace(id, false);MappedStatement keyStatement = configuration.getMappedStatement(id, false);// 創(chuàng)建對應的KeyGenerator(主鍵自增策略),添加到configuration中configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore)); }三、綁定Mapper接口
每個映射配置文件的命名空間可以綁定一個Mapper接口,并注冊到MapperRegistry中。
// 綁定mapper接口 private void bindMapperForNamespace() {//獲取映射文件的命名空間String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 解析命名空間對應的類型 即daoboundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!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);}}} }?
總結
以上是生活随笔為你收集整理的mybatis源码阅读(三):mybatis初始化(下)mapper解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 婴儿游泳馆怎么在手机上年报申报,附详细流
- 下一篇: 教你如何判断电脑硬件和软件好坏如何检查电