本文共 14354 字,大约阅读时间需要 47 分钟。
先来吐槽下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时,一般会通过SqlSession的getMapper方法来获取对应的配置文件的当中的mapper,由上面的分析可知,这里创建出来的是,DefaultSqlSession,
publicT getMapper(Class type) { return configuration. getMapper(type, this); }
这里获取Mapper方式直接从Configuration当中获取了,我们来看Configuration类当中查看下
publicT getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
Configuration类也不想做这个事情,就把这个获取Mapper的方法,扔给了MapperRegistry,我们继续去看,MapperRegistry当中如何获取mapper对象
publicT getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) 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 MapperProxymapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
先通过sqlSession,接口类,methodCache来创建了一个动态代理类MapperProxy,我们先看下MapperProxy类
到了这里就非常清楚了,实现了InvocationHandler,重写了invoke方法,采用的就是jdk自动的动态代理接口,这个地方不熟悉的可以去看我关于动态代理模式的源码分析。
到这里都已经非常清楚了,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。