Mybatis源码解析
传统的写法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 有哪些子类
点开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.
我们来看一下整个配置文件解析流程
借鉴于
链接: https://blog.csdn.net/weixin_39723544/article/details/84028645
https://mp.weixin.qq.com/s/3_Fh8hTnMHMWX1e1EGe5-w