【mybatis原理】

article/2025/9/1 12:59:20

mybatis

    • mybatis原理
      • mybatis框架分层架构
      • 核心接口和对象
      • mapper接口与xml的映射
      • mybatis执行过程
      • mybatis执行时序图
      • 一级缓存和二级缓存
        • 一级缓存
        • 二级缓存
      • mybatis核心流程
        • 1、初始化阶段
        • 2、代理阶段
        • 3、数据读写阶段
      • mybatis如何获取数据源
      • mybatis如何获取执行SQL
      • MyBatis 如何执行 sql 语句?

mybatis原理

mybatis框架分层架构

在这里插入图片描述

核心接口和对象

在这里插入图片描述
在这里插入图片描述

mapper接口与xml的映射

在这里插入图片描述

mybatis执行过程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

mybatis执行时序图

在这里插入图片描述

一级缓存和二级缓存

在这里插入图片描述

一级缓存

一级缓存执行时序图:
在这里插入图片描述

Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
 为什么要使用一级缓存,不用多说也知道个大概。但是还有几个问题我们要注意一下。

1、一级缓存的生命周期有多长?

a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

2、怎么判断某两次查询是完全相同的查询?

mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

2.1 传入的statementId

2.2 查询时要求的结果集中的结果范围

2.3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

2.4 传递给java.sql.Statement要设置的参数值

一级缓存总结:
MyBatis一级缓存的生命周期和SqlSession一致
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

二级缓存

MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
在这里插入图片描述
MyBatis的缓存机制整体设计以及二级缓存的工作模式:
在这里插入图片描述

SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了,如果我们配置了二级缓存就意味着:

  • 映射语句文件中的所有select语句将会被缓存。 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
  • 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

在这里插入图片描述

mybatis核心流程

mybatis的核心流程,包括:初始化阶段、代理阶段、数据读写阶段
在这里插入图片描述

1、初始化阶段

初始化阶段就是mybatis解析xml文件(mybatis-config.xml和xxxmapper.xml),将xml里面的内容解析到Configuration对象中(全局单例模式)

在将解析的流程之前先介绍一下几个对象

XmlConfigBuilder:解析mybatis-config.xml

XmlMapperBuilder:解析xxxMapper.xml

XmlStatementBuilder:解析xxxMapper.xml中的增删改查的sql

初始化过程的总结:

1、将xml的内容解析到configuration中
在这里插入图片描述
2、configuration中的关键的属性对应到xml的内容

(1)Configuration属性填充
在这里插入图片描述

(2)resultMap解析
在这里插入图片描述

(3)MappedStatement内容图解
在这里插入图片描述

2、代理阶段

早些年在使用ibatis时候,其实是没有这个代理阶段的过程,我们使用如下的方式进行编程(面向sqlsession编程):

@Test
public void queryUser() {SqlSession sqlSession = sqlSessionFactory.openSession();TUser user = sqlSession.selectOne("com.taolong.mybatis.test.TUserMapper.slectUserById", 1);System.out.println(user);
}

当apache对ibatis进行了修改之后编程了mybatis后,我们使用下面的方式进行编程(面向接口编程,达到解耦,程序员更喜欢的方式):

@Test
public void queryUser2() {SqlSession sqlSession = sqlSessionFactory.openSession();TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);TUser user = mapper.queryUserById(1);System.out.println(user);
}

从ibatis到mybatis的过程如下图所示,所以今天将的mybatis核心流程中的代理阶段和数据读写阶段就是如下图中的翻译的过程:
在这里插入图片描述

这里有个问题,就是TUserMapper是一个接口,并没有具体的实现类,那么mybatis是如何通过TUserMapper的接口来调用方法呢?带着这个问题,我们来阅读源码。同样通过debug的方式来跟踪源代码

在这里插入图片描述
进入getMapper,查看具体源码:
defaultSqlsession.getMapper():从configuration中动态获取Mapper
在这里插入图片描述
configuration.getMapper(): 从configuration中的注册mapper中心获取mapper对象
在这里插入图片描述
mapperRegistry.getMapper()

@SuppressWarnings("unchecked")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 {//通过工厂模式生成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是代理类,该类实现了InvocationHandler,所以可以看出是使用了动态代理模式final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);
}

newInstance()

@SuppressWarnings("unchecked")protected T newInstance(MapperProxy<T> mapperProxy) {//返回动态代理生成的代理对象return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

上面就是通过动态代理生成了一个mapper的对象进行增强,所以第二阶段的绑定阶段就是通过动态代理产生一个Mapper的对象,其实返回就是MapperProxy,当调用mapper的方法时,其实就是调用了MapperProxy中的invoke方法,这个动态代理的地方建议大家深入的了解一下,重点看一下MapperProxyFactory类、MapperProxy、InvocationHandler、Proxy如何生成动态代理。到这里,我们已经能够解释上面的一个问题(就是InternalToolMapper是一个接口,并没有具体的实现类,那么mybatis是如何通过InternalToolMapper的接口来调用方法呢?)因为这里的InternalToolMapper是生成的一个动态代理类来代理InternalToolMapper,而非真正的InternalToolMapper。

代理阶段流程梳理:
1、先从Configuration配置类MapperRegistry对象中获取mapper接口和对应的代理对象工厂信息(MapperProxyFactory)。
2、利用代理对象工厂MapperProxyFactory创建实际代理类(MapperProxy)
3、在MapperProxy类中通过MapperMethod类对象内保存的中对应方法的信息,以及对应的sql语句的信息进行分析,最终确定对应的增强方法进行调用。

3、数据读写阶段

既然知道TUserMapper是生成的动态代理类,那么当调用int user = mapper.updateBySql(“sql”);时应该是调用了MapperProxy的invoke方法,我们继续跟着流程走,在继续流程之前我们带着第二个问题思考:sqlsession中有很多方法(select One,selectList,selectMap等)在ibatis中我们在代码中注明了要调用哪个方法,但是在mybatis没有注明,那么mybatis是如何知道调用的是哪个方法呢?

(1)MapperProxy.invoke()

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 (isDefaultMethod(method)) {//判断是否是默认方法,不增强return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}//MapperMethod方法就是封装了mapper接口中对应方法的信息,它是mapper接口和sql语句的桥梁final MapperMethod mapperMethod = cachedMapperMethod(method);//如果是我们定义在mapper的操作数据库的方法,则执行下面代码return mapperMethod.execute(sqlSession, args);}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;}

这里mappedMethod非常重要,因为它封装了mapper接口中的方法信息,它是mapper接口和sql语句的桥梁,是通过它来确定调用sqlsession的具体的哪个方法,大家可以先看一下它的数据结构,MappedMethod中的SqlCommand里面封装了SqlCommandType(insert、update、delete、select),里面的name封装了对应的mapper接口名和方法名;MappedMethod中的MethodSignature封装了接口方法的返回值类型,以及ParamNameResolver可以解析出接口的入参。所以通过MappedMethod就可以知道他是调用sqlsession的哪个方法(sqlcommandType可以知道是增删改查的哪一个,再看它的返回类型是list还是一个对象就知道是调用sqlsession的selectOne还是selectList…)以及xml中的具体的哪个方法.

sqlSession原子的操作最终就在mapperMethod.execute()出现了。也就是当我们通过mapper对象调用接口方法时,方法被路由到MapperProxy中,最终通过MapperMethod核心类包装进行当前会话的原子CRUD操作。

(2)mapperMethod.execute()

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT://根据methodSignature,判断是调用执行的是哪个方法,if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {//这些方法最终都是会调用sqlsession.select方法result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);//其实selectOne其实最终也会调用selectList,然后去集合的第一个内容而已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;}

(3)DefaultSqlSession.selectOne()

public <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.//还是会调用selectList方法List<T> list = this.<T>selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}

(4)selectList()

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {//通过接口名+方法名获取(namespace+方法名)MappedStatementMappedStatement ms = configuration.getMappedStatement(statement);//调用executor的query方法,查询return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

这里注意最后一句executor.query()方法,这里是使用了代理设计模式,BaseExecutor和CachingExecutor都是实现了Executor方法,那么这里是进入了CachingExecutor方法,这里面会涉及到mybatis中二级缓存的一些逻辑

(5)cachingExcutor.query()

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {//获取mappedStatement的CacheCache cache = ms.getCache();if (cache != null) {//是否需要清空cache(在xml文件中的cache标签设置,比如flushInterval时间到期)flushCacheIfRequired(ms);//判断是否使用cache,xml文件中设置if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);//直接从缓存中获取结果@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);//查询的结果保存到缓存中tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}//如果没有设置缓存,那么就直接调用被代理的对象方法查询return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

这里就是处理mybatis的二级缓存的逻辑,这是在一级缓存之前处理的,所以如果同时配置二级缓存和一级缓存那么会先使用二级缓存。另外判断是否使用二级缓存需要在mybatis-config.xml中配置属性cacheEnable和在相应的xml中配置cache标签属性。最后就是使用代理设计模式调用BaseExecutor.query()(baseExecutor的子类)

(6)baseExecutor.query()

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}//判断是否需要清空一级缓存if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;//从一级缓存中取出结果,返回list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//从数据中查询结果list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}

这里是涉及到mybatis的一级缓存的逻辑能从一级缓存中获取结果就取出结果,否则就查询数据库。有两个需要注意的地方**:1,一级缓存中的CacheKey是计算时非常严格的它是由mappedStatement,parameter,rowBounds和boundSql一起生成的一个值**;2,如果有update、insert、delete这些操作,缓存是会清空的。感兴趣的可以深入了解一下。接着流程往下

(7)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 {//查询结果,这里使用了模板设计模式,list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}//查询的结果加入到缓存中,方便下次使用localCache.putObject(key, list);//这下面应该是存储过程相关的,如果是存储过程那么类型就是Callableif (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

接着往下看,我们是调用doQuery()方法,但是BaseExecutor是没有实现doQuery(),这里实际上使用了模板设计模式,将操作延迟到子类中(BatchExecutor,CloseExecutor,ReuseExecutor,SimpleExecutor)

SimpleExecutor:默认配置,使用statement对象访问数据库,每次访问都要创建statement对象

ReuseExecutor:使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象

BatchExecutor:实现批量操作多条sql的能力

不同的子类有不同的实现,如果想了解更多关于模板设计模式,请参考(模板设计模式),我们这里看SimpleExecutor.doQuery()

(8)simpleExecutor.doQuery()

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();//这里面生成的是statementHandler的实现类 RoutingStatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());//调用 RoutingStatementHandler.query()return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}

这里为什么要使用RoutingStatementHandler.query()方法呢,其实是使用了静态代理设计模式,请继续往下看

(9)RoutingStatementHandler.query()

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {return delegate.<E>query(statement, resultHandler);}

我们来看看RoutingStatementHandler中被代理的对象是哪个

(9-1)RoutingStatementHandler()

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {//其实从这里判断是选择代理哪个statementswitch (ms.getStatementType()) {//默认的statementcase STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;//预编译的statementcase PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;//存储过程的statementcase CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}

其实这里就是使用静态的代理模式判断到底需要代理哪一个statement,不得不赞叹mybatis的代码写的非常优雅,一个看似非常简单的地方,如果换作是我们直接在就在上面使用if else判断得了。我们这里显然是用了PreparedStatement,因为会预编译

(10)PreparedStatement.query()

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

其实到这里就已经完成了一次数据库的查询操作。

在这里插入图片描述
MapperRegistry : mapper接口和对应的代理对象工厂的注册中心;

MapperProxyFactory:用于生成mapper接口动态代理的实例对象;

MapperProxy:实现了InvocationHandler接口,它是增强mapper接口的实现;

MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享;

mybatis如何获取数据源

    <environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment></environments>// 获取配置文件String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);// 构建SqlSessionFactory对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 创建 XMLConfigBuilder 对象XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 执行 XML 解析// DefaultSqlSessionFactory对象return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}

在这里插入图片描述

mybatis如何获取执行SQL

与获取数据库源类似,只要解析Mapper配置文件中的对应标签,就可以获得对应的sql语句。之前我们讲过,SqlSessionFactory中的configuration属性保存数据库源信息,事实上这个configuration属性将整个配置文件的信息都给封装成一个类来保存了。解析的前半部分与之前一样,分歧点在之前提到的parseConfiguration方法,其中在environmentsElement方法下面还有一个mapperElement方法。

在这里插入图片描述
在这里插入图片描述
配置文件中mappers标签加载mapper文件的方式共有四种:resource、url、class、package。代码中的if-else语句块分别判断四种不同的加载方式,可见package的优先级最高。parent是配置文件中mappers标签中的信息,通过外层的循环一个一个读取多个Mapper文件。这里使用的方式是resource,所以会执行光标所在行的代码块,进入**mapperParser.parse()**方法。

在这里插入图片描述
我们要的是 mapper 标签的内容,因此我们关注 configurationElement(parser.evalNode("/mapper")) 这一句,进入 configurationElement 方法。
在这里插入图片描述
context 就是我们解析整个 Mapper 文件 mapper 标签中的内容,既然现在得到了内容,那只需再找到对应的标签就能获得sql语句了。注意 buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)),我们看到了熟悉的 select、insert、update、delete,这些标签里就有我们写 sql 语句。进入 buildStatementFromContext 方法
在这里插入图片描述
list 保存了我们在 Mapper 文件中写的所有含有 sql 语句的标签元素,用一个循环遍历 list 的每一个元素,分别将每一个元素的信息保存到 statementParser 中。进入 parseStatementNode 方法。

在这里插入图片描述
在这里插入图片描述
这个方法代码内容很多,仅摘出节选,里面定义了很多局部变量,这些变量用来保存sql语句标签(例如)的参数信息(例如缓存useCache)。再把所有参数传到addMappedStatement中。进入addMappedStatement方法。

在这里插入图片描述
MappedStatementstatement=statementBuilder.build(),使用build方法得到MappedStatement实例,这个类封装了每一个含有sql语句标签中所有的信息,再是configuration.addMappedStatement(statement),保存到configuration中

MyBatis 如何执行 sql 语句?

既然有了SqlSessionFactory,我们可以从中获得SqlSession的实例。开启session的语句是SqlSessionsession=sessionFactory.openSession(),进入openSession方法。

在这里插入图片描述
在这里插入图片描述
最终会执行openSessionFromDataSource方法。在之前environment已经有了数据库源信息,调用configuration.newExecutor方法。
在这里插入图片描述
Executor叫做执行器Mybatis一共有三种执行器,用一个枚举类ExecutorType保存,分别是SIMPLE,REUSE,BATCH,默认就是SIMPLE。if-else语句判断对应的类型,创建不同的执行器。在代码末端处有个if判断语句,如果cacheEnabled为true,则会创建缓存执行器,默认是为true,即默认开启一级缓存。

回到openSessionFromDataSource方法,最终返回一个DefaultSqlSession实例。得到session我们就可以执行sql语句了。SqlSession提供了在数据库执行SQL命令所需的所有方法。你可以通过SqlSession实例来直接执行已映射的SQL语句,以selectOne方法为例,进入该方法后发现,最终会调用到selectList方法

public static void main(String[] args) throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//开启sessionSqlSessionsession=sessionFactory.openSession()//SqlSession接口的selectOne(...)方法运行自己写的方法如下:第一个参数是接口方法的全限定命名,第二个参数是方法需要的参数User one =sqlSession.selectOne("com.it55.mybatis.mapper.MyMapper.getOne", 1);
}

在这里插入图片描述
configuration.getMappedStatement(statement)得到了我们之前保存的MappedStatement对象,再调用executor.query 方法,调用 query 方法之前会执行 wrapCollection 方法,保存 sql 语句中用户传入的参数。进入 query 方法。
在这里插入图片描述
configuration.getMappedStatement(statement)得到了我们之前保存的MappedStatement对象,再调用executor.query 方法,调用 query 方法之前会执行 wrapCollection 方法,保存 sql 语句中用户传入的参数。进入 query 方法。
在这里插入图片描述
boundSql 里面就有我们要执行的 sql 语句,CacheKey 是用来开启缓存的。执行父类 BaseExecutor 中的 createCacheKey 方法,通过 id,offsetid,limited,sql 组成一个唯一的 key,调用下一个 query 方法。
在这里插入图片描述
Cache cache = ms.getCache() 是二级缓存,二级缓存为空,直接调用 query 方法。
在这里插入图片描述
list = resultHandler == null ? (List) localCache.getObject(key) : null 传入 key 值在本地查询,如果有返回证明 key 已经缓存到本地,直接从本地缓存获取结果。否则 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql),去数据库查询。

在这里插入图片描述
localCache.putObject(key, EXECUTION_PLACEHOLDER) 首先将 key 缓存至本地,下一次查询就能找到这个 key 了。进入 doQuery 方法。
在这里插入图片描述
stmt = prepareStatement(handler, ms.getStatementLog()),得到一个 Statement。进入 prepareStatement 方法。
在这里插入图片描述
我们看到了一个熟悉的 Connection 对象,这个就是原生 JDBC 的实例对象。回到 doQuery 方法,进入 handler.query(stmt, resultHandler) 方法。
在这里插入图片描述
statement 强转型为 PreparedStatement 类型,这下我们又得到了 PreparedStatement 的类型实例了,调用 execute 方法,这个方法也是属于原生 JDBC。执行完成后 return resultSetHandler.handleResultSets(ps),进入 handleResultSets 方法。

在这里插入图片描述

参考:
https://tech.meituan.com/2018/01/19/mybatis-cache.html


http://chatgpt.dhexx.cn/article/WjuI0XYP.shtml

相关文章

Mybatis原理

文章目录 - 什么是Mybatis&#xff1f;- Mybaits的优点&#xff1a;- MyBatis框架的缺点&#xff1a;- MyBatis与Hibernate有哪些不同&#xff1f;- 架构MyBatis缓存一级缓存一级缓存和sqlsession之间的关系一级缓存的生命周期有多长&#xff1f;SqlSession 一级缓存的工作流程…

深入详解Mybatis的架构原理与6大核心流程

MyBatis 是 Java 生态中非常著名的一款 ORM 框架&#xff0c;目前在一线互联网大厂中应用广泛&#xff0c;Mybatis已经成为了一个必会框架。 如果你想要进入一线大厂&#xff0c;能够熟练使用 MyBatis 开发已经是一项非常基本的技能&#xff0c;同时大厂也更希望自己的开发人员…

总结Mybatis的原理

一、什么是Mybatis Mybatis是一个半ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;底层封装了JDBC&#xff0c;是程序员在开发时只需要关注SQL语句本身&#xff0c;不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。MyBatis 可以使用简单的 XM…

MyBatis基本工作原理介绍

1、MyBatis基本工作原理介绍 计算机的基本工作就是存储和计算&#xff0c;而MyBatis是存储领域的利器。MyBatis的基本工作原理就是&#xff1a;先封装SQL&#xff0c;接着调用JDBC操作数据库&#xff0c;最后把数据库返回的表结果封装成Java类。 2、MyBatis的核心流程介绍 m…

Mybatis 工作原理详解

目录 Mybatis持久层框架 结果集进行ORM映射 步骤解析 1、获取结果集及结果映射入口 2、开始ORM映射接口 3、数据库结果集解析&#xff0c;完成ORM映射 4、保存并获取ORM映射后的结果集 参数传递方式 顺序传参法 Param注解传参法 Map传参法 Java Bean传参法 mybat…

《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

MyBatis是目前非常流行的ORM框架&#xff0c;它的功能很强大&#xff0c;然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路&#xff0c;并且讨论MyBatis的几个核心部件&#xff0c;然后结合一个select查询实例&#xff0c;深入代码&#xff0c;来探究MyBatis的实…

【图片压缩】三个方法压缩图片体积

图片体积过大&#xff0c;会占用电脑过多的容量&#xff0c;也有可能无法发送给其他人。我们可以压缩图片体积来解决问题。分享三个方法压缩图片体积&#xff1a; 方法一&#xff1a;改变图片格式 首先是改变图片的格式&#xff0c;如果你不介意压缩之后的图片画质损失&#…

Windows不同压缩软件、压缩算法、压缩率详细对比测试与选择

上次写了图片压缩&#xff0c;这倒让我想起几年前看过的一个很有意思的东西 那就是这张鸭子图&#xff1a; 不过微信会压缩图片&#xff0c;你可以打开这个链接&#xff1a;http://2.im.guokr.com/F70Kn-4wz7aF5Yejf9W3g6kO4exDBqVEb0TumQmxy5MiAQAAEAEAAEpQ.jpg 来获取原图 …

为什么压缩图片和压缩

为什么要压缩图片&#xff1f; 表示图像需要大量的数据&#xff0c;但图像数据是高度相关的&#xff0c;或者说存在冗余(Redundancy)信息&#xff0c;去掉这些冗余信息后可以有效压缩图像&#xff0c;同时又不会损害图像的有效信息。 视网膜上有两种感光细胞&#xff0c;能够…

2款免费的图片压缩工具

今天写这篇文章的目的&#xff0c;主要是为大家介绍2款免费的图片压缩工具&#xff0c;在工作和学习中这也是一项必备的技能。比如当你遇到比较大的图片需要发送的时候&#xff0c;或者对图片的大小有强制要求的时候&#xff0c;这些小工具就派上了用场。 网上也有一些付费的压…

如何批量压缩图片体积大小kb?

工作中&#xff0c;我们在使用图片素材时&#xff0c;图片kb体积过大怎么办&#xff1f; 通常我们会直接在电脑上将图片调整成我们想要的尺寸大小&#xff0c;可以利用的工具有photoshop或者截图的方法&#xff0c;这对于有些小伙伴来说不是很难的事情&#xff0c;今天我就不做…

图片批量压缩方法及步骤

图片批量压缩方法及步骤&#xff01;平常我们会将手机拍摄的照片传输到电脑里保存&#xff0c;时间久了后电脑中会有大量的图片&#xff0c;这些图片大都是1M-2M的体积大小&#xff0c;这些图片会占用大量的电脑磁盘空间&#xff0c;可能会导致电脑变得很卡等现象。但是又不忍心…

银河麒麟批量压缩图片的方法

适用系统&#xff1a;银河麒麟V10(SP1)&#xff0c;CPU&#xff1a;Kirin990&#xff0c;架构&#xff1a;aarch64。 软件商店下载“简单图像压缩转换软件”。桌面左下角点开菜单搜索“Simple Image Reducer”&#xff0c;右键添加到桌面快捷方式。打开Simple Image Reducer。…

python压缩图片和视频

引言 在真实项目中&#xff0c;往往要进行图片的传输和视频的传输&#xff0c;但因为用户上传的图片和视频所占用的大小问题而导致占用服务器的空间多&#xff0c;并且用户访问这些图片的时候因为图片或视频太大而长时间加载&#xff0c;所以要对用户上传的视频和图片进行压缩…

7款最好用的图片无损,视频无损压缩软件

第一:QVE视频压缩软件 下载地址:http://www.qvevideo.com/compress 非常实用的视频,图片两用压缩软件,能够将各种视频压缩成mp4,flv等格式, 缩减视频体积,释放磁盘空间,节省网络带宽,压缩后能够保持视频高清晰度,压缩率高达90%以上。 1,首先打开软件,切换到【图片压缩】…

苹果手机解压缩软件_照片压缩软件哪款好用?推荐5款好用的图片压缩软件

在我们办公的时候&#xff0c;总会遇到各种各样的问题&#xff0c;就比如PDF与文档之间的转换&#xff0c;图片的压缩&#xff0c;文档加密的方法等等&#xff0c;这些都是上班族经常需要处理的问题&#xff0c;特别是图片压缩&#xff0c;很多人都想知道照片压缩软件哪款好&am…

分享一个好用的图片压缩软件

为了性能优化需要&#xff0c;一般需要优化网站上的图片&#xff0c;减少大小。但问题来了&#xff0c;很多压缩软件是有损压缩&#xff0c;压缩后图片质量惨不忍睹。 下面我分享一下刚刚了解到的图片压缩软件&#xff0c;名字叫智图。 官网地址是&#xff1a; http://zhitu.is…

无损对图片进行压缩软件Caesium使用方法及下载

无损对图片进行压缩&#xff0c;除了用PS外&#xff0c;还有一款小白也可以用的软件Caesium。 使用方法如下&#xff1a; 1、打开文件夹&#xff0c;双击Caesium.exe 2、弹出的界面如下 3、点击左上角红色方框打开文件5837014_0_0.jpg&#xff0c;压缩选项的品质任意修改&…

开源免费的图片压缩软件,从50M到50K,极力安利

我相信大家在生活中肯定每日都离不开图片的处理了&#xff0c;比如&#xff1a;考试报名、图片传送、网页图片上传的过程中&#xff0c;都或多或少遇到过“您上传的图片太大”的问题 每次遇到这种情况是真的有点心塞&#xff0c;所以今天特别给大家带来一款人人必备的实用、超好…

如何压缩图片200k以下?

“图片过大无法上传”&#xff0c;“因您上传的图片大小超过限制”这样的提示大家都有遇到过吧&#xff1f;这就是告诉我们现在的图片需要压缩一下才能继续使用了&#xff0c;否则我们就无法使用当前照片了。 那有很多平台&#xff0c;特别是上传一些证件照的时候&#xff0c;要…