Mybatis作为一款非常火的ORM框架,虽然我们一直在用,但是一直都没有去仔细了解它的底层,这次决心好好研究一下Mybatis。
Mybatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久化层框架,主要完成2件事情:
- 封装JDBC操作
- 利用反射打通Java类与SQL语句之前的相互转换
一、Mybatis 的配置
Mybatis的配置文件格式大致如下:
<?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>
<properties resource="config.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mybatis/NewsMapper.xml"/>
</mappers>
</configuration>
二、Mybatis的主要成员
- Configuration :Mybatis的所有配置信息都保存在Configuration对象中,配置文件中的大部分配置都会存储到该类中
- SqlSession : 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
- Executor : Mybatis执行器,是Mybatis调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler: 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
- ParameterHandler : 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
- ResultSetHandler : 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
- TypeHandler : 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
- MappedStatement : MappedStatement维护一条<select|update|delete|insert>节点的封装
- SqlSource : 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql : 表示动态生成的SQL语句以及相应的参数信息
其中,我们需要重点关注的是 SQL 参数什么时候被设置,以及结果集怎么转换为JavaBean对象,这两个过程正好对应 StatementHandler 和 ResultSetHandler类的处理逻辑.
四、Mybatis的初始化
MyBatis的初始化过程其实就是解析配置文件和初始化Configuration 的过程
初始化:
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1.创建 SqlSessionFactoryBuilder 建造对象,然后用它创建SqlSessionFactory(使用了建造者模式).
SqlSessionFactoryBuilder类的源码如下:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
其中 XMLConfiguration对象会进行XML配置文件的解析,实际为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) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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节点下会解析properties、typeAliases、plugins 等等节点。
需要注意的是 在解析environments 节点时会根据transactionManager的配置来创建事务管理器,根据dataSource的配置来创建DataSource对象,这里面包含了数据库登录的相关信息,在解析mappers节点时,会读取该节点下所有的mapper文件,然后进行解析,并将解析后的结果存到configuration对象中。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
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());
}
}
}
}
// 解析单独的mapper 文件.
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("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
至此,configuration就初始化完成了, 然后根据configuration对象来创建 SqlSession,MyBatis初始化结束.
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}