Mybatis初始化过程详解
spring.xml配置如下:
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"> <property name="driver" value="com.mysql.jdbc.Driver"/> <!--配置url地址的时候需要开启useUnicode且将encoding编码定义为UTF-8 否则插入数据的时候会出现???乱码 而&是因为如果单纯以&符号做拼接的话没法识别,要以&的形式转义一次 --> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean id="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean"> <!--configLocation用于配置mybatis的配置文件地址 dataSource用于配置mybatis对应的数据源信息 mapperLocations用于配置xml文件扫描的路径 typeAliasesPackage对象的别名配置 --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="dataSource" ref="dataSource"></property> <!-- 该参数也可以放在mybatis-config中进行配置 所以该处省略即可 <property name="mapperLocations" value="classpath:com/demo/dao/*Mapper.xml" ></property> <property name="typeAliasesPackage" value="com.demo.bean"></property>--> </bean>
mybatis-config.xml配置如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 参数设置 --> <settings> <!-- 通过设置该参数可以将数据库cc_xx带下划线的参数默认映射到java属性中ccXx字段上 默认该属性为false --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 全局设置延迟加载 默认为false 如果不设置或者设置为false则全都正常加载--> <setting name="aggressiveLazyLoading" value="true"></setting> <!--全局的二级缓存的开关,默认就是开着的true。由于二级缓存是和命名空间绑定在一起的, 想要使用这个二级缓存功能只需要开启后在每个xml文件的根节点mapper标签内加入<cache/>即可 --> <setting name="cacheEnabled" value="true"/> </settings> <!-- 别名定义 --> <typeAliases> <!-- typeAlias 将type实体类重命名为country --> <!-- 定义别名后在mapper.xml文件中直接使用别名即可(resultType='country') 不需要再写全称了! <typeAlias alias="country" type="org.web.blog.bean.Country"></typeAlias>--> <!-- package可以取代typeAlias 它是通过扫描路径下所用的bean 然后通过默认规则 将bean的首字母小写作为默认别名 也可以在bean上通过@Alias("xxx")来手动创建别名 --> <package name ="com.demo.bean"></package> </typeAliases> <!-- mysql 配置节点 可以配置多个连接节点 具体使用哪个节点的配置可以由default参数来定义,default与子节点的id相关联--> <environments default="xs"> <!-- 正式环境节点配置 --> <environment id="xs"> <!--JDBC MANAGED JDBC:这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。 MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文). 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。 --> <transactionManager type="JDBC"/> <!-- UNPOOLED POOLED JNDI UNPOOLED:Mybatis会为每一个数据库操作创建一个新的连接,并关闭它,该方式 适用于只有小规模数量并发用户的简单应用程序上 POOLED:Mybatis会创建一个数据库连接池,连接池中的一个连接将会被用作数据库操作 一旦数据库操作完成,就会将次连接返回给连接池 JNDI:Mybatis从在应用服务器向配置好的JNDI数据源dataSource获取数据库连接 生产环境优先考虑 --> <dataSource type="UNPOOLED"> <property name="driver" value ="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> <!-- 测试环境节点配置 <environment id="xss"> <dataSource> </dataSource> </environment> --> </environments> <mappers> <!--直接映射到相应的mapper文件 <mapper resource="com/demo/dao/UserMapper.xml"/>--> <!--扫描路径下java 接口文件,然后通过文件路径的全限定名作为路径去找对应的xml文件 xx.xx.java 对应xx.xx.xml 如果xml文件与java文件的路径不一致 则映射失败,这个时候如果想单纯使用java文件 可以在接口方法上使用注解的方式来实现sql即可--> <package name="com.demo.dao"/> </mappers> </configuration>
这2个文件就是Mybatis所有的配置参数的文件了,有了这两个文件Mybatis的工厂就能够初始化。
下面来说说具体的初始化过程
//通过Reader将配置文件读入 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); //FIXME 数据库初始化问题。5.0版本 drive驱动异常 6.0就不会 //通过SqlSessionFactoryBuilder创建以Reader读取的内容为基础的SqlSessionFactory工厂类 //在创建SqlSessionFactory工厂类创建的过程中会去解析.xml配置文件中的配置属性及mappers映射的方法信息 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); //然后通过工厂类创建一个SqlSession,就可以使用SqlSession执行sql了 //SqlSession中的方法是所用mybatis-config.xml映射的方法 如果这些方法中有重名的方法 就会出现方法名引用 //如果多个xml文件中出现重名的sql方法时可以用全限定名来分别方法是属于哪个文件下的xx.xx.selectAll SqlSession sqlSession = sqlSessionFactory.openSession();
可以看到系统通过读取mybatis-config.xml配置文件 然后通过将读取后的参数交给SqlSessionFactory工厂进行节点解析,并创建相应的SqlSession实例。通过创建
后的SqlSession实例就可以实现sql的实现了。
可以看到关键的流程就是在SqlSessionFactory中得以实现,接下来看看SqlSessionFactory是如何加载这些配置参数并返回的。
它分别实现了
FactoryBean<SqlSessionFactory>,
InitializingBean,
ApplicationListener<ApplicationEvent>
这三个接口。
实现了InitializingBean 后可以保证bean在初始化的时候,bean中的afterPropertiesSet()方法得以运行
/** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); }
可以从源码中看到引用了一个buildSqlSessionFactory()方法来初始化sqlSessionFactory。
下面是buildSqlSessionFactory()的源码。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return this.sqlSessionFactoryBuilder.build(configuration); }
可以从源码中看到 SqlSessionFactory创建的最终参数是configuration。
而configuration的参数就是通过xmlConfigBuilder这个类解析xml文件并读取各个节点产生的一个初始化参数对象
比较有意思的是XMLConfigBuilder 这个类继承自 BaseBuilder,而BaseBuilder中定义了final关键字的configuration对象
从而当通过XMLConfigBuilder.getConfiguration 获得configuration对象时,这个对象也是final属性,即BaseBuilder定义的那个
所以SqlSessionFactoryBean中buildSqlSessionFactory()方法内定义的局部变量configuration 被赋值的那刻起,指向的内存地址就已经与XMLConfigBuilder中的configuration
是一样的,也就是说这2个对象是相同的。
这个时候再通过xmlConfigBuilder.parse()方法的调用
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
可以看到是在解析xml文件中的各个标签,然后一次set到configuration 对象中去。
这个时候buildSqlSessionFactory()方法中
return this.sqlSessionFactoryBuilder.build(configuration);
这个configuration参数就已经被初始化完毕。里面包含了mybatis-confg.xml中各个标签的属性等。