mybatis-6-配置解析-mybatis-config.xml

mac2022-06-30  11

一、Xml的加载和解析

1.入口SqlSessionFactoryBuilder.build()

public SqlSessionFactory build(Reader reader, String environment, Properties properties) { //省略所有try..catch..finally XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); }

2.XMLConfigBuilder-解析mybatis-config.xml

private boolean parsed;//标记是否已经解析 private final XPathParser parser;//用来解析Node private Strin environment;//<environment>标签的default属性 private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); 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()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }

上面比较简单,创建用来解析Node的XPathParser、验证xml的Resolve等、保存配置信息的configuration等。

public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); //<setting name="vfsImpl" value="org.apache.ibatis.io.JBoss6VFS"/> loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); }

1) propertiesElement解析properties节点

private void propertiesElement(XNode context) throws Exception { if (context != null) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource");//获取标签的resource属性 String url = context.getStringAttribute("url");//获取标签的url属性 if (resource != null && url != null) {//二者互斥 throw ...//省略 } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url));//加载jdbc.properties } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults);//存到configuration中去 } }

例子如下:

<properties url="file:./src/test/java/org/apache/ibatis/builder/jdbc.properties"> <property name="prop1" value="bbbb"/> </properties> //jdbc.properties内容如下 //driver=org.apache.derby.jdbc.EmbeddedDriver //url=jdbc:derby:ibderby;create=true //username= //password= //解析后得结果如下: // {url=jdbc:derby:ibderby;create=true, password=, prop1=bbbb, driver=org.apache.derby.jdbc.EmbeddedDriver, username=}

2)settingsAsProperties处理<settings>标签

private Properties settingsAsProperties(XNode context) { Properties props = context.getChildrenAsProperties(); //校验属性在configuration中有定义 MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException... } } return props; }

上面将<settings>中的子标签转换为properties的key-value。

//样例如下 <settings>     <setting name="cacheEnabled" value="true"/>     <setting name="lazyLoadingEnabled" value="false"/>     <setting name="multipleResultSetsEnabled" value="true"/>     <setting name="useColumnLabel" value="true"/>     <setting name="useGeneratedKeys" value="false"/>     <setting name="defaultExecutorType" value="SIMPLE"/>     <setting name="defaultStatementTimeout" value="25"/> <setting name="vfsImpl" value="org.apache.ibatis.io.JBoss6VFS"/> </settings>

3) loadCustomVfs加载VFS文件

在<setting>标签中如果配置了VFS类(如上面中vfsImpl),则会调用此方法进行加载,并存到configuration中

private void loadCustomVfs(Properties props) throws ClassNotFoundException { String value = props.getProperty("vfsImpl"); if (value != null) { String[] clazzes = value.split(","); for (String clazz : clazzes) { if (!clazz.isEmpty()) { Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz); configuration.setVfsImpl(vfsImpl); } } } }

4) typeAliasesElement 别名的注册

别名有三种情况,一是指定别名,二是注解Alias指定别名,三是默认别名(类名小写)

<typeAliases> <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/> <package name="org.apache.ibatis.domain.blog"/> </typeAliases> private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) {//扫描某个包下的所有类 String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); if (alias == null) {//未指定别名,默认使用类名小写 typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException... } } } } }

5)pluginElement 自定义插件扩展

自定义插件可以对sql执行过程的某个点进行拦截,需要实现Interceptor接口,使用注解指定需要拦截的方法签名即可。

<plugins> <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin"> <property name="pluginProperty" value="100"/> </plugin> </plugins> private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //resolveClass()方法会调用resolveAlias(alias)注册别名后,在创建实例 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance);//添加到configuration中 } } }

6) objectFactoryElement和objectFactoryElement和reflectorFactoryElement

和上面类似,省略。

7) <environment> 指定多个环境

<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="" value=""/> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default");//获取default属性 } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) {//判断是否和指定的id相同,不是抛出异常 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));//事务标签 //根据type创建数据源工厂,并获取<dataSource>标签下的property,设置到DataSource中 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } }

上面先获取String environment,若未指定则使用默认的。接着判断配置中的<environment>标签的id是否和指定的一样,不一样的话抛出异常。最后获取事务、数据源工厂等,最后用创建者模式将这些属性存入到Environment类中。

8)<databaseIdProviderElement>  指定数据库类型

<databaseIdProvider type="DB_VENDOR"> <property name="Apache Derby" value="derby"/> </databaseIdProvider> private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); if ("VENDOR".equals(type)) { type = "DB_VENDOR"; } Properties properties = context.getChildrenAsProperties(); //创建VendorDatabaseIdProvider databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());//获取厂商的名字 configuration.setDatabaseId(databaseId); } }

获取厂商名字的方法如下:

//VendorDatabaseIdProvider public String getDatabaseId(DataSource dataSource) { return getDatabaseName(dataSource); } private String getDatabaseName(DataSource dataSource) throws SQLException { String productName = getDatabaseProductName(dataSource); if (this.properties != null) { for (Map.Entry<Object, Object> property : properties.entrySet()) { if (productName.contains((String) property.getKey())) {//遍历配置的property name return (String) property.getValue(); } } return null; } return productName; } private String getDatabaseProductName(DataSource dataSource) throws SQLException { Connection con = null; try { con = dataSource.getConnection(); DatabaseMetaData metaData = con.getMetaData(); return metaData.getDatabaseProductName(); } }

9)mapperElement 引用其它配置

<mappers> <mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/> <mapper url="file:./src/test/java/org/apache/ibatis/builder/NestedBlogMapper.xml"/> <mapper class="org.apache.ibatis.builder.CachedAuthorMapper"/> <package name="org.apache.ibatis.builder.mapper"/> </mappers> 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.... } } } } }

 对于 <mappers>标签的解析中可以发现,引入文件有四种方式package(扫描指定包)、resource、url、class,但同时只能出现上面一种方式。引入的文件需要委托到XMLMapperBuilder中进行解析。至此所有的mybatis-config.xm中配置都已解析完成,XMLMapperBuilder的解析会放在下一篇文章中。

 

最新回复(0)