1、概述
目前工作中,直接使用mybatis原生API开发的场景很少,基本都是结合spring一起使用。但对于分析mybatis的源码来说,使用API的方式能更容易的理清思路。先介绍下原生API的使用方式。
public static void main(String[] args) {
String resource = "configuration.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1);
System.out.println(role.getLfPartyId() + "," + role.getPartyName());
} finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
其中比较重要的类有SqlSessionFactoryBuilder
、SqlSessionFactory
、SqlSession
、RoleMapper
。下面先简要介绍下他们的使用范围与作用。
-
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder
是典型的建造者模式的应用,一般他的生命周期就是存在于方法中,主要作用就是根据mybatis的全局配置文件(这里不对mybatis的配置文件做过多介绍,比较简单)构建出SqlSessionFactory
对象,同时会建立好Configuration
对象(该对象及其重要,贯穿整个mybatis的生命周期,后面会重点介绍)。 -
SqlSessionFactory
SqlSessionFactory
是典型的工厂模式,用于生成SqlSession
对象,一般全局单例。SqlSessionFactory
是个接口,有两个实现类DefaultSqlSessionFactory
、SqlSessionManager
,系统默认使用DefaultSqlSessionFactory
。 -
SqlSession
SqlSession
可以简单理解为JDBC的connect对象,执行SQL方法(具体其实是Executor对象执行的sql方法,具体后面会分析),生命周期也是方法级别。SqlSession
也是个接口,有两个实现类DefaultSqlSession
、SqlSessionManager
,系统默认使用DefaultSqlSession
。 -
RoleMapper
RoleMapper
接口是一整个Mapper接口的代表,它没有实现类,作用是发送SQL(帮助SqlSession
找到SQL),生命周期与SqlSession
一致。
总结:从上面简短的介绍就可以看出mybatis框架设计的巧妙,充分体现了依赖倒置原则(面向接口编程)。
2、从初始化开始说起
从上面概述的原生API的使用代码片段可以看出,mybatis的初始化,是从构建SqlSessionFactory
对象开始的,那我们就进入到SqlSessionFactoryBuilder
内部,看看SqlSessionFactory
对象以及重要的Configuration
对象是如何被构建出来的。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
....
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
首先解析XML文件构建出Configuration
对象,然后将该对象设置为DefaultSqlSessionFactory
对象的属性后返回。这里可以很明显的看出,系统确实默认是使用的DefaultSqlSessionFactory
对象。这里先分析下Configuration
对象是如何构建的,然后再分析DefaultSqlSessionFactory
对象的细节。
2.0 Configuration
概述
在介绍Configuration
对象是如何构建之前,我们先简单介绍下Configuration
对象的作用。简单理解,Configuration
对象其实就是mybatis的XML配置文件对应的Java类表示形式(类似的有tomact的XML配置文件和spring的XML配置文件,他们都是会解析成对应的Java类)。在SqlSessionFactoryBuilder
类内部会解析XML文件,相应的节点都会被解析成Configuration
的相关属性。主要有如下的属性解析过程:
- properties全局参数
- settings设置
- typeAliases别名
- typeHandler类型处理器
- ObjectFactory对象(很少使用)
- plugin插件
- environment环境(与spring结合后,由spring来管理环境的切换)
- DatabaseIdProvider数据库标识(很少使用)
- Mapper映射器
其中我们分析的重点是第9步,后面会详细分析,前面的8步比较简单,在后面稍微提及。
2.1 Configuration
构建过程
在上面的代码中,我们已经看到,Configuration
对象是通过XMLConfigBuilder
对象构建而成的,分析下该对象的初始化及其parse
方法的具体实现,即可明白Configuration
对象的构建过程。
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;
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
我们发现直接调用了new Configuration()
方法,实例化了Configuration
对象,然后在parse
方法中通过parseConfiguration
方法解析"/configuration"节点来实现对Configuration
对象的属性填充。再看看该方法的实现
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// Configuration类中有typeAliasRegistry属性,是对HashMap的简单封装,里面存储alias->Class的关联关系
typeAliasesElement(root.evalNode("typeAliases"));
// Configuration类中有InterceptorChain属性,是对ArrayList的简单封装,里面存储所有的插件实体对象,内部有个pluginAll方法,通过层层包装的方式来实现插件的调用
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"));
// Configuration类中有typeHandlerRegistry属性,内部有多个HashMap的属性,里面存储JDBC type到Java type的类型转换规则
typeHandlerElement(root.evalNode("typeHandlers"));
// Configuration类中有mapperRegistry属性,是对HashMap的简单封装,里面存储Mapper接口到`MapperProxyFactory`的映射关系
// 解析的同时也会向mappedStatements属性添加值,该属性也是对HashMap的简单封装,里面存储namespace+id 与MappedStatement对象的映射关系,后者就是mapper.xml文件中的select、insert、update、delete等SQL语句的节点。
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
代码都做了相关注释,内部实现也都不是很复杂(需要说明的是在settingsElement(settings);
方法中,会根据配置的defaultExecutorType属性设置Executor的类型,默认是Simple,configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
),
其次一个较为复杂且最为重要的就是mapperElement(root.evalNode("mappers"));
方法,即是我们上面说的到第9步。我们看看他的实现。
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 {
// resource、url、class三个属性不可同时存在
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.");
}
}
}
}
}
目前大部分都是使用package的方式来批量导入mapper的XML文件,所以我们重点分析他,其他的导入方式在底层原理是类似的。重点是这段代码configuration.addMappers(mapperPackage);
,它最终会调用到MapperRegistry
类的addMapper
方法中。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// knownMappers.containsKey判断是否存在
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 绑定mapper接口与代理类工厂的映射关系,这是mapper接口能直接使用的关键
// 这里mapper接口的代理类还没实例化,只是绑定了工厂类,代理类的实例化在sqlSession.getMapper()方法中实例化
knownMappers.put(type, new MapperProxyFactory<>(type));
// 下面两行代码会具体实现上文提到了mappedStatements属性填充
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
再看MapperAnnotationBuilder
类的parse
方法
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper的XML文件
loadXmlResource();
// 下面是解析mapper接口中的注解信息,这种方式对代码的侵入性比较大,现阶段很少使用,基本可以不用关注
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
loadXmlResource();
方法会调用到XMLMapperBuilder
类的parse()
方法,
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 解析resultMap
parsePendingResultMaps();
// 解析cache
parsePendingCacheRefs();
// 解析SQL语句
parsePendingStatements();
}
在该方法里,我们重点关注parsePendingStatements()
,在其内部会调用XMLStatementBuilder
类的parseStatementNode()
方法
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
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);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
这个方法就是重头戏了,解析mapper的XML文件,将XML中的select、delete、update、insert等节点解析成MappedStatement对象,并缓存到Configure类中。其中不同的节点会匹配不同的statementType,SQL语句会封装到SqlSource对象中,默认是DynamicSqlSource
实现类。
至此整个configuration对象的初始化过程中的重要阶段就介绍完毕了。configuration对象在整个mybatis运行过程中的每个阶段都有它的影子,这是一个非常重的对象。对其中的一些比较重要的属性的理解,直接关系到对整个mybatis的运行机制的理解。所以这里再简单总结下configuration对象中比较重要的一些属性。
// 记录Mapper接口与代理工厂类的绑定关系
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 责任链模式,记录所有的插件
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 记录所有的类型转换器
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 记录所有的类型简称
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 记录所有的namespace+id与MappedStatement的绑定关系,其中MappedStatement内部关联具体的SQL语句
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
2.2 SqlSessionFactory
简介
在之前的分析中,我们已经知道,SqlSessionFactoryBuilder
对象,默认是实例化了DefaultSqlSessionFactory
对象,我们先看下该对象的初始化过程
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
很简单,DefaultSqlSessionFactory
对象内部持有Configuration
对象的引用,初始化过程就是完成该属性的复制。下面再分析下其获取SqlSession
对象的过程
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里我们可以看到,mybatis默认确实是使用的DefaultSqlSession
实现类。其中ExecutorType在上面已经介绍过,默认使用的Simple。SqlSession
对象持有两个比较重要的对象的引用,Configuration
和Executor
,Configuration
我们在前面已经介绍过了,Executor
我们在后面再详细介绍。
SqlSession
就简单介绍到这里,具体的使用原理,在下面介绍Mapper接口的时候再详细说明。
3、Mapper接口的调用过程
我们知道在Java中,接口方法是没法调用的,在Mybatis中却很奇怪的可以调用,且能正常运行。这其中是什么原理呢?
这其实就是代理在其中作祟了。我们从概述介绍的代码片段中的RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
代码行开始,介绍Mapper接口的调用过程。我们知道,Mybatis默认是使用DefaultSqlSession,所以我们就从该类的getMapper方法入手。
// DefaultSqlSession.class
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// Configuration.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
如上代码可以看出,DefaultSqlSession的getMapper方法,通过层层委托,最终到了MapperRegistry类的getMapper方法,该类中有个knownMappers属性,在上面介绍初始化过程中已经介绍过,里面存储着type接口与MapperProxyFactory类的映射关系。最终我们得知,getMapper方法返回了type的JDK动态代理对象。其中MapperProxy
类实现了InvocationHandler
接口,我们在后面调用接口方法时,其实最终都会调用该类的invoke方法(其实就是最简单的JDK动态代理技术,这里就不详细介绍了)。我们就看下该方法的具体实现吧
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Object类中的方法,比如wait、notify等不做代理,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) { // JDK8中接口有默认方法实现,也不做代理,直接调用
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 对method和MapperMethod关联值做了缓存
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
其中,MapperMethod
对象有两个内部类,这里得介绍下:
-
SqlCommand
根据传入的configuration
、mapperInterface
、method
可以获取该method对象具体需要执行的sql语句。这里就用到了上面初始化过程中介绍的configuration
类的一个重要属性mappedStatements
(内部保存了namespace+id-->MappedStatement的映射关系)
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
-
MethodSignature
根据传入的configuration
、mapperInterface
、method
可以获取该方法的返回值类型,参数解析器等信息,这里就不详细分析。
如此我们知道,Mapper接口的方法调用,最终会执行MapperMethod
对象的execute
方法中。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 1
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 2
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
...
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
方法有点长,我们这里就以select类型作为分析对象。首页根据方法的返回值类型决定具体的实现方式,其中标注的1和2分别表示返回数组和返回普通对象(这里不是很严谨),这两个分支是调用频率最高的,我们这里以1位代表,分析下执行的过程。其中executeForMany
方法最终会调用到DefaultSqlSession
类的selectList
方法中,如下:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement
对象包含有具体需要执行的SQL语句,executor
对象是在DefaultSqlSessionFactory
调用openSession
方法时构建成功的,默认是SimpleExecutor
对象(目前大部分的项目不会开启Mybatis的二级缓存,否则这里默认会是CachingExecutor
对象,其实就是对SimpleExecutor
对象的装饰),并注入到DefaultSqlSession
对象的属性中。(这里的executor
对象已经是被插件代理过后的对象了)。
4、总结
对于初始化的过程,就不做总结了。比较复杂的过程就是configuration
对象的构建比较复杂,因为这个对象太重了,但是在使用的过程中,我们基本是感知不到这个对象的存在的。这也是写一个框架并能让大多数人能接受这个框架的需要注意的点,将复杂留给自己,将简单给与用户。
这里主要总结下Mapper接口具体是如何运行并能执行SQL拿到数据的过程。
- 调用SqlSession的getMapper方法
- 委托给MapperRegistry对象的getMapper方法,通过type拿到MapperProxyFactory对象,然后该对象会返回一个MapperProxy对象,作为Mapper接口的代理类。
- 调用Mapper接口的方法,会知道到MapperProxy对象的invoke方法中,该方法会委托给MapperMethod对象的execute方法
- MapperMethod对象的execute方法会根据MapperMethod对象的command属性的type值决定决定SQL的执行类型。这里已select为例,分析executeForMany方法。
- executeForMany方法会再委托给SqlSession的selectList方法,且传入command属性的name值即Mapper接口名+方法名。
- SqlSession的selectList方法会委托给executor类的query方法去执行具体的SQL
SQL的具体执行又涉及到Executor
、StatementHandler
、ResultSetHandler
、ParameterHandler
四个重要的类,下次与mybatis的插件机制一起分析。