文章目录

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

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();}}
}

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

上述代码

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

在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());}}

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

  1. java计算机毕业设计Vue框架龙猫宠物交易平台MyBatis+系统+LW文档+源码+调试部署

    java计算机毕业设计Vue框架龙猫宠物交易平台MyBatis+系统+LW文档+源码+调试部署 java计算机毕业设计Vue框架龙猫宠物交易平台MyBatis+系统+LW文档+源码+调试部署 本源码技 ...

  2. mybatis mysql 调用存储过程 多个返回值_图解MyBatis的SQL执行流程(干货)

    前言 MyBatis可能很多人都一直在用,但是MyBatis的SQL执行流程可能并不是所有人都清楚了,那么既然进来了,通读本文你将收获如下: 1.Mapper接口和映射文件是如何进行绑定的 2.MyB ...

  3. 「Vue 学习笔记 1」Vue 项目快速搭建,初始项目各个文件夹作用介绍和启动代码执行流程分析

    「Vue 学习笔记 1」Vue 项目快速搭建,初始项目各个文件夹作用介绍和启动代码执行流程分析 前言 一.我的开发环境 二.使用 Vue CLI (Vue 脚手架)快速搭建项目 三.初始项目的目录结构 ...

  4. springmvc源码阅读3--dispatcherServlet reqeust的执行流程

    一.前言 太多的时候总是会遇到群里面问,报404怎么肥事呀,前台的参数怎么后台收不到呀--,上次就在群里面遇到过,围绕这一个点:input的name值是不是错了呀,人家妹子都截了好几次图说没有问题,然 ...

  5. springMvc的执行流程(源码分析)

    1.在springMvc中负责处理请求的类为DispatcherServlet,这个类与我们传统的Servlet是一样的.我们来看看它的继承图 2. 我们发现DispatcherServlet也继承了 ...

  6. Spring5源码 - 07 Spring Bean 生命周期流程 源码解读02

    文章目录 Pre 通俗流程 finishBeanFactoryInitialization Pre Spring5源码 - 06 Spring Bean 生命周期流程 概述 01 接上文 通俗流程 下 ...

  7. 跟随一笔交易来看以太坊c++客户端源码执行流程 / 源码分析

    本文初步分析了一个交易在以太坊内部的处理流程,涉及到交易的接收,检查,执行,同步,区块的构建以及挖矿,结合前面一篇基于黄皮书的理解总结,对以太坊有了更多的认识.因为主要的工作在c++层面,所以这里以c ...

  8. yaf php源码,PHP-Yaf执行流程-源码分析

    介绍 Yaf框架是通过PHP扩展实现的一种PHP MVC开发框架,因为Yaf框架是PHP扩展实现的,所以性能非常高,而且通过阅读源码可以深入的了解框架内部实现细节和一些优秀的扩展开发技巧,但是学习Ya ...

  9. 微软AJax.net源码初步分析(2)--服务执行流程

    我以一个最简单的helloworld为例,演示AJax.net源码中调用后台服务的机制,只是列出一些大体的框架,具体细节我还在研究中:) 不当之处,欢迎指正. 我先把例子中的核心代码列出,方便大家阅读 ...

最新文章

  1. iOS实现图片自动轮播展示
  2. CRM 客户端程序开发:获取表单界面上各种字段的值及其他属性
  3. 西安交通大学计算机科学与技术学科评估,西安交通大学学科评估排名!附西安交大a类学科名单...
  4. linux内核网络协议栈--sk_buff结构体(四)
  5. 课后练习----实现窗口的切换
  6. Silverlight:应用程序模型
  7. 阿里妈妈流量反作弊算法实践
  8. MySQL的几个character_set变量的说明
  9. python __name__怎么使用_python学习笔记26(python中__name__的使用)
  10. oracle中包含用什么函数,oracle中包含用什么函数 oracle数据包含有什么函数?
  11. 解决 googel 无法直接跳转网页打开搜索结果
  12. 还在苦恼如何查看微信共同好友吗?用Python轻松搞定
  13. java八皇后答案_java八皇后问题详解
  14. 尚硅谷web前端工程师1000集学习笔记11
  15. 【2020年08月10日已解决】联想小新pro13锐龙版2020wifi问题解决方案
  16. 财经365独家:基建物业投资路线图
  17. 网站静态化——前后端分离(下)
  18. 论文翻译—3D NDT算法论文(节选6.1-6.2)
  19. 分享13道Redis面试题,助你面试不再慌
  20. Numpy库的介绍及使用

热门文章

  1. 好玩Python--分析你的微信好友签名
  2. 通过系统进程查找sql语句
  3. Baseline管理
  4. 经常使用的webservice接口
  5. 皕杰报表和炎黄盈动(AWS BPM)集成 操作手册
  6. 《奠基计算机网络》2011年8月15日出版 视频下载 http://www.91xueit.com
  7. Asp.net禁用页面缓存的方法总结
  8. [WebException: The underlying connection was closed: The message length limit was exceeded.]解决方法...
  9. 高清接口芯片---gv7600、sii9135
  10. 关于子网掩码的是是非非