解析mybatis-config.xml和mapper/*.xml文件
解析xml配置是mybatis实例化SqlSessionFactory的开始
mybatis解析xml文件时有一个共同的实现,就是先根据xml文件流创建此文件的创建者
然后再根据其创建者调用本身的 parse()方法完成解析,最后都会放入Configuration对象中供全局使用
- 创建SqlSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
//声明配置类名称
String resource = "mybatis-config.xml";
// 获取配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 用输入流创建SqlSessionFactory
return new SqlSessionFactoryBuilder().build(inputStream);
}
- new SqlSessionFactoryBuilder().build(inputStream);
new SqlSessionFactoryBuilder().build(inputStream);有多个实现类,其中输入流传参就是其中一种
// SqlSessionFactoryBuilder::build()
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
- build(inputStream, null, null);
// SqlSessionFactoryBuilder::build(),继续调用build方法的另外一种实现方式
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 获取XMLConfig文件的创建者,使用此创建者对象可以解析config.xml对象
/**
* inputStream:流文件
* environment:环境配置,mysql服务器地址,用户名,密码等信息
* properties:属性
*/
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// xml配置文件解析
return build(parser.parse());
} catch (Exception e) {
...
} finally {
// 关闭输入流
...
}
}
4.build(parser.parse());
parser.parse() 对文件进行解析
//XMLConfigBuilder::parse()
public Configuration parse() {
// 文件是否解析过,每个实例化XMLConfigBuilder对象里面都有一个布尔值,parsed,标记此实例化对象
// 中的config.xml文件是否已经解析,默认值是未解析,解析完成后设置成已解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 设置成已解析(这里可能不在解析完成后设置,可能是因为解析需要花费时间,而在等待过程中,可能又有代码进来解析)
// 写在这里可以防止这种情况发生,保障每次在解析的配置文件只有一个
parsed = true;
// 解析xml文件
//parser.evalNode("/configuration") 获取流文件中,<configuration>标签包裹的所有值
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
- 解析配置文件 parseConfiguration(parser.evalNode("/configuration"));
mybatis-config.xml文件中没有的标签先略过
<!--environments 标签的值-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--数据库地址-->
<property name="url" value="jdbc:mysql://localhost:3306/path2java?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&verifyServerCertificate=false"/>
<!--数据库账号-->
<property name="username" value="root"/>
<!--数据库密码-->
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--mappers标签的值-->
<mappers>
<!--映射文件路径-->
<mapper resource="mapper/AdvertiseSiteMapper.xml"/>
</mappers>
private void parseConfiguration(XNode root) {
try {
...
// read it after objectFactory and objectWrapperFactory issue #631
// 获取<environments>标签没所有值
// 并在解析完成后,生成一个Environment对象,并且放入Configruation对象的environment对象中,以后每次创建sqlSession都会携带里面的数据库信息
environmentsElement(root.evalNode("environments"));
// 获取<mappers>标签的所有属性
// 拿到此处的信息,相当于在yml文件中配置的mapper.xml文件地址:mapper/*.xml
// 此对象解析完成后会将mapper.xml文件的内容生成sql对象,存放到Configruation中
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- 解析mapper/AdvertiseSiteMapper.xml文件
mapperElement(root.evalNode("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 {
// 获取文件xml文件路径 resource="mapper/AdvertiseSiteMapper.xml"
String resource = child.getStringAttribute("resource");
// 获取url地址
String url = child.getStringAttribute("url");
// 获取class类
String mapperClass = child.getStringAttribute("class");
//解析只有resource存在时的情况
if (resource != null && url == null && mapperClass == null) {
// 获取 mapper/AdvertiseSiteMapper.xml 文件的文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 根据mapper/AdvertiseSiteMapper.xml文件常见其mapper创建者
// 这一步做的主要功能就是将文件流放入mapperParser对象中
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 根据其创建者信息,解析mapper/AdvertiseSiteMapper.xml文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
...
} else if (resource == null && url == null && mapperClass != null) {
...
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
- 开始解析 mapper/AdvertiseSiteMapper.xml
XMLMapperBuilder::parse();
public void parse() {
// 根据文件全路径名,判断是否已经解析过此xml文件
if (!configuration.isResourceLoaded(resource)) {
// 获取文件流中的 <mapper>标签的所有信息
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
- 解析mapper/AdvertiseSiteMapper.xml的标签下的所有属性
<mapper namespace="cn.kgm.mapper.MyBatisMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.kgm.entity.MyBatis">
<id column="id" property="id"/>
<id column="content" property="content"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="BaseColumnList">
id,content,create_time
</sql>
<select id="getOne" resultType="cn.kgm.entity.MyBatis" parameterType="java.lang.Long">
select
<include refid="BaseColumnList"/>
from my_batis where id = #{id}
</select>
</mapper>
- configurationElement(parser.evalNode("/mapper"));
解析mapper标签对象
//XMLMapperBuilder::configurationElement();
private void configurationElement(XNode context){
try{
// 对应的Mapper接口 namespace="cn.kgm.mapper.MyBatisMapper"
String namespace=context.getStringAttribute("namespace");
if(namespace==null||namespace.equals("")){
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置Mapper对象地址
builderAssistant.setCurrentNamespace(namespace);
// 缓存相关配置
cacheRefElement(context.evalNode("cache-ref"));
// 缓存
cacheElement(context.evalNode("cache"));
// 参数映射关系
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 返回值映射关系
resultMapElements(context.evalNodes("/mapper/resultMap"));
// SQL 预设模板
sqlElement(context.evalNodes("/mapper/sql"));
// 获取select,insert,update,delete四种标签的内容并解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}catch(Exception e){
throw new BuilderException("Error parsing Mapper XML. Cause: "+e,e);
}
}
10.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
获取SQL标签内容,此处只有 select标签有值
<select id="getOne" resultType="cn.kgm.entity.MyBatis" parameterType="java.lang.Long">
select
<include refid="BaseColumnList"/>
from my_batis where id = #{id}
</select>
// XMLMapperBuilder::buildStatementFromContext();
private void buildStatementFromContext(List<XNode> list){
if(configuration.getDatabaseId()!=null){
buildStatementFromContext(list,configuration.getDatabaseId());
}
// 此时走这里
buildStatementFromContext(list,null);
}
private void buildStatementFromContext(List<XNode> list,String requiredDatabaseId){
// 获取四种标签的列表
for(XNode context:list){
// 根据每个标签创建XMLStatementBuilder
final XMLStatementBuilder statementParser=new XMLStatementBuilder(configuration,builderAssistant,context,requiredDatabaseId);
try{
// 解析标签,类似之前各种builder类的parse()方法
statementParser.parseStatementNode();
}catch(IncompleteElementException e){
configuration.addIncompleteStatement(statementParser);
}
}
}
- statementParser.parseStatementNode();
获取select|delete|insert|update四种标签内的所有信息,分别解析
//每一个标签的内容最后都会生成一个MappedStatement对象,最后放入全局配置Configuration中的mappedStatements对象中
//StrictMap 强校验的Map,mybatis为了防止值和键为null而重写的Map
protected final Map<String, MappedStatement> mappedStatements=new StrictMap<MappedStatement>("Mapped Statements collection");