mybatis源碼解析6---MappedStatement解析


MappedStatement類位於mybatis包的org.apache.ibatis.mapping目錄下,是一個final類型也就是說實例化之后就不允許改變

MappedStatement對象對應Mapper.xml配置文件中的一個select/update/insert/delete節點,描述的就是一條SQL語句,屬性如下:

 

 1   private String resource;//mapper配置文件名,如:UserMapper.xml
 2   private Configuration configuration;//全局配置
 3   private String id;//節點的id屬性加命名空間,如:com.lucky.mybatis.dao.UserMapper.selectByExample
 4   private Integer fetchSize;
 5   private Integer timeout;//超時時間
 6   private StatementType statementType;//操作SQL的對象的類型
 7   private ResultSetType resultSetType;//結果類型
 8   private SqlSource sqlSource;//sql語句
 9   private Cache cache;//緩存
10   private ParameterMap parameterMap;
11   private List<ResultMap> resultMaps;
12   private boolean flushCacheRequired;
13   private boolean useCache;//是否使用緩存,默認為true
14   private boolean resultOrdered;//結果是否排序
15   private SqlCommandType sqlCommandType;//sql語句的類型,如select、update、delete、insert
16   private KeyGenerator keyGenerator;
17   private String[] keyProperties;
18   private String[] keyColumns;
19   private boolean hasNestedResultMaps;
20   private String databaseId;//數據庫ID
21   private Log statementLog;
22   private LanguageDriver lang;
23   private String[] resultSets;

 

其中StatementType指操作SQL對象的類型,是個枚舉類型,值分別為:

STATEMENT(直接操作SQL,不進行預編譯),

PREPARED(預處理參數,進行預編譯,獲取數據),

CALLABLE(執行存儲過程)

ResultSetType指返回結果集的類型,也是個枚舉類型,值分別為:

FORWARD_ONLY:結果集的游標只能向下滾動

SCROLL_INSENSITIVE:結果集的游標可以上下移動,當數據庫變化時當前結果集不變

SCROLL_SENSITIVE:結果集客自由滾動,數據庫變化時當前結果集同步改變

言歸正傳,現在我們知道一個MappedStatement對象對應一個mapper.xml中的一個SQL節點,而Mapper.xml文件是初始化Configuration對象的時候進行解析加載的,則說明MappedStatement對象就是在初始化Configuration對象的時候創建的,並且是final類型不可更改。

之前我們知道Configuration對象的初始化過程,是通過XMLConfigBuilder類的parse方法進行初始化的,現在來看下是如何初始化MappedStatement對象的,Configuration對象初始化代碼如下:

 1 public Configuration parse() {
 2     if (parsed) {
 3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 4     }
 5     parsed = true;
 6     parseConfiguration(parser.evalNode("/configuration"));
 7     return configuration;
 8   }
 9 
10   private void parseConfiguration(XNode root) {
11     try {
12       Properties settings = settingsAsPropertiess(root.evalNode("settings"));
13       //issue #117 read properties first
14       propertiesElement(root.evalNode("properties"));
15       loadCustomVfs(settings);
16       typeAliasesElement(root.evalNode("typeAliases"));
17       pluginElement(root.evalNode("plugins"));
18       objectFactoryElement(root.evalNode("objectFactory"));
19       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
20       reflectorFactoryElement(root.evalNode("reflectorFactory"));
21       settingsElement(settings);
22       // read it after objectFactory and objectWrapperFactory issue #631
23       environmentsElement(root.evalNode("environments"));
24       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
25       typeHandlerElement(root.evalNode("typeHandlers"));
26       mapperElement(root.evalNode("mappers"));//MappedStatement對象的初始化 27     } catch (Exception e) {
28       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
29     }
30   }

 

parseConfiguration方法是根據XNode對Configuration對象進行屬性賦值,mapperElement方法即解析<mappers>標簽中的內容,mapperElement方法源碼如下:

 1 private void mapperElement(XNode parent) throws Exception {
 2     //parent是Configuration配置文件中的<mappers>標簽
 3     if (parent != null) {
 4        //遍歷<mappers>標簽下的所有子標簽
 5       for (XNode child : parent.getChildren()) {
 6         if ("package".equals(child.getName())) {
 7           //加載package包下的所有mapper
 8           String mapperPackage = child.getStringAttribute("name");
 9           configuration.addMappers(mapperPackage);//加載packege包下的所有mapper
10         } else {
11           //按resource或url或class加載單個mapper
12           String resource = child.getStringAttribute("resource");
13           String url = child.getStringAttribute("url");
14           String mapperClass = child.getStringAttribute("class");
15           if (resource != null && url == null && mapperClass == null) {
16             ErrorContext.instance().resource(resource);
17             InputStream inputStream = Resources.getResourceAsStream(resource);
18             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
19  mapperParser.parse();//解析xml文件流
20           } else if (resource == null && url != null && mapperClass == null) {
21             ErrorContext.instance().resource(url);
22             InputStream inputStream = Resources.getUrlAsStream(url);
23             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
24  mapperParser.parse();//解析xml文件流
25           } else if (resource == null && url == null && mapperClass != null) {
26             Class<?> mapperInterface = Resources.classForName(mapperClass);
27  configuration.addMapper(mapperInterface);//加載指定接口的mapper 28           } else {
29             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
30           }
31         }
32       }
33     }
34   }

 

可以看出共有三種方法可以加載mapper,一個是批量加載指定package下所有mapper,一個是根據mapper接口路徑加載指定mapper,還有一種是解析mapper.xml文件流進行加載,接下來挨個來看下;

先來看個最簡單的,根據指定接口加載mapper,也就是configuration.addMapper(mapperInterface)方法,源碼如下:

Configuration的addMapper方法

1 public <T> void addMapper(Class<T> type) {
2     mapperRegistry.addMapper(type);
3   }

 

調用了mapperRegistry的addMapper方法。mapperRegistry是Configuration類的一個屬性

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

 

根據MapperRegistry的名字可以理解為此類的作用是mapper的注冊中心,用於注冊mapper,MapperRegistry源碼如下:

 1 package org.apache.ibatis.binding;
 2 
 3 import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
 4 import org.apache.ibatis.io.ResolverUtil;
 5 import org.apache.ibatis.session.Configuration;
 6 import org.apache.ibatis.session.SqlSession;
 7 
 8 import java.util.Collection;
 9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.Map;
12 import java.util.Set;
13 
14 public class MapperRegistry {
15 
16   private final Configuration config;//全局配置
17   private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();//已注冊的mapper集合
18 
19   public MapperRegistry(Configuration config) {
20     this.config = config;
21   }
22 
23   @SuppressWarnings("unchecked")
24   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
25     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
26     if (mapperProxyFactory == null) {
27       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
28     }
29     try {
30       return mapperProxyFactory.newInstance(sqlSession);
31     } catch (Exception e) {
32       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
33     }
34   }
35   //判斷指定mapper是否已經存在
36   public <T> boolean hasMapper(Class<T> type) {
37     return knownMappers.containsKey(type);
38   }
39 
40   //新增一個mapper
41   public <T> void addMapper(Class<T> type) {
42     if (type.isInterface()) {
43       if (hasMapper(type)) {
44         throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
45       }
46       boolean loadCompleted = false;
47       try {
48         knownMappers.put(type, new MapperProxyFactory<T>(type));
49         // It's important that the type is added before the parser is run
50         // otherwise the binding may automatically be attempted by the
51         // mapper parser. If the type is already known, it won't try.
52         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
53         parser.parse();
54         loadCompleted = true;
55       } finally {
56         if (!loadCompleted) {
57           knownMappers.remove(type);
58         }
59       }
60     }
61   }
62 
63   //獲取所有mapper集合
64   public Collection<Class<?>> getMappers() {
65     return Collections.unmodifiableCollection(knownMappers.keySet());
66   }
67 
68   
69   //根據package名稱加載包下所有的mapper
70   public void addMappers(String packageName, Class<?> superType) {
71     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
72     resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
73     Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
74     for (Class<?> mapperClass : mapperSet) {
75       addMapper(mapperClass);
76     }
77   }
78 
79   //根據package批量加載mapper
80   public void addMappers(String packageName) {
81     addMappers(packageName, Object.class);
82   }
83   
84 }
85   

 

從源碼可看出MapperRegistry就是Mapper的注冊中心,有兩個屬性一個是全局配置Configuration還有一個是已經加載過的mapper集合 knownMappers

新增一個mapper的方法就是addMapper,就是向knownsMappers集合中put一條新的mapper記錄,key就是mapper的類名全稱,value是這個mapper的代理工廠;

分析到這里,發現Configuration對象初始化的時候會解析所有的xml文件中配置的所有mapper接口,並添加到Configuration的mapper集合knowMappers中,但是貌似還沒有MappedStatement的影子,也沒有看到哪里解析了mapper.xml配置。

不用急,上面源碼的第52行就是了,52到54行的意思目前還沒有看源碼,但是先猜測下:52行是通過Configuration對象和mapper類來構造一個MapperAnnotationBuilder對象,通過字面意思是Mapper的構建類,而第53行的parse(),應該就是解析mapper.xml文件的,第54行標記加載完成,

只有當mapper接口和mapper.xml匹配成功才能叫做是加載成功,所以下一章篇就再來看看MappedStatement是如何創建的。

總結:MappedStatement類就是對應的Mapper.xml中的一個sql語句


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM