下面是测试方法
public class Test {
public static void main(String[] args) throws Exception {
String resource = "mybatis.xml";
// 创建Configuration,用于从XML中构建SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 从SqlSessionFactory中获取SqlSession,SqlSession提供了在数据库执行SQL命令所需的所有方法
SqlSession sqlSession = sqlSessionFactory.openSession();
// 旧版Mybatis是这么用的,用字面量的形式指定mapper的全限定名;TestMapper类上也不需要@Mapper注解
// Object test2 = sqlSession.selectOne("lyfeifei.mapper.TestMapper.selectById", 1);
// System.out.println(test2.toString());
// 但现在有了一种更简洁的方式,加上@Mapper注解后mybatis会自动生成一个代理类
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
TestDao test1 = mapper.selectByIdAndName(1L, "张三");
// TestDao test1 = mapper.selectByIdForAnno(1L);
System.out.println(test1.toString());
}
}
org.apache.ibatis.session.SqlSessionFactoryBuilder#build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// SqlSessionFactoryBuilder使用了XMLConfigBuilder作为解析器,这里主要进行属性初始化
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse()主要用于创建Configuration
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.
}
}
}
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {
// 首先判断有没有解析过配置文件,只有没有解析过才允许解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// mybatis配置文件解析的主流程
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration 解析所有一级标签
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义VFS loadCustomVfs
// VFS主要用来加载容器内的各种资源,比如jar或者class文件。mybatis提供了2个实现 JBoss6VFS 和 DefaultVFS,并提供了用户扩展点,
// 用于自定义VFS实现,加载顺序是自定义VFS实现 > 默认VFS实现 取第一个加载成功的,默认情况下会先加载JBoss6VFS,
// 如果classpath下找不到jboss的vfs实现才会加载默认VFS实现
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析类型别名typeAliasesElement
typeAliasesElement(root.evalNode("typeAliases"));
// 加载插件pluginElement
pluginElement(root.evalNode("plugins"));
// 加载对象工厂objectFactoryElement
objectFactoryElement(root.evalNode("objectFactory"));
// 创建对象包装器工厂objectWrapperFactoryElement
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 加载反射工厂reflectorFactoryElement
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 将上面那得到setting,调用settingsElement(Properties props)将各值赋值给configuration,同时在这里有重新设置了默认值,
// 所有这一点很重要,configuration中的默认值不一定是真正的默认值
settingsElement(settings);
// 加载环境配置environmentsElement
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
// 数据库厂商标识加载databaseIdProviderElement
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 加载类型处理器typeHandlerElement
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
看两个比较重要的:获取数据源、获取执行语句
org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement 获取数据源
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");
// 查找匹配的environment
if (isSpecifiedEnvironment(id)) {
// 事务配置并创建事务工厂
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 数据源配置加载并实例化数据源, 数据源是必备的
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建Environment.Builder
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
把解析后的数据源信息设置到Configuration:configuration.setEnvironment(environmentBuilder.build());
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 获取执行语句
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 如果要同时使用package自动扫描和通过mapper明确指定要加载的mapper,一定要确保package自动扫描的范围不包含明确指定的mapper,
// 否则在通过package扫描的interface的时候,尝试加载对应xml文件的loadXmlResource()的逻辑中出现判重出错,
// 报org.apache.ibatis.binding.BindingException异常,即使xml文件中包含的内容和mapper接口中包含的语句不重复也会出错,
// 包括加载mapper接口时自动加载的xml mapper也一样会出错
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// TODO 这里应该是处理@Mapper注解
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文件解析类
// TODO 这里应该是解析xml文件中的注解信息
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.");
}
}
}
}
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper的核心
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
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"));
// 解析CRUD语句
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);
}
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext 重点看看CRUD的解析
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// SQL语句的解析主要在XMLStatementBuilder中实现
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 最后解析成MappedStatement 加到map中
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();
// 解析SQL命令类型,目前主要有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// insert/delete/update后是否刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// select是否使用缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,
// 就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
// 我猜测这个属性为true的意思是查询的结果字段根据定义的嵌套resultMap进行了排序,后面在分析sql执行源码的时候,我们会具体看到他到底是干吗用的
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// 解析语句中包含的sql片段,也就是
// <select id="select" resultType="map">
// select
// field1, field2, field3
// <include refid="someinclude"></include>
// </select>
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
// 上述解析完成之后,CRUD就没有嵌套的sql片段了,这样就可以进行直接解析了。现在,我们回到parseStatementNode()刚才中止的部分继续往下看。
// 接下去是解析selectKey节点。selectKey节点用于支持数据库比如Oracle不支持自动生成主键,或者可能JDBC驱动不支持自动生成主键时的情况。
// 对于数据库支持自动生成主键的字段(比如MySQL和SQL Server),那么你可以设置useGeneratedKeys=”true”,
// 而且设置keyProperty到你已经做好的目标属性上就可以了,不需要使用selectKey节点。由于已经处理了SQL片段节点,当前在处理CRUD节点,
// 所以先将包含的SQL片段展开,然后解析selectKey是正确的,因为selectKey可以包含在SQL片段中
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 最后为SelectKey对应的sql语句创建并维护一个KeyGenerator
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 解析crud语句的类型,mybatis目前支持三种,prepare、硬编码、以及存储过程调用
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
// 结果集的类型,FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
org.apache.ibatis.session.Configuration#addMappers 解析mapper的地方还有个核心方法
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
org.apache.ibatis.binding.MapperRegistry#addMappers
public void addMappers(String packageName, Class<?> superType) {
// mybatis框架提供的搜索classpath下指定package以及子package中符合条件(注解或者继承于某个类/接口)的类,
// 默认使用Thread.currentThread().getContextClassLoader()返回的加载器,和spring的工具类殊途同归
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 无条件的加载所有的类,因为调用方传递了Object.class作为父类,这也给以后的指定mapper接口预留了余地
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 所有匹配的calss都被存储在ResolverUtil.matches字段中
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 调用addMapper方法进行具体的mapper类/接口解析
addMapper(mapperClass);
}
}
org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
// 对于mybatis mapper接口文件,必须是interface,不能是class
if (type.isInterface()) {
// 判重,确保只会加载一次不会被覆盖
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 为mapper接口创建一个MapperProxyFactory代理
knownMappers.put(type, new MapperProxyFactory<>(type));
// 其中的MapperBuilderAssistant和XMLConfigBuilder一样,都是继承于BaseBuilder
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// MapperBuilderAssistant初始化完成之后,就调用build.parse()进行具体的mapper接口文件加载与解析
parser.parse();
loadCompleted = true;
} finally {
//剔除解析出现异常的接口
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {
String resource = type.toString();
// 首先根据mapper接口的字符串表示判断是否已经加载,避免重复加载,正常情况下应该都没有加载
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
// 每个mapper文件自成一个namespace,通常自动匹配就是这么来的,约定俗成代替人工设置最简化常见的开发
assistant.setCurrentNamespace(type.getName());
// 解析缓存
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
void parseStatement(Method method) {
// 获取参数类型,如果有多个参数,这种情况下就返回org.apache.ibatis.binding.MapperMethod.ParamMap.class,ParamMap是一个继承于HashMap的类,否则返回实际类型
Class<?> parameterTypeClass = getParameterType(method);
// 获取语言驱动器
LanguageDriver languageDriver = getLanguageDriver(method);
// 获取方法的SqlSource对象,只有指定了@Select/@Insert/@Update/@Delete或者对应的Provider的方法才会被当作mapper,
// 否则只是和mapper文件中对应语句的一个运行时占位符
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
// 获取方法的属性设置,对应<select>中的各种属性
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
// 获取语句的CRUD类型
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
// 只有INSERT/UPDATE才解析SelectKey选项,总体来说,它的实现逻辑和XML基本一致,这里不展开详述
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
// 解析@ResultMap注解,如果有@ResultMap注解,就是用它,否则才解析@Results
// @ResultMap注解用于给@Select和@SelectProvider注解提供在xml配置的<resultMap>,
// 如果一个方法上同时出现@Results或者@ConstructorArgs等和结果映射有关的注解,那么@ResultMap会覆盖后面两者的注解
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else if (isSelect) {
// 如果是查询,且没有明确设置ResultMap,则根据返回类型自动解析生成ResultMap
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
到这里mapper就解析完了,下篇再看sql的执行