从 mybatis 的官网,写最简单的 “ HelloWorld ”
Reader reader = Resources.getResourceAsReader("conf.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
StudentDao mapper = session.getMapper(StudentDao.class);
System.err.println(mapper.listStudent());
首先解析全局配置文件, 转换成一个 Reader 对象, 这个是 JDK 的代码,直接看第二行, 创建了一个 SqlSessionFactory
对象
SqlSesseionFactory
使用了 建造者模式,, 最后调用的是 build 方法,创建了一个 SqlSessionFactory 对象
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 将全局配置文件转换成一个 Document 对象放入 XPathParser 对象中
// 而 XPathParser 对象保存在此 XMLConfiguration 对象中
// 这个 Document 对象类似于 “树” 结构
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 开始解析这个 Document 对象, 保存为一个 Configuration 对象中
// 创建默认的 SqlSessionFactory 对象, DefaultSqlSessionFactory
return build(parser.parse());
}
....
}
解析全局配置文件
// 由 XMLConfigBuilder 对象解析
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 解析完毕后设置一个标志位, 只能解析配置文件一次
parsed = true;
// 解析配置文件中的 <configuration> 标签, 保存到 XNode 对象中
// 然后处理这个 XNode 对象
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
解析 <configuration>
下的所有子标签
// 挨个解析所有子标签, 可以看到, 严格按照顺序依次执行
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
// 将 <setting> 标签的元素保存为 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);
// 解析并创建数据源
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 mapper 配置文件
mapperElement(root.evalNode("mappers"));
}
catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
这里就不一个一个的看了, 看看最常用的 <environments>
,<mappers>
……
解析 <environments>
标签
// 处理 <environments> 标签, 这些信息被保存在 XNode 对象中,作为参数传了进来
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 如果没有使用指定的 Environment, 就使用默认的
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 处理子标签 <environment>, 同样封装成 XNode 对象
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// id 匹配, 使用指定 id 的 Environment
if (isSpecifiedEnvironment(id)) {
// 事务工厂,默认使用 JDBC 管理事务
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 处理子标签 <dataSource>, 创建数据源工厂
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 根据配置创建数据源
DataSource dataSource = dsFactory.getDataSource();
// 这里同样使用了建造者模式
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 保存到 Configuration 对象中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
解析子标签 <dataSource>
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
// 将 <dataSource> 的子标签转换成 Properties 对象
Properties props = context.getChildrenAsProperties();
// 反射创建 DataSourceFactory 对象
DataSourceFactory factory = (DataSourceFactory) resolveClass(type)
.getDeclaredConstructor().newInstance();
// 将 Properties 元素与 DataSourceFactory 绑定
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
解析 <mappers>
标签
// 处理 <mappers> 标签, 可以看到, mybatis 支持四种方式导入 Mapper
// <mapper resource="mapper/student.xml"/>
// <mapper url="mapper/...xml"/>
// <mapper class=""/>
// <package name="com.gp.ibatis.dao"/>
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);
// 创建用于解析 mapper。xml 配置文件的对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,
// 解析 mapper.xml 配置文件 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();
}
// 如果使用的是 class 直接导入 Mapper 接口
else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 直接放到 configuration 对象的 mapperRegistry 集合中
configuration.addMapper(mapperInterface);
}
else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
解析 mapper.xml 配置文件
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 将标签 <mapper> 封装成 XNode 对象,然后解析它的所有子标签
configurationElement(parser.evalNode("/mapper"));
// 标记该 mapper.xml 配置文件已加载
configuration.addLoadedResource(resource);
// 将 mapper.xml 文件中的 namespace 与接口绑定
bindMapperForNamespace();
}
// 处理待定的对象, 即无法正常创建的对象
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析 <mapper>
的所有子标签
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 标记当前正在处理的 namespace
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
// 解析 <parameterMap> 标签, 老式的关系映射, 已被废弃
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 <resultMap> 标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 处理所有 <select>, <insert> ... 标签, 这应该是一个 XNode 集合
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
解析 CRUD 标签
// 最后来到 XMLMapperBuilder 的这个方法
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 用于解析 <select>, <insert> ... 标签的对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析 CRUD 标签, 封装成 MappedStatement 对象
statementParser.parseStatementNode();
}
catch (IncompleteElementException e) {
// 同样, 如果无法创建 Statement 对象, 将其放入
// configuration 对象中的 incompleteStatement 集合中
configuration.addIncompleteStatement(statementParser);
}
}
}
----------------------
// 查看 XMLStatementBuilder 的 parseStatementNode 方法
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 检查使用的数据库是否统一, 否则直接返回
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 检查是否为 <select> 标签
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// 首先解析 <include> 标签
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 然后解析 <selectKey> 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 最后再来解析 SQL 语句
KeyGenerator keyGenerator;
....
// 拿到 SqlSource 对象, 包含已解析的 SQL, 参数 name, JdbcType ...
// 解析 SQL 时, 如果是 # 用占位符替换, 如果是 $ 不处理
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
....
// 最后封装成 MappedStatement 对象, 放入 Configuration 对象中
}
最后将 Configuration 对象返回出去, 封装到 DefaultSqlSesseionFactory 对象中