MyBatis源码相对于Spring应该是层次比较清晰,容易理解的;其实简单来说就是解耦、封装,让开发者更关注业务层的开发,实现简单而又方便的调用。

本文主要介绍mybatis框架的几个重要点:xml文件加载初始化、mapper接口动态代理加强和源码的一些模块以及所涉及的设计模式;通过阅读本文之后你将会学到Mybatis的工作流程以及常用的框架技术和模式。

文章目录

一 、xml文件加载初始化

二、Mapper接口动态代理加强

三、Mybatis涉及的模块以及设计模式使用


注:以下介绍主要是基于使用注解的SpringBoot配置。

一 、xml文件加载初始化

Mybatis加载XML文件核心类:

XMLConfigBuilder:解析Mybatis-config.xml文件节点</configuration>;这一块可以忽略,因为现在几乎都是Spring集成Mybatis,通过application.properties来配置,跳过config文件解析直接进入第二阶段XMLMapperBuilder解析mapper.xml文件。

XMLMapperBuilder:解析mapper.xml文件节点</mapper>;Mybatis结合springBoot或者Spring使用时,在配置dataSource创建SqlSessionFactory时启动的XMLMapperBuilder对象。主要有ResultMap、Sql节点等。

XMLStatementBuilder:解析select|insert|update|delete,封装成MappedStatement。

bing绑定:绑定mapper class和对代理工厂对象,mapperRegistry是其注册中心;创建Mapper接口的代理工厂类MapperProxyFactory,该类主要为Mapper接口方法生成代理类(MapperProxy(JDK动态代理))进行增强。

集成SpringBoot初始化XML:

 配置数据源时SqlSessionFactory是必不可少,它是生成SqlSession的工厂类(默认DefaultSqlSessionFactory);而SqlSessionFactory是由SqlSessionFactoryBean实例化的如下图代码:

  • MapperLocations 就是设置Mapper文件资源的;
  • plugins是引入插件例如分页插件,关于插件第三节中详细介绍;
  • 加载解析xml,在getObject()------->afterPropertiesSet()------->buildSqlSessionFactory(),最后这个方法会真正调用Mybatis加载XML文件核心类加载、初始化mapper文件,这边主要介绍Mapper是如何绑定代理工厂的

注意BeanFactory和FactoryBean的区别

public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(datasource);// 设置mybatis的xml所在位置bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/test1/*.xml"));bean.setPlugins(new Interceptor[]{DefinePageInterceptor.buildPageInterceptor()});// FactoryBean就是通过getObject()获取真正的bean实例(注意BeanFactory和FactoryBean的区别)return bean.getObject();
}

Mapper是 如何绑定代理工厂(注意文件中的注释):

// 1、XMLMapperBuilder 为Mapper绑定代理工厂
private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);// 1. 绑定Mapper的代理工厂MapperProxyTactory---入口configuration.addMapper(boundType);}}}
}-----------------------------------------------------------------------------
// 1、MapperRegistry 注册Mapper对应的代理工厂类
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {knownMappers.put(type, new MapperProxyFactory<T>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}

二、Mapper接口动态代理加强

问题思考:Mybatis是如何通过Mapper接口进行sql操作的?

MapperProxy就是Mapper接口的代理类,这个代理类由代理工厂类MapperProxyFactory生成如图所示(此工厂类在第一步中已经将Mapper接口类型作为key进行缓存),MapperProxy的必须属性参数有mapper相关的sqlsession,mapper接口类型Class。

// 1、MapperRegistry:注册中心获取代理工厂
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 {// 2、代理工厂创建代理类return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}-------------------------------------------------------------------------------------// 1、MapperProxyFactory 代理工厂类创建代理protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}

1、动态代理当调用Mapper接口中的方法就会交由MapperProxy进行代理

  @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {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);}// 1、获取MapperMethod 执行execute(method的执行是放在MapperMethod中的)final MapperMethod mapperMethod = cachedMapperMethod(method); //  2、MapperMethod中根据SqlCommand的类调用sqlSession的 insert|update.....return mapperMethod.execute(sqlSession, args);}// 3、先取缓存如果没有,创建一个新的(封装了mapper接口、要执行的方法以及贯穿整个mybatis的Configuration)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;}

method.execute(sqlsession,args)真正 调用sqlSession的地方。走进去看一下sqlSession触发不同的sql类型,但是对于select由于返回类型可能不同,再继续处理。

public Object execute(SqlSession sqlSession, Object[] args) {Object result;// 根据sqlCommand类型执行insert|updateswitch (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:// 2、根据select返回类型的不同执行不同的方法,,其最终都会执行selectList,只是前期准备不同。if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {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);result = sqlSession.selectOne(command.getName(), param);}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;}

2、Executor执行:其实最终还是要使用Executor执行的

  @Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {// 1、根据namespace获取MappedStatementMappedStatement ms = configuration.getMappedStatement(statement);// 2、wrapCollection()封装参数值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有两个实现类,类图如下;Executor是由Configuration创建的,并且使用了装饰器模式以及动态代理进行加强(interceptorChain.pluginAll(executor))

// 1、DefaultSqlSessionFactory 中创建DefaultSqlSession
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);// 2、DefaultSqlSessionFactory中创建DefaultSqlSessionfinal Executor executor = configuration.newExecutor(tx, execType);// 3、封装参数到SqlSession中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();}}
------------------------------------------------------------------------------------
// 1、Configuration中创建Executor
public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);}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 {executor = new SimpleExecutor(this, transaction);}// 2、装饰器模式对基础的Executor进行装饰if (cacheEnabled) {executor = new CachingExecutor(executor);}// 3、遍历所有的拦截器,然后调用各个拦截器的plugin方法,然后使用Plugin.wrap()为executor生成 Plugin代理对象(封装了interceptor、待拦截的method、target对象)。// 拦截器链InterceptorChain会对每一个拦截器依次封装在代理对象中(即:realObject --> proxy1RealObject --> prox2Prox1RealObject)只要符合拦截条件都会被依次代理增强。拦截执行 的时候会倒序依次执行(prox2Prox1RealObject-->proxy1RealObject -->realObject)executor = (Executor) interceptorChain.pluginAll(executor);return executor;}

ReuseExecutor:对Statement进行了缓存,重复利用。

以SimpleExecutor为例看其处理过程:

下图中doQuery()方法第四行代码configuration.newStatementHandler():在Configuration类中通过RoutingStatementHandler(静态代理)根据MappedStatement的StatementType(默认PREPARED)生成哪一种StatementHandler(类图如下),

 @Overridepublic <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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());// 1、sql执行---最终会使用PreparedStatementHandler (如下代码) return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 是否对获取的连接进行log加强Connection connection = getConnection(statementLog);// 获取Statement ----最终会调用instantiateStatement()如下代码stmt = handler.prepare(connection, transaction.getTimeout());// 参数设值 --- 如下代码  handler.parameterize(stmt);return stmt;}-----------------------------------------------------------------------------------------
// 1、PreparedStatementHandler 使用Statement执行execute() (和使用jdbc一模一样的)@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();// 2、结果集处理见(ResultSetHandler处理小节)return resultSetHandler.<E> handleResultSets(ps);}// 3、PreparedStatementHandler 使用connection获取Statement (和使用jdbc一模一样的)@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();if (keyColumnNames == null) {return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);} else {return connection.prepareStatement(sql, keyColumnNames);}} else if (mappedStatement.getResultSetType() != null) {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);} else {return connection.prepareStatement(sql);}}// 4、参数设值(获取ParameterMapping使用MetaObject(反射模块详细介绍)取值,然后占位符位置设值)@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}

StatementHandler继承关系:

3、ResultSetHandler解析查询结果:结果解析(ResultSetHandler)唯一默认实现类DefaultResultSetHandler,也会被加强;

// 1、  Configuration中ResultSetHandler生成 (DefaultResultSetHandler默认唯一实现)
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}----------------------------------------------------------------------------------------
// 2、DefaultResultSetHandler处理查询结果接上一小节@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());final List<Object> multipleResults = new ArrayList<Object>();int resultSetCount = 0;ResultSetWrapper rsw = getFirstResultSet(stmt);List<ResultMap> resultMaps = mappedStatement.getResultMaps();int resultMapCount = resultMaps.size();validateResultMapsCount(rsw, resultMapCount);// 返回结果集处理封装对象。-----不详细介绍while (rsw != null && resultMapCount > resultSetCount) {// 3、获得resultMap,实体类和表中数据字段的对应关系ResultMap resultMap = resultMaps.get(resultSetCount);// 4、将值设置成对应的resultmap对象handleResultSet(rsw, resultMap, multipleResults, null);rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}........return collapseSingleResultList(multipleResults);}

总结:ResultSetHandler、StatementHandler职责分工明确、层次分明,jdbc操作数据库的整套代码被完全封装起来,其本质和逻辑还是一样的。

三、Mybatis涉及的模块以及设计模式使用

  1. 反射模块:查的结果ResultSet通过反射构建bean。(实例化目标对象和对象属性赋值需要反射生成)---工厂模式

反射的核心类:

      1)Reflector:存放class定义的属性、方法信息

      2)DefaultReflectorFactory:依据class类新创建Reflector并缓存

      3)MetaClass:引用ReflectorFactory、Reflector,可以获取类的元信息

      4)ObjectWrapper:对bean、MetaClass的封装,可以实现对bean属性的设值以及获取

      5)DefaultObjectFactory:实例化对象

      6)MetaObject:主要封装了beanObjectWrapper、DefaultObjectFactory、DefaultReflectorFactory,因此可以通过MetaObject设置和获取bean的属性。(主要是通过ObjectWrapper操作)

  • 用于实例化目标对象的类

------ObjectFactory:MyBatis每次创建结果对象的新实例时,使用ObjectFactory构建POJO,默认实现DefaultObjectFactory(反射实例化对象)

反射创建对象(注意区别):

  • 用于对象属性赋值的类

------ReflectorFactory:创建Reflector的工厂类,Reflector是MyBatis反射模块的基础,每个Reflector对象都对应一个类,在其中缓存了反射操作所需要的类元信息。默认实现类DefaultReflectorFactory(读取类元信息封装在Reflector中,每一个类都有一个Reflector)。

Reflector(核心元信息缓存类):

------MetaObject:

MetaObject封装了对象元信息,包装了MyBatis中的五个核心的反射类,也是提供给外部使用的反射工具类,可以利用它读取或者修改对象的属性信息(其实也是通过ObjectWrapper调用相关的方法,例如BeanWrapper)。

// MetaObject 包装五个核心类 private final Object originalObject;private final ObjectWrapper objectWrapper;private final ObjectFactory objectFactory;private final ObjectWrapperFactory objectWrapperFactory;private final ReflectorFactory reflectorFactory;

BeanWrapper作用如下:(是对bean的封装可以给bean属性设置值,也可以获取某个属性的值)

public class BeanWrapper extends BaseWrapper {//1、BeanWrapper 封装了bean对象以及对应的类元信息,通过BeanWrapper get()和set()获取属性值或者设置属性值private final Object object;private final MetaClass metaClass;public BeanWrapper(MetaObject metaObject, Object object) {super(metaObject);this.object = object;this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());}
}

MetaClass(封装类元信息,并且也是通过Reflector来获取类的相关属性信息):

public class MetaClass {// 1、封装 类的属性和方法信息,并且可以通过此类获取属性等信息(如方法返回类型,参数类型)private final ReflectorFactory reflectorFactory;private final Reflector reflector;private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {this.reflectorFactory = reflectorFactory;this.reflector = reflectorFactory.findForClass(type);}public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {return new MetaClass(type, reflectorFactory);}
}

总结:反射模块核心类使用如下

 public void mybatisReflect(){DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();// 1、DefaultObjectFactory创建beanUser user = defaultObjectFactory.create(User.class);DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();// 2、创建MetaObject对象并在BeanWrapper中使用defaultReflectorFactory创建Reflector(Reflector加载了类元信息)MetaObject metaObject = MetaObject.forObject(user, defaultObjectFactory, objectWrapperFactory, defaultReflectorFactory);// 3、BeanWrapperObjectWrapper objectWrapper = metaObject.getObjectWrapper();// 4、使用ObjectWrapper读取对象信息,并对对象属性进行赋值操作(最终调用Reflector获取method,然后通过反射设值)objectWrapper.set(new PropertyTokenizer("userName"),"xiaodai");String userName = user.getUserName();System.out.println(userName);}

2. 插件模块:

MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

-------执行器的拦截-----------------------------------

  • ParameterHandler (getParameterObject, setParameters)

-------参数处理的拦截--------------------------------

  • ResultSetHandler (handleResultSets, handleOutputParameters)

--------结果集处理的拦截----------------------------

  • StatementHandler (prepare, parameterize, batch, update, query)

--------sql语法构建的拦截--------------------------

// 通过pluginAll实现对四大接口拦截设置
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler 其实是个静态代理,根据不同要求创建不同的对象StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);}

这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,InterceptorChain的pluginAll就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象。

在创建SqlSession的时候最先会执行的Configuration的newExecutor()并进行拦截器配置和加强。例:加入PageInterceptor拦截器,newExecutor()时会调用interceptorChain的pluginAll方法,然后在执行PageInterceptor.plugin(),最后会使用Plugin.wrap()为executor生成Plugin代理对象。最后在执行executor.query()时会被Plugin加强,根据PageInterceptor注解@Signature判断是否是要拦截的方法,PageInterceptor拦截主要是分页处理。

Plugin这个类的作用就是根据 @Interceptors注解,得到这个注解的属性 @Signature数组,然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。最终根据调用的target对象实现的接口(会和@Signature的type进行比较决定是否返回一个代理对象替代原先的target对象

注:拦截的入口其实就是在代理类Plugin的invoke是真实对象之前根据判断@Signature解析的Method和classType是否和代理的对象的类型和方法一致来决定是否执行拦截器。

拦截器链InterceptorChain会对每一个拦截器依次封装在代理对象中(即:realObject --> proxy1RealObject --> prox2Prox1RealObject)只要符合拦截条件都会被依次代理增强。拦截执行的时候会倒序依次执行(prox2Prox1RealObject-->proxy1RealObject -->realObject)。

3. Logging模块可以引入第三方例如Slfj----------适配器模式:

mybatis只有属于自己一套的log接口,并没有所有本身的日志,那么如何和市面上面 流行的日志框架做整合呢?这个时候就需要用到适配器模式。

如上图实现mybatis的日志实现类都需要实现这个接口。这使用到LogFactory

==org.apache.ibatis.logging.LogFactory== 日志工厂

如上图 tryImplementation()先判断logConstructor是否为空,只有logConstructor为空的时候tryImplementation里面的方法才会执行。

例如:useSlf4jLogging()  ---> 会获取Slf4jImpl的构造器,当使用LogFactory.getLogger()就会实例化,此时logConstructor已经有值,那么其它的try都不会在创建构造器。

4. 缓存模块--------Cache接口(装饰器模式)

实现类-->PerpetualCache:Cache的基本实现类

BlockingCache:它是个阻塞版本的缓存装饰器,保证只有一个线程到数据库中查找 key 对应的数据,使用                                                                    ConcurrentHashMap<Object, ReentrantLock> 实现了阻塞功能。

FifoCache:FifoCache 底层维护了一个队列,当需要清除时会清除队列头部的缓存

LruCache:LruCache底层维护了一个 LinkedHashMap 它是一种有序的 HashMap。当我们获取一个对象的时候会把                                      这个对象移动到 LinkedHashMap 的尾部表示最近访问的,需要清除的时候清除头部即可。

SoftCache&WeakCache:是 JVM 中定义的引用类型,是为了更方便的进行JVM垃圾回收的,针对那些只用一次的对象                                   进 行更高效率的回收。

MyBatis中涉及到动态SQL需要通过CacheKey来封装缓存的key值。

构成CacheKey对象的要素(如下图代码):

  • mappedStatement的id
  • 指定查询结果集的范围(分页信息)
  • 查询所使用的SQL语句
  • 用户传递给SQL语句的实际参数值

BaseExecutor的query:

cacheKey比较是否相等判断逻辑:

总结:MyBatis 的缓存接口 Cache 的基础实现 PerpetualCache 维护了一份 HashMap 作为缓存的实现。同时它还有很多装饰类,比如阻塞式缓存BlockingCache 内部维护了带对象同步器的 ConcurrentHashMap、带清除策略的 FifoCache/LruCache,FifoCache 内部维护了一个队列Dqueue,LruCache内部维护了一份 LinkedHashMap 。这些都使得缓存模块的功能很多,业务方可以根据自己的需求进行灵活组装。

一级缓存和二级缓存:

MyBatis 中的缓存就是说 MyBatis 在执行一次SQL查询或者SQL更新之后,这条SQL语句并不会消失,而是被MyBatis 缓存起来,当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,而不是再次执行SQL命令。

一级缓存:

mybatis的一级缓存是基于sqlsession的缓存,默认开启,不同sqlsession之间的缓存数据区域是不会相互影响的,如果会话失效,则缓存失效。

清除缓存:

当commit或者rollback的时候会清除缓存,并且当执行insert、update、delete的时候也会清除缓存。

mybatis一级缓存失效的四种情况

  1. sqlsession变了 缓存失效
  2. sqlsession不变,查询条件不同,一级缓存失效
  3. sqlsession不变,中间发生了增删改操作,一级缓存失败
  4. sqlsession不变,手动清除缓存,一级缓存失败

二级缓存:

二级缓存是手动开启的,作用域为mapper级别(也可以说MapperStatement级缓存,也就是一个namespace就会有一个缓存),不同的sqlsession两次执行相同的namespace下的sql,且向sqlq中传递的参数也相同,即最终执行相同的sql,则第一次中会从数据库查并存缓存,第二次会从内存查;因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。

如果开启了二级缓存的话,你的Executor将会被装饰成CachingExecutor,缓存是通过CachingExecutor来操作的,查询出来的结果会存在statement中的cache中,若有更新,删除类的操作默认就会清空该MapperStatement的cache(也可以通过修改xml中的属性,让它不执行),不会影响其他的MapperStatement。

5、建造者模式:在解析mapper.xml时会通过MapperBuilderAssistant助理进行每一阶段的属性值的builder(例如addResultMap、addMappedStatement)。

Mybatis源码日知录相关推荐

  1. 博主新书:《大数据日知录:架构与算法》目录

    <大数据日知录:架构与算法>目录 4目录编辑 第0 章 当谈论大数据时我们在谈什么................ 1 0.1 大数据是什么........................ ...

  2. 大数据日知录:架构与算法

    大数据丛书 大数据日知录:架构与算法(大数据领域专家力作,专注大数据架构和算法,全面梳理大数据相关技术) 张俊林 著   ISBN 978-7-121-24153-6 2014年9月出版 定价:69. ...

  3. 大数据丛书 大数据日知录:架构与算法

    大数据丛书 大数据日知录:架构与算法(大数据领域专家力作,专注大数据架构和算法,全面梳理大数据相关技术) 张俊林 著   ISBN 978-7-121-24153-6 2014年9月出版 定价:69. ...

  4. 《BIG DATA大数据日知录 架构和算法》读书笔记

    2019独角兽企业重金招聘Python工程师标准>>> <BIG DATA大数据日知录 架构和算法>读书笔记 博客分类: 架构 分布式计算 1.数据分片和路由 Hash ...

  5. java毕业设计OA办公系统mybatis+源码+调试部署+系统+数据库+lw

    java毕业设计OA办公系统mybatis+源码+调试部署+系统+数据库+lw java毕业设计OA办公系统mybatis+源码+调试部署+系统+数据库+lw 本源码技术栈: 项目架构:B/S架构 开 ...

  6. 大数据日知录要点整理

    大数据日知录要点整理 第0 章 当谈论大数据时我们在谈什么 1  NOSQL选型:kv-cassandra.dynamo,列式存储-HBase,图存储-Neo4j 社交网络数据存储适合用图数据库,而实 ...

  7. MyBatis 源码分析 - 内置数据源

    1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...

  8. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  9. mybatis源码_Mybatis源码之SqlSession

    SqlSession简介 Mybatis是一个强大的ORM框架,它通过接口式编程为开发者屏蔽了传统JDBC的诸多不便,以简单的方式提供强大的扩展能力.其中的接口式编程就是指日常使用的Mapper接口, ...

最新文章

  1. php 英文小写变大写,PHP英文字母大小写转换函数
  2. IntelliJ IDEA启动Tomcat后,却无法访问Tomcat主页
  3. Android --- 调用MediaStore.Images.Media.insertImage保存图片时生成两张图片的问题
  4. C++:05---命名空间
  5. 配置nfs环境的一些命令
  6. java歌词添加,分享 :java实现 歌词文件的智能命名解决方法
  7. robot framework 使用四:分层设计和截图以及注意事项
  8. 应用安全-浏览器安全-攻防
  9. Java中stringbutter_java 中String和StringBuffer与StringBuilder的区别及使用方法
  10. 魔兽世界服务器重置时间,大芒果魔兽世界单机版 如何更改所有副本的重置时间?...
  11. scrapy-redis爬取豆瓣电影短评,使用词云wordcloud展示
  12. Maven学习(七)Maven工程单一架构案例
  13. PPT计算机原理结构初步,测量实践初步(赖丽娟).ppt
  14. 有哪些国外便宜虚拟主机适合个人建站呢
  15. [人工智能-深度学习-81]:视觉 - 视频换脸软件大全
  16. JAVA根据指定大小压缩图片
  17. All In One - 第7章 安全运营
  18. 985博士走红,粉丝超800万遭网暴!如今状告“施暴者”
  19. 计算机激光鼠标,光电鼠标和激光鼠标的区别 选择一款你喜欢的【图文】
  20. pixelXL 下载编译源代码刷机烧录记录

热门文章

  1. bootstrap-loader使用过程中遇到的几个坑爹问题
  2. Stata输出统计结果到Excel或word
  3. Python--parse_args()详解
  4. 罗马数字转换为阿拉伯数字
  5. 《九州仙剑传》11.26正式上线链游玩家|唯美国风、闯荡仙界
  6. 【C】取十六进制中的某一位
  7. 201009128_Dawning_配置Jboss常见问题
  8. php存在的安全漏洞,PHP 5.2.10及之前版本存在多个安全漏洞
  9. nodejs框架express之创建服务器(初学)
  10. 玩转【斗鱼直播APP】系列之项目部署