您好,登錄后才能下訂單哦!
MybatisSqlSessionFactoryBuilder源碼的示例 分析,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
public static void main(String[] args) { try { // 基本mybatis環境 // 1.定義mybatis_config文件地址 String resources = "mybatis_config.xml"; // 2.獲取InputStreamReaderIo流 Reader reader = Resources.getResourceAsReader(resources); // 3.獲取SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); // 4.獲取Session SqlSession sqlSession = sqlSessionFactory.openSession(); // 5.操作Mapper接口 UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserEntity user = mapper.getUser(2); System.out.println(user.getName()); } catch (Exception e) { e.printStackTrace(); } }
// 2.獲取InputStreamReaderIo流
Reader reader = Resources.getResourceAsReader(resources);
public static Reader getResourceAsReader(String resource) throws IOException { InputStreamReader reader; if (charset == null) { reader = new InputStreamReader(getResourceAsStream(resource)); } else { reader = new InputStreamReader(getResourceAsStream(resource), charset); } return reader; }
通過上述代碼可知:使用了門面模式:定義了Resource類,把復雜過程封裝起來,方便用戶使用,返回reader為InputStreamReader,指的是讀取的mybatis_config.xml文件,斷點調試結果如下:
// 3.獲取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
進入SqlSessionFactoryBuilder()構造函數如下:
public SqlSessionFactoryBuilder() { }
可知,無參構造函數沒用做任何事情,再進入build(reader)源碼,reader參數為InputStream流
public SqlSessionFactory build(Reader reader) { return this.build((Reader)reader, (String)null, (Properties)null); }
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException var13) { ; } } return var5; }
我們來分析下XMLConfigBuilder這個類是干嘛的,進入XMLConfigBuilder構造函數如下:
public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); }
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); this.localReflectorFactory = new DefaultReflectorFactory(); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
進入super()代碼如下:
public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); }
通過上述代碼可知:this.parsed = false;后面有用,這里先提下。返回原先執行處:var5 = this.build(parser.parse());
var5 = this.build(parser.parse());
進入parser.parse()這個方法,代碼如下:
public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
由前面設置了this.parsed = false,可知this.parsed為false,就進入else分支,讀者這個時候就有疑問了,為啥要設置this.parsed = false呢?
我們通過else分支可知,又設置了 this.parsed = true;說明再下一次再次進入parse方法的時候,this.parsed=true會直接拋出異常。
這里我們可以總結下:
為什么XMLConfigBuilder只能被使用一次呢?
答:因為我們的Configuration是一個全局的,所以只能被解析一次。
多次解析的話,會拋出:Each XMLConfigBuilder can only be used once.異常,防止用戶私自調用parse()方法再去重復解析,因為配置文件是全局的,不能多次解析。
進入else分支的下面這個代碼中:
this.parseConfiguration(this.parser.evalNode("/configuration"));
private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectionFactoryElement(root.evalNode("reflectionFactory")); this.settingsElement(root.evalNode("settings")); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
我們先看看mybatis_config.xml配置文件的內容:
<configuration> <!-- 環境配置 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!-- 數據庫連接相關配置 ,這里動態獲取config.properties文件中的內容--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- mapping文件路徑配置 --> <mappers> <mapper resource="mappers/UserMapper.xml"/> </mappers> </configuration>
我們先進入下面這行代碼:因為這個environments在我們配置文件中配置了,我們先分析它:
this.environmentsElement(root.evalNode("environments"))
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (this.environment == null) { this.environment = context.getStringAttribute("default"); } Iterator i$ = context.getChildren().iterator(); while(i$.hasNext()) { XNode child = (XNode)i$.next(); String id = child.getStringAttribute("id"); if (this.isSpecifiedEnvironment(id)) { TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager")); DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource); this.configuration.setEnvironment(environmentBuilder.build()); } } } }
通過斷點調試environmentsElement()代碼結果如下:
我們看下這段代碼:
this.configuration.setEnvironment(environmentBuilder.build());
public void setEnvironment(Environment environment) { this.environment = environment; }
到這里我們就明白了:這里將解析的XML結點封裝成Environment對象,再把Environment對象設置給Configuration對象中。也就是解析XML,再把XML轉為Configuration實體類
到這里我們再來分析:mappers結點在配置文件中配置了,我們也來分析下,下面是mapper.xml配置文件的內容,看下是如何轉化為實體對象保存起來的:
<mapper namespace="com.mayikt.mapper.UserMapper"> <!-- 在select標簽中編寫查詢的SQL語句, 設置select標簽的id屬性為getUser,id屬性值必須是唯一的,不能夠重復 使用parameterType屬性指明查詢時使用的參數類型,resultType屬性指明查詢返回的結果集類型 resultType="com.mayikt.entity.User"就表示將查詢結果封裝成一個User類的對象返回 User類就是users表所對應的實體類 --> <!-- 根據id查詢得到一個user對象 --> <select id="getUser" parameterType="int" resultType="com.mayikt.entity.UserEntity"> select * from user where id=#{id} </select> </mapper>
this.mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception { if (parent != null) { Iterator i$ = parent.getChildren().iterator(); while(true) { while(i$.hasNext()) { XNode child = (XNode)i$.next(); String resource; if ("package".equals(child.getName())) { //注解方式配置掃包package resource = child.getStringAttribute("name"); this.configuration.addMappers(resource); } else { //resource 方式 resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream inputStream; if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); inputStream = Resources.getResourceAsStream(resource); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); inputStream = Resources.getUrlAsStream(url); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments()); mapperParser.parse(); } else { if (resource != null || url != null || mapperClass == null) { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } Class<?> mapperInterface = Resources.classForName(mapperClass); this.configuration.addMapper(mapperInterface); } } } return; } } }
通過上述代碼可知,配置方式有兩種:一種是注解形式掃包,第二種是resource方式
我們是resource方式的配置,所以進入else分支:
由上面斷點分析可知,這里會讀取mapper.xml配置文件的內容,轉化為inputStream流,再解析mapper.xml配置文件
XMLMapperBuilder類的作用:解析mapper配置文件得到Configuration對象,我們看下XMLMapperBuilder怎么去解析mapper配置文件
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } 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; }
最終進入:
mapperParser.parse()
public void parse() { if (!this.configuration.isResourceLoaded(this.resource)) { this.configurationElement(this.parser.evalNode("/mapper")); this.configuration.addLoadedResource(this.resource); this.bindMapperForNamespace(); }
進入addLoadedResource()方法:
public void addLoadedResource(String resource) { this.loadedResources.add(resource); }
protected final Set<String> loadedResources;
public Configuration() { this.loadedResources = new HashSet(); }
通過上述代碼可知:loadedResources存放的都是mybatis映射的文件路徑地址【mapper.xml】, 使用HashSet集合存放
存放進去之后,斷點如下:
我們進入下面這個方法:
this.bindMapperForNamespace();
private void bindMapperForNamespace() { String namespace = this.builderAssistant.getCurrentNamespace(); //拿到mapper.xml里面配置的namespace,這里是com.mayikt.mapper.UserMapper if (namespace != null) { Class boundType = null; try { boundType = Resources.classForName(namespace); //通過Java反射機制幫我去查找,這里得到interface com.mayikt.mapper.UserMapper } catch (ClassNotFoundException var4) { ; } if (boundType != null && !this.configuration.hasMapper(boundType)) {//判斷mapper.xml配置文件是否注冊過 this.configuration.addLoadedResource("namespace:" + namespace); this.configuration.addMapper(boundType); } } }
先看看addMapper方法:
this.configuration.addMapper(boundType);
public <T> void addMapper(Class<T> type) { this.mapperRegistry.addMapper(type); }
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { //判斷是否是接口類型 if (this.hasMapper(type)) { //再次判斷是否注冊過,如果注冊過,則拋出異常 throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { this.knownMappers.put(type, new MapperProxyFactory(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { this.knownMappers.remove(type); } } }
this.knownMappers.put(type, new MapperProxyFactory(type));
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
由上述代碼可知:mapperRegistry作用是:存放dao層mapper接口,debug結果如下:
最后,我們來看看loadedResources里面的東西:存放的是userMapper的配置文件
再看看mapperRegistery里面的東西:存放的是mapper接口
最后,我們回到開始的parse()方法,上述代碼執行完this.parseConfiguration(this.parser.evalNode("/configuration"))方法之后,返回configuration對象
public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
到這里,我們就結束了源碼分析,下面總結下大體流程:
獲取本地InputStreamReader對象(mybatis配置文件)
調用SqlSessionFactoryBuilder
###再使用XMLConfigBuilder解析mybatis配置文件,裝配到Configuration中。
將配置文件中的Mapper添加到Configuration mapperRegistry實現注冊。
備注:mapperRegistry存放當前所有的mapper接口。
loadedResources里面的東西:存放的是userMapper的配置文件
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。