先来吐槽下CSDN,我辛辛苦苦写的文章,保存到草稿之后,准备返回,结果提示我没有的登陆,等我登陆之后,发现我写的文章竟然没有了!!!!!而且这个事情已经好几次了,估计是用CSDN的人太多了吧,想逼走一部分用户。
之前写了一篇关于MyBatis整个架构,工作流程的文章,保存之后发现啥都没有,也不想再写了,这篇直接就开始进入整个流程的源码分析阶段。
第一阶段
获取相关配置,根据SqlSessionFactoryBuilder创建SqlSessionFactory,然后再根据SqlSessionFactory打开一个SqlSession这个阶段
对于SqlSessionFactoryBuilder创建SqlSessionFactory,一般我们程序当中的代码执行如下
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
private static void init() throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
}
从一个指定的文件当中获取到Reader对象,可以理解为文件字节流,SqlSessionFactoryBuilder根据这个文件的内容,创建好一个SqlSessionFactory对象,我们来看builder方法
public SqlSessionFactory build(Reader reader) {
return this.build((Reader)reader, (String)null, (Properties)null);
}
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;
}
builder方法的执行步骤如下
1、通过Reader字节流,将xml文件当中的内容,通过XMLConfigBuilder解析器,将其中的配置文件属性内容对应到具体的java对象字段上
2、在XMLConfigBuilder当中的读取到的配置信息会封装到对象Configuration对象上,并且该对象被XMLConfigBuilder所持有。
3、根据Configuration对象创建SqlSessionFactory。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
这个方法主要是创建一个具体的SqlSessionFactroy的实现类,DefaultSqlSessionFactory,并将Configuration对象传递进去,作为DefaultSqlSessionFactory当中的一个属性。
接下来我们看下在SqlSessionFactory当中通过OpenSession获去SqlSession的代码,如果是无参的调用
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
这一步直接调用的就是openSessionFromDataSource方法,其中传递的参数是我们从configuration当中获取到的executorType,这个配置属性,autocommit给出的是false。假如我们的配置文件如下,可以看到并没有ExecutorType,但是我们可以从XMLConfigBuilder当中看到如果xml没有配置defaultExecutorType属性的话,这里会默认设置成SIMPLE.
的话,会设置
接下来我们来看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;
}
我们来看下上面代码的具体做了什么
1、获取到配置文件的Enviroment信息,可以看到我们上面贴出来的xml的内容
2、从配置文件当中获取到TransactionFactory,这个TransactionFactory就是我们Environment当中配置的TransactionManager, 我们配置给出了type=‘JDBC’, 如果我们没有配置这个TransactionManager,那么就会使用初始化一个ManagedTransactionFactory对象。
3、根据我们上面获取到的TransactionFactory来创建一个Transation,这里我们上述配置的是JDBC,那么这个地方就是根据JdbcTransactionFactory来创建Transaction对象。(这个地方使用的就是工厂设计模式,不同类型的TransactionManager创建不同的Transaction)
4、根据Transaction和我们传递进来的ExecutorType创建Executor对象(根据我们上面xml配置,executorType是SIMPLE)
5、根据创建好的Configuration, 创建好的Executor,以及是否autocomit来创建session
接下来我们来看下在创建SqlSession之前的操作,创建Transaction,我们配置当中选择的是,JDBC,这里我们去看下JdbcTransactionFactory当中创建的JdbcTransaction对象
public class JdbcTransactionFactory implements TransactionFactory {
public JdbcTransactionFactory() {
}
public void setProperties(Properties props) {
}
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
创建jdbcTransaction时,会根据传递进来的数据库,事务级别,是否commit,来创建Transaction,JdbcTransaction的创建
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
this.dataSource = ds;
this.level = desiredLevel;
this.autoCommmit = desiredAutoCommit;
}
可以看到这个构造方法,就是对Transaction当中的对象进行赋值,关于Transaction具体是做什么的我们来看下其接口当中提供的方法。
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
可以看到Transaction主要是用来处理数据库事物的,对于获取数据库连接,提交,回滚,关闭连接。
看完Transaction之后,我们来看下ExecutorType是做什么的,创建Executor是在Configuration类当中进行创建的,我们来看下具体实现代码
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;
}
1、获取到当前传递进来ExecutorType,基于前面的分析,我们直到MyBatis给我们设置的是默认的ExecutorType, SIMPLE(枚举类型),
2、根据ExecutorType和事务创建Executor,可以看到我们当前创建的是SimpleExecutor。
3、判断是否支持缓存,如果是的话,我们这里就会采用CachingExecutor, 但是需要注意的是,我们并没有放弃使用之前的SimpleExecutor对象,而是将其作为参数传递到了CachingExecutor当中,后面分析缓存实现原理时,会着重分析这块内容。
4、这一步是将我们生成好的executor放入到InterceptorChain当中。(这个地方采用的就是责任链模式)关于plugin在后面分页插件当中会详细介绍。
以上是整个mybatis从配置文件到openSession的过程,接下来我们需要来看如何通过DefaultSqlSession来获取mapper,然后再进行增删改查操作。
获取mapper对象,并执行sql
我们在使用Mapper时,一般会通过SqlSession的getMapper方法来获取对应的配置文件的当中的mapper,由上面的分析可知,这里创建出来的是,DefaultSqlSession,
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
这里获取Mapper方式直接从Configuration当中获取了,我们来看Configuration类当中查看下
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
Configuration类也不想做这个事情,就把这个获取Mapper的方法,扔给了MapperRegistry,我们继续去看,MapperRegistry当中如何获取mapper对象
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);
}
}
MapperRegistry会获取到这个Class对应的MapperProxyFactory,通过MapperProxyFactory来动态创建mapper接口的实现类
我们来看下MapperProxyFactory类,通过SqlSession来动态创建mapper的实现类
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
先通过sqlSession,接口类,methodCache来创建了一个动态代理类MapperProxy,我们先看下MapperProxy类
到了这里就非常清楚了,实现了InvocationHandler,重写了invoke方法,采用的就是jdk自动的动态代理接口,这个地方不熟悉的可以去看我关于动态代理模式的源码分析。设计模式——代理模式之动态代理源码分析JDK1.8(一)
到这里都已经非常清楚了,MapperProxy是一个动态代理类。我们接着上面分析MapperProxyFactory进行分析,通过newInstance方法调用生成了动态代理类的对象,然后根据接口类的加载器,接口类,以及动态代理对象动态生成指定的mapper接口的实体类。
总结一下关于Mapper对象的创建过程
DefaultSqlSession(getMapper)---->Configuration(getMapper)------>MapperRegistry(getMapper)------>MapperProxyFactory(newInstance)-------->MapperProxy(构造方法)------->MapperProxyFactory(newInstance)
所以Mapper对象的动态生成,其实就是MapperProxyFactory和MapperProxy这两个类来创建的。
接下来我们要分析MapperProxy这个代理类是如何调用具体的方法的
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
1、invoke方法在调用时会先判断,如果是否是Object类当中的方法,
2、如果是的话,直接调用方法即可,
3、如果不是Object的Class对象,会先通过cachedMapperMethod来获取MapperMethod,
4、采用MapperMethod执行具体的方法。
接下来我们来看调用的cachedMapperMethod方法
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
1、这个methodCache是一个ConcurrentHashMap,key是Method,value为MapperMethod,是从MapperProxyFactory当中传递过来,在初始化MapperProxyFactory时,会创建一个空的ConcurrentHashMap。
2、如果当前Method在methodCache当中查询不到,就创建一个新的MapperMethod,并将其放入在cacheMethod当中。
3、最终返回一个mapperMethod方法
关于这个MapperMethod到底是什么我们从其构造方法入手,再进行分析
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
1、根据参数创建SqlCommand
2、获取到调用方法的签名
接下来我们看创建SqlCommand的构造方法
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
这个构造方法就是从Configuration当中获取到MappedStatement对象,MappedStatement其实就是我们xml当中的配置的需要执行的sql,以及返回类型,参数类型封装起来的一个java对象,关于解析过程这里不做详细分析,可以查看XmlStatementBuilder当中parseStatementNode以及MapperBuilderAssistant类当中的addMappedStatement方法,会将解析好的MappedStatement放入到configuration对象当中。再回到SqlCommand,其中封装的只有name和type属性,也就是通过这个构造方法,获取到我们在xml配置的id和type, 这里的SqlCommandType就是我们的select,update ,delete, insert标签。
分析完SqlCommand,接下来我们来看,构造方法当中另外一个MethodSignature的构造方法
public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
可以看到这一步就是将方法的信息都获取到并绑定在MethodSignature对象上,包括参数,返回类型等。
上面这些构造方法结束之后,我们再回到MapperProxy,因为我们最初就是要分析它的invoke方法,是如何调用
上面的分析,我们已经构造好了一个MapperMethod对象,接下来就是使用MapperMethod来执行execute方法
这里我们以insert为例,进入看下sqlSession的insert方法,根据前面的分析,这里是DefaultSqlSession
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
insert方法调用的是由参数的update方法
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这个方法是根据我们Mapper当中的id,来查询到对应的MappedStatement,然后交给Executor去执行update操作,Executor上面的分析,在默认情况下,MyBatis会给我们使用SimpleExecutor,同时如果缓存开启就将SimpleExecutor作为delegate创建一个CachingExecutor,我们去CachingExecutor当中查看这个update方法。
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
1、第一步就是必要时刷新缓存,暂时先不做分析
2、调用delegate执行更新方法,所以最终还是回到我们当时的SimpleExecutor当中执行这个update方法
下面是先从BaseExecutor当中进行一个判断
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
clearLocalCache();
return doUpdate(ms, parameter);
}
然后再调用子类具体的doUpdate方法,这里BaseExecutor和SimpleExecutor之间就使用了模板方法设计模式,接下来进入到SimpleExecutor当中
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
可以看到SimpleExecutor也并没有执行sql语句,而是将sql的执行交给了StatementHandler来执行。
上面关于MyBatis在处理Mapper时的一个过程进行总结:
通过MapperProxy和MapperProxyFactory动态代理地方式创建好Mapper接口的实现类,然后当我们调用mapper的接口时,会触发MapperProxy当中的invoke方法,invoke方法调用Executor执行sql,但是Executor会把这个sql的执行继续往下传递,最终执行SQL的就是StatementHandle。