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語句
