Mybatis源码解析-SqlSessionFactory

传统的写法JDBC

        String URL = "jdbc:mysql://127.0.0.1:3306/student?serverTimezone = GMT";
        String USER = "root";
        String PASSWORD = "123456";
        // 1.加载驱动程序
        try {
    
    
            Class.forName("com.mysql.jdbc.Driver");
            // 2.获得数据库链接
            Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 3.通过数据库的连接操作数据库,实现增删改查(使用Statement类)
            //预编译
            String sql="insert into studentTable (id,name,age) values (?,?,?)";
            PreparedStatement statement = conn.prepareStatement(sql);
            statement.setInt(1, 1);
            statement.setString(2, "张三");
            statement.setInt(3, 22);
            int i = statement.executeUpdate();
//			String sql="select * from studentTable where UserName='"+name+"'";
//			Statement statement = conn.createStatement();
//			ResultSet rs = statement.executeQuery(sql);
            System.out.println("插入条数:"+i);

            // 关闭资源
            conn.close();
            statement.close();
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }catch (SQLException e) {
    
    
            e.printStackTrace();
        }

源码SqlSessionFactory的创建

我们从JDBC执行一条sql的过程来分析mybatis是怎么从加载配置文件,比如:全局配置文件、XxxMapper.xml配置文件来执行sql的

String resource = "config/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentTable s=new StudentTable();
s.setId(1);
s.setAge("22");
s.setName("张三");
sqlSession.insert("insertStudent",s);
sqlSession.commit();
sqlSession.close();

首先根据mybatis-config.xml配置文件路径创建输入流,将输入流对象作为参数传到SqlSessionFactoryBuilder中的build方法得到一个SqlSessionFactory对象,源码如下

public class SqlSessionFactoryBuilder {
    
    
//省略其他代码
public SqlSessionFactory build(InputStream inputStream) {
    
    
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    
    
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    
    
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    
    
    try {
    
    
      //解析得到一个XMLConfigBuilder对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment,properties);
      //将Configuration对象传给DefaultSqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
    
    
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
    
    
      ErrorContext.instance().reset();
      try {
    
    
        inputStream.close();
      } catch (IOException e) {
    
    
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    
    
  	//最后默认返回DefaultSqlSessionFactory
    return new DefaultSqlSessionFactory(config);
  }
}

SqlSessionFactory是通过SqlSessionFactoryBuilder工厂类的build方法创建的, 不是通过构造方法直接new出来的。
此外,我们还可以知道,SqlSessionFactory提供了字节流和字符流以及直接的org.apache.ibatis.session.Configuration配置类的三种方式来读取配置文件信息。
但是无论是字符流还是字节流,然后将Configuration设置到SqlSessionFactory(默认的实现类是org.apache.ibatis.session.defaults.DefaultSqlSessionFactory)中的configuraion字段并且返回。
所以其本身也是很简单的,解析xml配置文件的操作都委托给了XMLConfigBuilder类。
接着点开XMLConfigBuilder类看看是怎么创建XMLConfigBuilder对象的,可以看到XMLConfigBuilder 是继承BaseBuilder 建造者对象,看看BaseBuilder 有哪些子类
1.0
点开XMLConfigBuilder,XMLConfigBuilder类的主要代码如下:
XMLConfigBuilder以及解析Mapper.xml文件的XMLMapperBuilder都继承自BaseBuilder。
他们对xml文件的加载和解析都交给了XPathParser,从而最终使用JDK自带的xml解析器而非dom4j、jdom第三方jar包,底层使用xPath进行节点解析。new XPathParser(reader, true, props, new XMLMapperEntityResolver()) 四个参数的含义分别是:配置文件流、是否进行DTD校验、属性配置、xml实体节点解析器。EntityResolver比较好理解,跟Spring的XML标签解析器一样,有默认的解析器,也有自定义的比如tx,dubbo等,主要使用了策略模式,在这里mybatis硬编码为了XMLMapperEntityResolver

public class XMLConfigBuilder extends BaseBuilder {
    
    
	//省略其他代码
	public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    
    
	    this(new XPathParser(inputStream, 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;
	  }
}

XPathParser类代码如下:
补充一下:EntityResolver的作用:EntityResolver的实现类XMLEntityResolver类中引用了本地的DTD文件,和本类在同一个package下,其中的ibatis-3-config.dtd应该主要是用于兼容用途。
其次就是XPathParser做了以下几件事:
1.将是否验证dtd(DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。),节点解析器进行设置,并且初始化xPath,便于xml文件解析和加载。
2.可知,commonConstructor并没有做什么。回过头到createDocument上,其使用了org.xml.sax.InputSource作为参数。代码实现如下:

public class XPathParser {
    
    

  private final Document document;
  private boolean validation;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;

  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    
    
  	// 1.将是否验证dtd,节点解析器进行设置,并且初始化xPath,便于xml文件解析和加载
    commonConstructor(validation, variables, entityResolver);
    // 2.可知,commonConstructor并没有做什么。回过头到createDocument上,其使用了org.xml.sax.InputSource作为参数,createDocument的关键代码如下:
    this.document = createDocument(new InputSource(inputStream));
  }

// 创建document文档
private Document createDocument(InputSource inputSource) {
    
    
    // important: this must only be called AFTER common constructor
    try {
    
    
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);
	 // 设置有本工厂创建的解析器文件是否支持xml命名空间
      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      // 是否忽略元素配置中的空格
      factory.setIgnoringElementContentWhitespace(false);
      // 设置是否将CDATA节点转换为Text节点
      factory.setCoalescing(false);
      // 设置是否展开实体节点,关系到sql片段的相关配置
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      // 设置解析mybatis-config.xml文档节点的解析器,也就是上面的XmlMapperEntityResolver
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
    
    
        @Override
        public void error(SAXParseException exception) throws SAXException {
    
    
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
    
    
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
    
    
        }
      });
      //主要是根据mybatis自身需要创建一个=文档解析器,调用parse方法(inputstream int).将解析后的xml以document对象的形式返回
      return builder.parse(inputSource);
    } catch (Exception e) {
    
    
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }
  //主要进行一些配置 
  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    
    
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }
	// ···省略其他无关代码···
}

接下来可以看 XMLConfigBuilder对象的创建是怎样的。

public class XMLConfigBuilder extends BaseBuilder {
    
    
	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;
  }
}

可以看出:主要调用了父类BaseBuilder的构造方法(作用:进行类型别名处理器注册、类型处理器注册)

public abstract class BaseBuilder {
    
    
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    
    
    // Configuration类的初始化的做了一些事情,后面讲具体是什么事。
    this.configuration = configuration;
    // 别名处理器
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    // 类型处理器
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
}

之后,设置environment及props等核心配置信息,(Configuration类是MyBatis的核心配置类,十分重要)。XmlConfigBuilder创建完毕之后,代码会回到SqlSessionFactoryBuilder类build(Reader reader, String environment, Properties properties)方法中,然后调用parser.parse()方法。至此,第一阶段:加载xml配置文件并转为Document对象完毕,而所有的配置过程都在parse()这个方法中(也是对Configuration类的配置)。

public class XMLConfigBuilder extends BaseBuilder {
    
    
	//省略其他代码
  public Configuration parse() {
    
    
    if (parsed) {
    
    
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //通过parseConfiguration(parser.evalNode("/configuration"))得知,xml文件中的configuration中的内容已经全部加载到XNode中了,后续的相关配置数据的获取都是通过这个XNode来获取的。
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(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);
    }
  }
}

Configuration类的配置文件这片文章会介绍,暂时还没写
链接: link.
我们来看一下整个配置文件解析流程
1.1
借鉴于
链接: https://blog.csdn.net/weixin_39723544/article/details/84028645
https://mp.weixin.qq.com/s/3_Fh8hTnMHMWX1e1EGe5-w

猜你喜欢

转载自blog.csdn.net/qq_40913932/article/details/112668786