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

【mybatis原理】相关推荐

  1. 《深入理解mybatis原理》 MyBatis缓存机制的设计与实现

    本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存机制中的方方面面展开讨论. MyBatis将数据缓存设计成两级结构,分为一级缓存 ...

  2. 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)

    文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...

  3. mybatis 原理_深入理解MyBatis原理 MyBatis数据源与连接池

    点击上方"程序开发者社区"关注,选择"设为星标" 第一时间送达实用干货 对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文 ...

  4. java解析sql语句简书,Mybatis原理解析(一)--java.sql数据库操作的基本实现方式

    在研究Mybatis原理之前,先看一下java中是如何实现一次对数据库的访问的: public void testSql() { Connection connection = null; State ...

  5. 【mybatis原理工作原理】

    文章目录 mybatis原理: mybatis缓存机制 mybatis原理: mybatis的工作原理就是:先封装sql,接着调用jdbc操作数据库,最后把数据库返回的表结果封装成java类. 通过代 ...

  6. spring原理 struts2原理 hibernate原理 MyBatis原理 SpringMVC

    Spring原理 最核心的就是IOC,动态注入DI,利用java里的反射,让一个对象的创建不用new了,可以自动的生产.Spring就是在运行时,跟xml Spring的配置文件来动态的创建对象,和调 ...

  7. 《深入理解Mybatis原理》 02-Mybatis数据源与连接池

    对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文将通过对MyBatis框架的数据源结构进行详尽的分析,并且深入解析MyBatis的连接池. 本文首先会讲述MyB ...

  8. Spring整合MyBatis原理之Mapper接口和xml文件的解析

    目录 1. 前言 2. 类 `SqlSessionFactoryBean` 2.1. 实现了 `FactoryBean` 接口的 `getObject()` 2.2. `buildSqlSession ...

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

    MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...

  10. MyBatis原理解析

    参考自: MyBatis原理分析(通俗易懂)_Coder Wang-CSDN博客_mybatis原理 从源码一层一层进行分析,写得清晰易懂.我再这里就不赘述了,就写一下自己的大白话总结吧. Mybat ...

最新文章

  1. UITableView学习笔记
  2. Android控件默认风格解析之SeekBar
  3. 程序员真正的天赋是什么?
  4. 项目优化经验mdash;mdash;垃圾回收导致的性能问题[z]
  5. golang 工作池demo
  6. AbstractQueuedSynchronizer 源码分析(共享锁)
  7. aspen和python一起使用_python-将$cond语句与$project一起使用并在PyMongo中聚合
  8. IOS用正则验证手机号
  9. python获取线程返回值_如何从python中的线程获取返回值?
  10. Android按钮点击控制进度条,Android通过按钮和进度条实现音量调节(AudioManager)...
  11. 我的世界基岩版种子和java版种子_我的世界:当前基岩版最强村庄神种,村庄下面集合所有要素...
  12. 冰点下载器手机版apk_冰点下载器手机版apk
  13. 谷歌和金山词霸合作 翻译行业垄断出现
  14. 手机市场的竞争,用户价值才是硬道理
  15. 【Android踩过的坑】5.android.content.res.Resources$NotFoundException: String resource ID #0x0
  16. 【hadoop生态之Hbase】HBase部署与使用【笔记+代码】
  17. Android获取汉字首字母工具类
  18. JBOSS EAP 6 系列七 JPA/hibernate
  19. 硬件工程师到底要会多少东西?
  20. CA双向认证完整实现步骤(附java客户端代码)

热门文章

  1. 老爸:“你做的什么游戏测试简直是不务正业!”——我上去就是一顿猛如虎的解释。
  2. MyBatis快速入门——第三章、DML语句操作
  3. 数据库系统对一些常见题型的解答(对学生表的操作以及对spj表的操作)
  4. 线性基(插入与查询)
  5. 使用 Servlet 自动刷新页面
  6. 聊天技巧必备:如何在聊天中保持自信?
  7. 数据结构与算法分析 作业讲解文档目录
  8. 进化论--人类最大的科学误区(2)
  9. 电压互感器 电流互感器
  10. zeppelin的安装以及使用