MyBatis学习-6
本节课来介绍一下MyBatis的工作原理 我们清楚运行原理,我们在以后的开发,还是插件都是很有必要的。
1.根据配置文件创建一个SqlSessionFactory
研究框架的源码(我也有点虚,一点一点来吧)首先我们回到当初的第一个测试中去看看:
@Test
public void test1() throws IOException{
//1.获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//2.获取SqlSession对象,
SqlSession openSession = sqlSessionFactory.openSession();
//3.获取接口的实现类对象
//会为接口创建UI个代理对象,代理对象去执行增删改查
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(mapper);
System.out.println(employee);
} finally {
openSession.close();
}
}
这次我们看看mapper到底是个什么东西,泡一下看一眼控制台得到了这个信息org.apache.ibatis.binding.MapperProxy@9bd0fa6
它是一个MapperProxy代理对象
- MyBaits在四大对象的创建过程中,都会有插件进行介入,插件可以利用动态带路机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
- MyBatis允许在以映射语句之行过程中的某一点进行拦截调用。
- 默认情况,MyBatis允许使用插件来拦截的方法调用包括:
Excutor ParameterHandler ResultSetHandler StatementHandler
这四个方法想必在JDBC中我们是见过的,我们在看源码的时候一定要留心这些方法
我们再看看在运行前都做了什么工作
- 首先通过自己写的getSqlSessionFactory我们创建了一个SqlSessionFactory
- 之后我们再去看getSqlSessionFactory,他返回了一个
new SqlSessionFactoryBuilder().build(inputStream)
我们去看Build的源码public SqlSessionFactory build(InputStream inputStream) { return this.build((InputStream)inputStream, (String)null, (Properties)null); }
他返回的就是他自己,ok我们再去看看SqlSessionFactoryBuilder的源码:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException var13) { } } return var5; }
他先创建了一个XML的解析器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
接下来继续看,var5 = this.build(parser.parse());
那么这个parse()是个什么方法呢我们继续来看他的源码public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.loadCustomLogImpl(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
parseConfiguration会解析每一个节点你可以在源码中看到settings,typeAliases,reflectorFactory…这些,然后保存在Configuration当中
在其中我们看到他调用了parseConfiguration
这个方法,我们看看他是干嘛的,他就在这个类的下面
首先他要拿到我们的空节点
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
这个Configuration获取了当前语句的所有信息包括全局配置封装的Mapperstatment,我们的映射文件信息,这里不需要了解很多,知道这个流程就可以了。
最后他返回了这个configuration,这时候我们还是回到了var5 = this.build(parser.parse());
当中,然后调用build方法我们再来看这个方法 public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
他的返回是根据传入的configuration,new了一个新的DefaultSqlSessionFactory,最后再把这个返回的传给创建sqlSessionfatory这个方法。
- 总结:配置文件解析保存在configuration当中,再把他封装到DefaultSqlSession当中。一个MapStatement代表了一个增删改查的全部信息
2.openSession
接下来就是我们使用sqlSessionFactory创建一个SqlSession对象,让我们一起来看看这个流程吧
- 首先我们调用了
sqlSessionFactory.openSession()
方法也就是调用了DefaultSqlSession的openSession(),让我们去DefaultSqlSession源码里去看看public SqlSession openSession(boolean autoCommit) { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit); }
我们看到它实际上返回的是一个openSessionFromDataSource()方法的返回值
- openSessionFromDataSource()是怎么工作的我们来看看这个方法的源码,源码就在这个类的下面
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment environment = this.configuration.getEnvironment(); TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); Executor executor = this.configuration.newExecutor(tx, execType); var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
能看到在这里他获取了一些信息,和事务,比如Environment,TransactionFactoryFromEnvironment,把这些信息放在tx当中。重点是他创建了一个Executor,我们来看看怎么创建的
- Executor,我们看一下这个newExecutor方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? this.defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Object executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (this.cacheEnabled) { executor = new CachingExecutor((Executor)executor); } Executor executor = (Executor)this.interceptorChain.pluginAll(executor); return executor; }
如果这个executor如果是BATCH(批量的)的就new一个BatchExecutor对象,如果是REUSE就new一个ReuseExecutor对象,否则就new一个SimpleExecutor,根据executor在全局配置中的类型创建相应的executor,executor是一个接口,里面包含了我们sql的各种方法(增删改查…),我们可以看一下这个接口中的方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.ibatis.executor;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement var1, Object var2) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean var1) throws SQLException;
void rollback(boolean var1) throws SQLException;
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
boolean isCached(MappedStatement var1, CacheKey var2);
void clearLocalCache();
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
}
回到我们的Executor当中接下来他写了这么一个判断 if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
就是在判断我们是否开启了二级缓存,如果是我们就用CachingExecutor对executor进行包装。
这还没有完,他还调用了interceptorChain.pluginAll()
方法后才return了executor。这一部非常重要,我们用每一个interceptorChain(插件)的pluginAll()方法对executor进行包装。
- 好绕了一大圈我们回到了创建好Executor后我们new了一个
DefaultSqlSession(this.configuration, executor, autoCommit)
,他包含了configuration和Executor信息,最终返回给openSession - 总结:返回SqlSession实现类DefaultSqlSession对象,他包含了configuration和Executor信息,Executor会在这一步被我们创建。
3.getMapper
- 我们先使用DefaultSqlSession对象的getMapper方法,那我们就看看这个getMapper是怎么写的
` public
T getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }` 他返回了一个mapperRegistry的getMapper(type, sqlSession)方法的返回值 - 来让我们看看这个mapperRegistry的getMapper方法,他就在这个文件当中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
我们可以看到它是通过接口创建一个MapperProxyFactory代理对象,然后返回了mapperProxyFactory.newInstance(sqlSession);,也就是说调用了这个代理对象的newInstance方法,参数是我们的sqlSession。
- 我们来看看这个newInstance是怎么工作的
public T newInstance(SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }
他先创建了一个MapperProxy
对象,那这个MapperProxy 又是什么呢我们再来看看他的源码 - MapperProxy
``` // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //
package org.apache.ibatis.binding;
import java.io.Serializable; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.session.SqlSession;
public class MapperProxy
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
});
}
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup)constructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
private boolean isDefaultMethod(Method method) {
return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
} } ``` 里面东西有点多哈,没事,我看先看他的开头它继承了一个接口InvocationHandler,这个接口不就是我们JDBC在做动态代理的时候用到的嘛,所我我们现在知道它是一个弄来创建动态代理的
- 然后返回到我们的newInstance(MapperProxy)再去看这个newInstance方法
protected T newInstance(MapperProxy<T> mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); }
发现它调用返回了我们Java反射中的动态代理对象。
- 总结一步步下来其实就是getMapper返回了接口的代理对象,包含了sqlSession。
4.getEmpById(1)
下面我们来看如何实现增删改查让我们setp into
- 首先mapperProxy调用invoke()这个方法,这是怎么回事呢,这就是代理方法在执行前会执行invoke方法,判断当前要执行的方法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } if (this.isDefaultMethod(method)) { return this.invokeDefaultMethod(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); }
就是if (this.isDefaultMethod(method)
,然后他会把mapperMethod包装成一个cachedMapperMethod,就是MyBatis能认识的方法,然后调用execute方法
-
execute方法,来让我们看一下源码 ``` public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException(“Unknown execution method for: “ + this.command.getName()); }
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
``` 先来判断command方法,根据不同的方法执行相应的方法包装参数为Map或者直接返回,接下来,调用SqlSession的selectone,返回正确的信息就完成了。
- 总结: 查询流程是这样的,首先创建代理对象,通过DefaultSqlSession,使用Executor方法,再到statementHandler,处理sql语句预编译,设置参数等相关操作,设置参数交给ParameterHandler,结果处理集交给ResultHandler。在整个过程中,进行数据库类型和JavaBean类型映射的是TypeHandler。这些操作的一切底层操作的都是�由JDBC:Statement:PrepareStatement来实现的。