框架源码专题:Mybatis启动和执行流程、源码级解析

 2023-09-15 阅读 24 评论 0

摘要:文章目录1. Mybatis 启动流程步骤一: 把xml配置文件解析成Configuration类步骤二: 创建SqlSession会话mybatis的三种执行器步骤三: 在sqlSession中操作数据①:为Mapper接口创建代理对象②:使用代理对象`proxy`调用`selectByI

文章目录

    • 1. Mybatis 启动流程
    • 步骤一: 把xml配置文件解析成Configuration类
    • 步骤二: 创建SqlSession会话
      • mybatis的三种执行器
    • 步骤三: 在sqlSession中操作数据
      • ①:为Mapper接口创建代理对象
      • ②:使用代理对象`proxy`调用`selectById`查询数据!
        • 自定义加解密TypeHandler
    • 2. Mybatis是如何解析动态sql的?
    • 3. StatementHandler

java流程框架。

1. Mybatis 启动流程

在这里排除建表、编写mybatis配置文件的过程,启动代码如下所示

public class App {public static void main(String[] args) {//mybatis配置文件名称String resource = "mybatis-config.xml";Reader reader;try {//把mybatis配置文件转换程一个readerreader = Resources.getResourceAsReader(resource);// 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory,// 将XML配置文件构建为Configuration配置类SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);// 创建SqlSessionSqlSession session = sqlMapper.openSession();try {// 执行查询 第一种方式,mybatis3之前的方式User user = (User)session.selectOne("com.zhai.mapper.UserMapper.selectById", 1);// 执行查询 第二种方式,mybatis3之后的方式UserMapper mapper1 = session.getMapper(UserMapper.class);User user1 = mapper1.selectById(1L);//提交sessionsession.commit();System.out.println(user.getUserName());} catch (Exception e) {e.printStackTrace();}finally {//关闭sessionsession.close();}} catch (IOException e) {e.printStackTrace();}}
}

Java代码执行流程图、

步骤一: 把xml配置文件解析成Configuration类

上述代码

 // 将XML配置文件构建为Configuration配置类,并封装成DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

Springboot框架。        在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder 读取所有的MybatisMapConfig.xml 和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

在这里插入图片描述
SqlSessionFactory使用的是工厂模式。可以看到,该Factory的openSession方法重载了很多个,分别支持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。

        其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此在构建Configuration对象时大量采用了建造者模式来解决。

不同的xxxConfigBuilder都继承抽象类BaseBuilder,实现对xml文件中不同位置节点的解析,关系图如下所示
在这里插入图片描述

2.1 Configuration类中解析数据源

在这里插入图片描述
        
2.2 Configuration类中解析增删改查节点(sql)

	//查找节点<select id="selectById"  resultMap="result"     >select id,userName,createTime from user<where><if test="param1>0">and id=#{param1}</if></where></select>

如上图,在xml文件中每一个增删改查节点都会被解析成一个 MapperStatement,被放入MapperStatement集合中

final Map<String, MappedStatement> mappedStatementskey: 命名空间 namespace
val: 也是一个mappedStatement,也是k-v结构val的key: <select id="selectById" resultMap="result"> 中的id:selectByIdval的val:  sql语句

SQL语句是根据不同的sqlNode拼接而成的,sqlNode通过责任链的方式调用的,比如上文中的select、where、if 都是sqlNode,每一个sqlNode都有text文本,最后通过责任链的方式把这些文本拼接成真正的sql语句。如果sql上有参数,则先把参数预编译成 ?,再根据对应的 typeHandler 完成参数填充!

在这里插入图片描述
        

2.3 Configuration类中解析结果集映射

Configuration类中还封装了返回结果集 和 javabean的映射,可以看出Configuration确实帮我们包办了很多事情!
在这里插入图片描述
        

Configuration类如下:

public class Configuration {// 环境,封装了dateSource,可根据dataSource获取connectionprotected Environment environment;protected boolean safeRowBoundsEnabled;protected boolean safeResultHandlerEnabled = true;protected boolean mapUnderscoreToCamelCase;protected boolean aggressiveLazyLoading;protected boolean multipleResultSetsEnabled = true;protected boolean useGeneratedKeys;protected boolean useColumnLabel = true;protected boolean cacheEnabled = true;。。。。。。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();protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();。。。。。。 属性太多 这里不一一列举了!!

然后把Configuration配置类在构建成DefaultSqlSessionFactory

  // 到这里配置文件已经解析成了Configurationpublic SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

整个解析流程如下所示:
在这里插入图片描述
对于MyBatis启动的流程(获取SqlSession的过程)这边简单总结下:

        SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事 务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis 需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了 Configration配置类对象;


步骤二: 创建SqlSession会话

SqlSession代表一次数据库会话,它是一个门面,增删改查的职责由其内部的executor执行器实现的。

SqlSession session = SqlSessionFactory.openSession()源码如下:

  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);/*** 创建一个sql执行器对象* 一般情况下 若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回* 一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor*/final Executor executor = configuration.newExecutor(tx, execType);/*** 创建返回一个DeaultSqlSessoin对象返回*/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();}}

        可以看到openSession中创建了一个执行器Executor,返回了一个带有执行器executor的DefaultSqlSession对象,而DefaultSqlSession类内部的增删改查方法底层都是调用execute执行器完成!比如:executor.update(param,param)executor.selectOne(param)等等!

        

mybatis的三种执行器

在这里插入图片描述

  1. SimpleExecutor:简单执行器,以插入为例,每次插入后都会关闭Statement,所以每次插入会产生一条sql,一个返回结果!
  @Overridepublic 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 {//SimpleExecutor会关闭StatementcloseStatement(stmt);}}
  1. ReuserExecutor:复用执行器,顾名思义,在插入时会复用这条sql语句,所以在插入时,只产生一次sql,每插入一条数据,返回一个插入结果!相对于SimpleExecutor来说,这里使用的都是同一条预编译sql
  @Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);Statement stmt = prepareStatement(handler, ms.getStatementLog());//执行插入!return handler.update(stmt);}//源码与SimpleExecutor相比,不用每次插入都closeStatement!!
  1. BatchExecutor:批量执行器,一次插入多条数据,相当于一次插入操作,与其他的Executor相比:只产生一次sql,一个返回结果
 @Overridepublic int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {...... //省略代码handler.batch(stmt);return BATCH_UPDATE_RETURN_VALUE;}

        在创建执行器Executor时,会先判断执行器类型并选择,然后调用Mybatis中注册的插件,完成对执行器Executor的增强,如果没有对Executor增强的插件,则返回原来的Executor

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;/*** 判断执行器的类型*/if (ExecutorType.BATCH == executorType) {//批量的执行器executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {//可重复使用的执行器executor = new ReuseExecutor(this, transaction);} else {//简单的sql执行器对象executor = new SimpleExecutor(this, transaction);}//判断mybatis的全局配置文件是否开启缓存if (cacheEnabled) {//把当前的简单的执行器包装成一个CachingExecutorexecutor = new CachingExecutor(executor);}/*** TODO:调用所有的拦截器对象plugin方法,* 可以使用Mybatis拦截器在这里对执行器Executor进行扩展,实现自定义功能*/executor = (Executor) interceptorChain.pluginAll(executor);return executor;}

如果有使用Mybatis的@Interceptor插件对执行器进行了增强,那么在创建完执行器后,会执行插件中的增强逻辑!

Mybatis插件原理:动态代理

  public static Object wrap(Object target, Interceptor interceptor) {// 获得interceptor配置的@Signature的typeMap<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);// 当前代理类型Class<?> type = target.getClass();// 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {//创建动态代理return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}

代理内容如下:

  @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());if (methods != null && methods.contains(method)) {//如果有插件增强了Executor,则会执行插件中的intercept方法return interceptor.intercept(new Invocation(target, method, args));}return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}

流程图:
在这里插入图片描述


        

步骤三: 在sqlSession中操作数据

想要在sqlSession中操作数据,又分为两个步骤

  1. UserMapper接口创建 代理对象proxy
  2. 使用代理对象proxy调用selectById(1L)查询数据!

        

①:为Mapper接口创建代理对象

        mapper接口是无法直接调用方法的,但我们在使用mybatis时却没有手动的为mapper接口提供一个具体的实现,最终还是调用selectById()成功了,这是什么原因呢?

        首先要明确一点的是:肯定是使用mapper接口的实现对象来调用selectById()方法的。我们既然没有手动为mapper接口增加实现,那肯定是mybtais底层帮我们做了这件事情!为mapper接口生成了代理对象!

    // 执行查询 第一种方式,mybatis3之前的方式User user = (User)session.selectOne("com.zhai.mapper.UserMapper.selectById", 1);// 执行查询 第二种方式,mybatis3之后的方式UserMapper mapper1 = session.getMapper(UserMapper.class);User user1 = mapper1.selectById(1L);

        上述代码中session.getMapper的方式在底层代码也是对session.selectOne代码的包装,所以我们从源头session.getMapper开始看起!进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:

  @Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}

进入getMapper方法,其内部封装了一层mapperRegistry.getMapper(type, sqlSession),再次进入mapperRegistry中的getMapper方法中:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {/*** 直接去缓存knownMappers中通过Mapper的class类型去找我们的mapperProxyFactory*/final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);/*** 缓存中没有获取到 直接抛出异常*/if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {/*** 通过MapperProxyFactory代理工厂创建我们的mapper的代理对象!!*/return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

mapperProxyFactory.newInstance源码如下:

  public T newInstance(SqlSession sqlSession) {/*** 创建我们的代理对象,最终会调用MapperProxy中的invoke方法*/final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);/*** 创建我们的Mapper代理对象返回*/return newInstance(mapperProxy);}

如上代码中,MapperProxy就是创建的代理对象,由于MapperProxy实现了InvocationHandler,所以会调用其内部的invoke方法来执行查询操作!
在这里插入图片描述

总结:Mapper接口代理类生成流程图:
在这里插入图片描述

        
        

②:使用代理对象proxy调用selectById查询数据!

         MapperProxyMapper接口的代理类,调用Mapper接口的所有方法都会先调用到这个代理类的 invoke方法。MapperProxy的invoke方法非常简单,主要干的工作就是创建MapperMethod对象或者是从缓存中获取MapperMethod对象。获取到这个对象后执行execute方法。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {/*** 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用*/if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {   //是否接口的default方法,此处肯定不是return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}/*** 真正的进行调用,做了二个事情* 第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)*/final MapperMethod mapperMethod = cachedMapperMethod(method);//真正执行sql的执行器executereturn mapperMethod.execute(sqlSession, args);}

execute方法会判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作。 execute方法如下!

  public Object execute(SqlSession sqlSession, Object[] args) {Object result;/*** 判断我们执行sql命令的类型*/switch (command.getType()) {//insert操作case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}//update操作case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}//delete操作case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}//select操作case SELECT://返回值为空if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {//返回值是一个Listresult = executeForMany(sqlSession, args);} else if (method.returnsMap()) {//返回值是一个mapresult = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {//返回游标result = executeForCursor(sqlSession, args);} else {//查询返回单个/*** 解析参数*/Object param = method.convertArgsToSqlCommandParam(args);//重点来了! 查询方法会调用selectOne,接上了mybatis3之前的查询方式result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();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;}

        以查询为例,最后会调用selectOne()方法,这就接上了标题1中的 mybatis3之前的查询方式!selectOne()方法查询方法其实是利用了sqlSession的门面,底层是executor执行的查询,如果在mapper.xml映射文件中配置了缓存cache标签,查询时会首先去二级缓存中查,没有查到再去一级缓存中找,如果还是没有,才去查询数据库。查到后,把查询结果立即放入一级缓存。当前session提交后,再把数据放入二级缓存!

缓存这一块比较简单,就不贴这段代码了,着重看一下去数据库查询时的代码!

  // queryFromDatabase去数据库查询数据private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {// doQuery 真正的查询逻辑list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {//清空一级缓存localCache.removeObject(key);}//查询结束,把结果立即放入一级缓存!localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

其中,查询数据库的逻辑都封装在doQuery方法中,跟一下SimpleExecutordoQuery源码!

  @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {//获取Mybatis的最主要的配置类Configuration configuration = ms.getConfiguration();/* 四大核心对象之一 StatementHandler 内部封装了ParameterHandler和ResultSetHandler,* 如果有插件对这三个对象进行了增强,则都在newStatementHandler中进行* 插件执行顺序为:Executor ->  StatementHandler -> ParameterHandler -> ResultSetHandler*/StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//获取PrepareStatementHandlerstmt = prepareStatement(handler, ms.getStatementLog());//使用PrepareStatementHandler执行查询return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

可以使用插件增强的Mybatis的四个核心类如下:
在这里插入图片描述

接着进入handler.query执行查询方法:

  @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;//调用PreparedStatement进行查询ps.execute();//处理结果集return resultSetHandler.handleResultSets(ps);}

然后处理返回结果集:通过TypeHandler的关系映射,返回对应结果!处理逻辑位于DefaultResultSetHandler类中!

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {//根据TypeHandler的映射关系,完成数据库数据类型 到 类中属性的映射!// rsw.getResultSet(): 返回的结果集//  mapping.column:对象中的列final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {metaObject.setValue(mapping.property, value);}}}return foundValues;}

比如:mysql中的字符串类型是varchar,而java中的字符串类型是String,这就需要一个类型转换器StringTypeHandler来负责类型转换

public class StringTypeHandler extends BaseTypeHandler<String> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)throws SQLException {ps.setString(i, parameter);}// rs :返回的结果集// columnName:结果集中的key,可根据key获取值@Overridepublic String getNullableResult(ResultSet rs, String columnName)throws SQLException {// rs.getString(columnName)其实就是JDBC的代码,// 作用是把结果集中的字符串结果放入 java类中的属性中去return rs.getString(columnName);}@Overridepublic String getNullableResult(ResultSet rs, int columnIndex)throws SQLException {return rs.getString(columnIndex);}@Overridepublic String getNullableResult(CallableStatement cs, int columnIndex)throws SQLException {return cs.getString(columnIndex);}
}

        

自定义加解密TypeHandler

        我们也可以自定义TypeHandler,比如:加密解密的类型转换器secretStringTypeHandler,(参数加密入库、查询结果解密)。

  1. 自定义字符串加密类型处理器SecretStringTypeHandler

public class SecretStringTypeHandler implements TypeHandler<String> {@Overridepublic void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, getEncryptResult(parameter));}@Overridepublic String getResult(ResultSet rs, String columnName) throws SQLException {return getDecryptResult(rs.getString(columnName));}@Overridepublic String getResult(ResultSet rs, int columnIndex) throws SQLException {return getDecryptResult(rs.getString(columnIndex));}@Overridepublic String getResult(CallableStatement cs, int columnIndex) throws SQLException {return cs.getString(columnIndex);}/*** 加密字段* @param result 原始值* @return 加密值*/private String getEncryptResult(String result) {if (StringUtils.isBlank(result)) {return result;}try {result = stringEncryptor.encrypt(result);} catch (Exception e) {log.error("set param fail: {}, {}", result, e.getMessage());}return result;}/*** 解密字段* @param result 加密值* @return 原始值*/private String getDecryptResult(String result) {if (StringUtils.isBlank(result)) {return result;}try {result = stringEncryptor.decrypt(result);} catch (Exception e) {log.error("get result fail: {}, {}", result, e.getMessage());}return result;}
}
  1. SecretStringTypeHandler注入Mybatis
@Configuration
public class MybatisConfigurationCustomizer implements ConfigurationCustomizer {private static final Logger log = LoggerFactory.getLogger(MybatisConfigurationCustomizer.class);@Autowiredprivate SecretStringTypeHandler secretStringTypeHandler;@Beanpublic SecretStringTypeHandler secretStringTypeHandler(StringEncryptor stringEncryptor) {return new SecretStringTypeHandler(stringEncryptor);}@Overridepublic void customize(Configuration configuration) {log.debug("<-- MybatisConfigurationCustomizer ");//注意要加上类型别名configuration.getTypeAliasRegistry().registerAlias("secretStringTypeHandler", SecretStringTypeHandler.class);configuration.getTypeHandlerRegistry().register(secretStringTypeHandler);}
}
  1. 在使用时只需在对应XxxMapper.xml中加入自定义的类型转换器即可
//为想要执行加解密的属性 添加secretStringTypeHandler
<result column="family_address" jdbcType="VARCHAR" property="familyAddress" typeHandler="secretStringTypeHandler"/>

这样就可以利用自定义的secretStringTypeHandler来实现复杂的功能了
在这里插入图片描述

SqlSession执行sql操作数据流程总结如下:

在这里插入图片描述


                 

2. Mybatis是如何解析动态sql的?

        在执行SQL之前,需要将SQL语句完整的解析出来。我们都知道SQL是配置在映射文件中的,但由于映射文件中的SQL可能会包含占位符#{},以及动态SQL标签,比如<if><where>等。因此,我们并不能直接使用映射文件中配置的SQL。

        MyBatis会将映射文件中的SQL解析成一组SQL片段。如果某个片段中也包含动态SQL相关的标签,那么,MyBatis会对该片段再次进行分片,解析成一个个的XxxSqlNode,比如:ifSqlNode、whereSqlNode等等。最终,一个SQL配置将会被解析成一个SQL片段树。然后通过责任链的方式去调用每一个XxxSqlNode的解析实现类,然后把所有解析的sql片段追加到一个静态变量中去,就得到了可执行的SQL。

注意:mybatis对动态sql有过两次修改,如下:

  1. 动态sql解析时会把#{}转换成的预编译状态,还会根据语法自动处理<where>、<if>标签,并自动增加或删除and等连接符。这个过程发生在创建Configuration对象时
  2. 当真正执行查询操作时,比如:selectById(1L),此时才会将预编译的替换为真正的参数1

在这里插入图片描述


3. StatementHandler

mysql的架构:
在这里插入图片描述
        MyBatis的源码中,StatementHandler是一个非常核心接口。之所以说它核心,是因为从代码分层的角度来说,StatementHandlerMyBatis源码的边界,再往下层就是JDBC层面的接口了。
        StatementHandler需要和JDBC层面的接口打交道,它要做的事情有很多。在执行SQL之前,StatementHandler需要创建合适的Statement对象,然后填充参数值到Statement对象中,最后通过Statement对象执行SQL。这还不算完,待SQL执行完毕,还要去处理查询结果等。这些过程看似简单,但实现起来却很复杂。下面我们来看一下StatementHandler的继承体系。
在这里插入图片描述
RoutingStatementHandler没有什么逻辑,只是一个装饰器类,根据类型选择对应的StatementHandler

public class RoutingStatementHandler implements StatementHandler {private final StatementHandler delegate;public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {//根据类型选择对应的StatementHandlerswitch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/59750.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息