Mybatis--第三部分:mybatis架构原理和源码剖析

mybatis架构原理

1.架构设计

  

我们把Mybatis的功能架构分为三层:
(1) API接⼝层:
      提供给外部使⽤的接⼝ API,开发⼈员通过这些本地API来操纵数据库。
      接⼝层⼀接收到 调⽤请求就会调⽤数据处理层来完成具体的数据处理。
      MyBatis和数据库的交互有两种⽅式:
                      a. 使⽤传统的MyBati s提供的API ;
                      b. 使⽤Mapper代理的⽅式
(2) 数据处理层:
    负责具体的SQL查找、SQL解析、SQL执⾏和执⾏结果映射处理等。
    它主要的⽬的是根据调⽤的请求完成⼀次数据库操作。
 
(3) 基础⽀撑层:
    负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是
共 ⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑

2.主要组件和关系梳理

  

 

SqlSeesion:

    作为Mybatis工作的顶层的API接口,作为会话访问,完成增删改查功能

Executor:

    Mybatis的核心执行器,负责SQL动态语句生成,查询缓存和维护

StatementHandler:

    parameterHandler:

            负责JDBC和Statement的交互,包括Statement参数设置  

            TypeHandler:负责JDBC与javaType之间数据转换,

                   1.对Statement对象设置  特定的参数

                    2.对Statement返回的结果集,取出特定的列

    ResultSetHandler:

            将JDBC返回的结果集 resultSet,转换成List

mybatis源码剖析(传统方式)

1.Mybatis初始化过程

 //读取配置文件,读成字节输入流,还没开始解析
Inputstream inputstream = Resources.getResourceAsStream("mybatisconfig.xml");
 //这⼀⾏代码正是初始化⼯作的开始。
  解析配置文件封装 Configuration 创建
DefaultSqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 1.我们最初调⽤的build
 public SqlSessionFactory build (InputStream inputStream){
 //调⽤了重载⽅法
 return build(inputStream, null, null);
 }
 
 // 2.调⽤的重载⽅法
 public SqlSessionFactory build (InputStream inputStream, String
environment,
 Properties properties){
 try {
 // XMLConfigBuilder是专⻔解析mybatis的配置⽂件的类
 XMLConfigBuilder parser = new XMLConfigBuilder(inputstream,
environment, properties);
 //这⾥⼜调⽤了⼀个重载⽅法。parser.parse()的返回值是Configuration对象
 return build(parser.parse());
 } catch (Exception e) {
 throw ExceptionFactory.wrapException("Error building
SqlSession.", e)
 }

 

2.执行sql流程

  //生产了SqlSessionFactory实例对象   设置了事务不自动提交  完成了Executor对象创建
SqlSession sqlSession = factory.openSession();
  //根据Statementid从Configuration集合中获取MappedStatement
  //将查询任务委派给Executor执行器,进行查询修改操作
List
<User> list = sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");

SqlSession

  SqlSession是⼀个接⼝,它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager (弃⽤,不做介绍)
  SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀
  个SqlSession,并且在使⽤完毕后需要close
  SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执⾏器
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor; }
Executor
  Executor也是⼀个接⼝,他有三个常⽤的实现类:
  BatchExecutor (重⽤语句并执⾏批量更新)
  ReuseExecutor (重⽤预处理语句 prepared statements)
  SimpleExecutor (普通的执⾏器,默认)
 
1).获得 sqlSession:
//6. 进⼊ o penSession ⽅法。
 public SqlSession openSession() {
 //getDefaultExecutorType()传递的是SimpleExecutor
 return
openSessionFromDataSource(configuration.getDefaultExecutorType(), null,
false);
 }
 //7. 进⼊penSessionFromDataSource。
 //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,
autoCommit是否开启事务
 //openSession的多个重载⽅法可以指定获得的SeqSession的Executor类型和事务的处理
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);
 //根据参数创建指定类型的Executor
 final Executor executor = configuration.newExecutor(tx, execType);
 //返回的是 DefaultSqlSessionreturn new DefaultSqlSession(configuration, executor, autoCommit);
 } catch(Exception e){
 closeTransaction(tx); // may have fetched a connection so lets call
close()
 }
2).执⾏ sqlsession 中的 api:
//8.进⼊selectList⽅法,多个重载⽅法。
 public <E > List < E > selectList(String statement) {
 return this.selectList(statement, null);
 public <E > List < E > selectList(String statement, Object parameter) {
 return this.selectList(statement, parameter, RowBounds.DEFAULT);
 public <E > List < E > selectList(String statement, Object
parameter, RowBounds rowBounds) {
 try {
 //根据传⼊的全限定名+⽅法名从映射的Map中取出MappedStatement对象
 MappedStatement ms =
configuration.getMappedStatement(statement);
 //调⽤Executor中的⽅法处理
 //RowBounds是⽤来逻辑分⻚
 // wrapCollection(parameter)是⽤来装饰集合或者数组参数
 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();
 }

3.executor剖析

 //此⽅法在SimpleExecutor的⽗类BaseExecutor中实现
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
 //根据传⼊的参数动态获得SQL语句,最后返回⽤BoundSql对象表示
 BoundSql boundSql = ms.getBoundSql(parameter);
 //为本次查询创建缓存的Key
 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
 //进⼊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 #601
 deferredLoads.clear();
 if (configuration.getLocalCacheScope() ==
LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache();
 }
 }
 return list;
 }
 //从数据库查询
 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);
 if (ms.getStatementType() == StatementType.CALLABLE) {
 localOutputParameterCache.putObject(key, parameter);
 }
 return list;
 }
 // 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();
 //传⼊参数创建StatementHanlder对象来执⾏查询
 StatementHandler handler =
configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
resultHandler, boundSql);
 //创建jdbc中的statement对象
 stmt = prepareStatement(handler, ms.getStatementLog());
 // StatementHandler 进⾏处理
 return handler.query(stmt, resultHandler);
 } finally {
 closeStatement(stmt);
 }
 }
 //创建Statement的⽅法
 private Statement prepareStatement(StatementHandler handler, Log
statementLog) throws SQLException {
 Statement stmt;
 //条代码中的getConnection⽅法经过重重调⽤最后会调⽤openConnection⽅法,从连接池
中获 得连接。
 Connection connection = getConnection(statementLog);
 stmt = handler.prepare(connection, transaction.getTimeout());
 handler.parameterize(stmt);
 return stmt;
 }
 //从连接池获得连接的⽅法
 protected void openConnection() throws SQLException {
 if (log.isDebugEnabled()) {
 log.debug("Opening JDBC Connection");
 }
 //从连接池获得连接
connection = dataSource.getConnection();
 if (level != null) {
 connection.setTransactionIsolation(level.getLevel());
 }
 }
上述的Executor.query()⽅法⼏经转折,
最后会创建⼀个StatementHandler对象,然后将必要的参数传递给StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。
从上⾯的代码中我们可以看出,Executor的功能和作⽤是:
(1、根据传递的参数,完成SQL语句的动态解析,⽣成BoundSql对象,供StatementHandler使⽤;
(2、为查询创建缓存,以提⾼性能
(3、创建JDBC的Statement连接对象,传递给*StatementHandler*对象,返回List查询结果。

4.StatementHandler剖析

StatementHandler对象主要完成两个⼯作:
      1.对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包含若⼲个?占位符,
      我们其后再对占位符进⾏设值。StatementHandler通过parameterize(statement)⽅法对 S tatement 进⾏设值;
 
      2.StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)⽅法来
      完成执⾏Statement,和将Statement对象返回的resultSet封装成List;
 
1).进⼊到 StatementHandler 的 parameterize(statement)⽅法的实现: 
public void parameterize(Statement statement) throws SQLException {
 //使⽤ParameterHandler对象来完成对Statement的设值
 parameterHandler.setParameters((PreparedStatement) statement);
}
 /**
     * ParameterHandler 类的 setParameters(PreparedStatement ps) 实现
     * 对某⼀个Statement进⾏设置参数
     */
    public void setParameters(PreparedStatement ps) throws SQLException {
        ErrorContext.instance().activity("setting
                parameters").object(mappedStatement.getParameterMap().getId());
                List < ParameterMapping > parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i <
                    parameterMappings.size(); i++) {
                ParameterMapping parameterMapping =
                        parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448
                        ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if
                    (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value =
                                parameterObject;
                    } else {
                        MetaObject metaObject =
                                configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 每⼀个 Mapping都有⼀个 TypeHandler,根据 TypeHandler 来对
                    preparedStatement 进 ⾏设置参数
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) jdbcType =
                            configuration.getJdbcTypeForNull();
                    //设置参数
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                }
            }
        }
    }
从上述的代码可以看到,
StatementHandler的parameterize(Statement)⽅法调⽤了ParameterHandler的setParameters(statement)⽅法,
ParameterHandler的setParameters(Statement )⽅法负责根据我们输⼊的参数,对statement对象的?占位符处进⾏赋值。
 
 2)进⼊到StatementHandler 的 List query(Statement statement, ResultHandler resultHandler)⽅法的
实现:
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
 // 1.调⽤preparedStatemnt。execute()⽅法,然后将resultSet交给ResultSetHandler处 理
 PreparedStatement ps = (PreparedStatement) statement;
 ps.execute();
 
 //2.使⽤ ResultHandler 来处理 ResultSet
 return resultSetHandler.<E> handleResultSets(ps);
}
从上述代码我们可以看出,
StatementHandler 的List query(Statement statement, ResultHandlerresultHandler)⽅法的实现,
是调⽤了
ResultSetHandler 的 handleResultSets(Statement)⽅法。
ResultSetHandler 的 handleResultSets(Statement)⽅法会将 Statement 语句执⾏后⽣成的 resultSet结 果集转换成List结果集
 public List<Object> handleResultSets(Statement stmt) throws SQLException {
 ErrorContext.instance().activity("handling
results").object(mappedStatement.getId());
//多ResultSet的结果集合,每个ResultSet对应⼀个Object对象。⽽实际上,每 个 Object 是
List<Object> 对象。
//在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,也 就是说,
multipleResults最多就⼀个元素。
 final List<Object> multipleResults = new ArrayList<>();
 int resultSetCount = 0;
 //获得⾸个ResultSet对象,并封装成ResultSetWrapper对象
 ResultSetWrapper rsw = getFirstResultSet(stmt);
 //获得ResultMap数组
 //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,也 就是
说,resultMaps就⼀个元素。
 List<ResultMap> resultMaps = mappedStatement.getResultMaps();
 int resultMapCount = resultMaps.size();
 validateResultMapsCount(rsw, resultMapCount); // 校验
 while (rsw != null && resultMapCount > resultSetCount) {
 //获得ResultMap对象
 ResultMap resultMap = resultMaps.get(resultSetCount);
 //处理ResultSet,将结果添加到multipleResults中
 handleResultSet(rsw, resultMap, multipleResults, null);
 //获得下⼀个ResultSet对象,并封装成ResultSetWrapper对象
 rsw = getNextResultSet(stmt);
 //清理
 cleanUpAfterHandlingResultSet();
 // resultSetCount ++
 resultSetCount++;
 }
 }
 //因为'mappedStatement.resultSets'只在存储过程中使⽤,本系列暂时不考虑,忽略即可
    String[] resultSets = mappedStatement.getResultSets();
 if(resultSets!=null)
    {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping =
                    nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId =
                        parentMapping.getNestedResultMapId();
                ResultMap resultMap =
                        configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);

            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }
    //如果是multipleResults单元素,则取⾸元素返回
 return

    collapseSingleResultList(multipleResults);
}

mybatis源码剖析(动态代理方式)

getmapper()剖析

写法如下

public static void main(String[] args) {
 //前三步都相同
 InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
 SqlSession sqlSession = factory.openSession();
 //这⾥不再调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤接⼝中的⽅法。

//使用JDK动态代理,对mapper接口产生代理对象
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//代理对象调用接口的任意方法,都是执行的动态代理中的invoke方法
 List<User> list = mapper.getUserByName("tom");
 }
思考⼀个问题,通常的Mapper接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢?
答案很简单动态代理
开始之前介绍⼀下MyBatis初始化时对接⼝的处理:MapperRegistry是Configuration中的⼀个属性,
它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。mappers中可以
配置接⼝的包路径,或者某个具体的接⼝类。
<mappers>
 <mapper class="com.lagou.mapper.UserMapper"/>
 <package name="com.lagou.mapper"/>
</mappers>
•当解析mappers标签时,它会判断解析到的是mapper配置⽂件时,会再将对应配置⽂件中的增删 改
查标签 封装成MappedStatement对象,存⼊mappedStatements中。(上⽂介绍了)当
判断解析到接⼝时,会
建此接⼝对应的MapperProxyFactory对象,存⼊HashMap中,key =接⼝的字节码对象,value =此接
⼝对应的MapperProxyFactory对象。
 
1)进⼊ sqlSession.getMapper(UserMapper.class )中
//DefaultSqlSession 中的 getMapper
 public <T> T getMapper(Class<T> type) {
 return configuration.<T>getMapper(type, this);
 }
 //configuration 中的给 g etMapper
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 return mapperRegistry.getMapper(type, sqlSession);
 }
 //MapperRegistry 中的 g etMapper
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 //从 MapperRegistry 中的 HashMap 中拿 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 {
 //通过动态代理⼯⼚⽣成示例。
 return mapperProxyFactory.newInstance(sqlSession);
 } catch (Exception e) {
 throw new BindingException("Error getting mapper instance. Cause:
" + e, e);
 }
 }
 //MapperProxyFactory 类中的 newInstance ⽅法
 public T newInstance(SqlSession sqlSession) {
 //创建了 JDK动态代理的Handler类
 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession,
mapperInterface, methodCache);
 //调⽤了重载⽅法
 return newInstance(mapperProxy);
 }
 //MapperProxy 类,实现了 InvocationHandler 接⼝
 public class MapperProxy<T> implements InvocationHandler, Serializable {
 //省略部分源码
 private final SqlSession sqlSession;
 private final Class<T> mapperInterface;
 private final Map<Method, MapperMethod> methodCache;
 //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的!
 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface,
Map<Method, MapperMethod> methodCache) {
 
 this.sqlSession = sqlSession;
 this.mapperInterface = mapperInterface;
 this.methodCache = methodCache;
 }
 //省略部分源码
 }

invoke()剖析

在动态代理返回了示例后,我们就可以直接调⽤mapper类中的⽅法了,但代理对象调⽤⽅法,执⾏是
 
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 对象
 final MapperMethod mapperMethod = cachedMapperMethod(method);
 //重点在这:MapperMethod最终调⽤了执⾏的⽅法
 return mapperMethod.execute(sqlSession, args);
 }

2)进⼊execute⽅法:

public Object execute(SqlSession sqlSession, Object[] args) {
 Object result;
 //判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch
(command.getType()) {
 case INSERT: {
 //转换参数
 Object param = method.convertArgsToSqlCommandParam(args);
 //执⾏INSERT操作
 // 转换 rowCount
 result = rowCountResult(sqlSession.insert(command.getName(),
param));
 break;
 }
 case UPDATE: {
 //转换参数
 Object param = method.convertArgsToSqlCommandParam(args);
 // 转换 rowCount
 result = rowCountResult(sqlSession.update(command.getName(),
param));
 break;
 }
 case DELETE: {
 //转换参数
 Object param = method.convertArgsToSqlCommandParam(args);
 // 转换 rowCount
 result = rowCountResult(sqlSession.delete(command.getName(),
 param));
 break;
 }
 case SELECT:
 //⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进
⾏处理
 if (method.returnsVoid() && method.hasResultHandler()) {
 executeWithResultHandler(sqlSession, args);
 result = null;
 //执⾏查询,返回列表
 } else if (method.returnsMany()) {
 result = executeForMany(sqlSession, args);
 //执⾏查询,返回Map
 } else if (method.returnsMap()) {
 result = executeForMap(sqlSession, args);
 //执⾏查询,返回Cursor
 } else if (method.returnsCursor()) {
 result = executeForCursor(sqlSession, args);
 //执⾏查询,返回单个对象
 } else {
 //转换参数
 Object param = method.convertArgsToSqlCommandParam(args);
 //查询单条
 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()); 
 //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
 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; }

二级缓存剖析

启⽤⼆级缓存

⼆级缓存构建在⼀级缓存之上,在收到查询请求时,MyBatis ⾸先会查询⼆级缓存,若⼆级缓存未命
中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。
 
⼆级缓存------》 ⼀级缓存------》数据库
 
与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的
MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。
 
1)开启全局⼆级缓存配置:
<settings>
 <setting name="cacheEnabled" value="true"/>
</settings>
 
2) 在需要使⽤⼆级缓存的Mapper配置⽂件中配置标签
 <cache></cache>
 
3)在具体CURD标签上配置 useCache=true
<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
 select * from user where id = #{id}
 </select>
问题:
  1.cache如何解析的,怎么创建cache对象?
  2.源码分析证实,⼆级缓存------》 ⼀级缓存------》数据库?
  3.为何只有SqlSession提交或关闭之后才生效?

标签 < cache/> 的解析

根据之前的mybatis源码剖析,xml的解析⼯作主要交给XMLConfigBuilder.parse()⽅法来实现
  1)XMLConfigBuilder.parse():
 // XMLConfigBuilder.parse()
 public Configuration parse() {
 if (parsed) {
 throw new BuilderException("Each XMLConfigBuilder can only be used
once.");
 }
 parsed = true;
 parseConfiguration(parser.evalNode("/configuration"));// 在这⾥
 return configuration;
 }

  2)parseConfiguration():

// 既然是在xml中添加的,那么我们就直接看关于mappers标签的解析
private void parseConfiguration(XNode root) {
 try {
 Properties settings =
settingsAsPropertiess(root.evalNode("settings"));
 propertiesElement(root.evalNode("properties"));
 loadCustomVfs(settings);
 typeAliasesElement(root.evalNode("typeAliases"));
 pluginElement(root.evalNode("plugins"));
 objectFactoryElement(root.evalNode("objectFactory"));
 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
 reflectionFactoryElement(root.evalNode("reflectionFactory"));
 settingsElement(settings);
 // read it after objectFactory and objectWrapperFactory issue #631
 environmentsElement(root.evalNode("environments"));
 databaseIdProviderElement(root.evalNode("databaseIdProvider"));
 typeHandlerElement(root.evalNode("typeHandlers"));
 // 就是这⾥
 mapperElement(root.evalNode("mappers"));
 } catch (Exception e) {
 throw new BuilderException("Error parsing SQL Mapper Configuration.
Cause: " + e, e);
 }
 }

3)mapperElement():

private void mapperElement(XNode parent) throws Exception {
 if (parent != null) {
 for (XNode child : parent.getChildren()) {
 if ("package".equals(child.getName())) {
 String mapperPackage = child.getStringAttribute("name");
 configuration.addMappers(mapperPackage);
 } else {
 String resource = child.getStringAttribute("resource");
 String url = child.getStringAttribute("url");
 String mapperClass = child.getStringAttribute("class");
 // 按照我们本例的配置,则直接⾛该if判断
 if (resource != null && url == null && mapperClass == null) {
 ErrorContext.instance().resource(resource);
 InputStream inputStream =
Resources.getResourceAsStream(resource);
 XMLMapperBuilder mapperParser = new
XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
 // ⽣成XMLMapperBuilder,并执⾏其parse⽅法
 mapperParser.parse();
 } else if (resource == null && url != null && mapperClass ==
null) {
 ErrorContext.instance().resource(url);
 InputStream inputStream = Resources.getUrlAsStream(url);
 XMLMapperBuilder mapperParser = new
XMLMapperBuilder(inputStream, configuration, url,
configuration.getSqlFragments());
 mapperParser.parse();
 } else if (resource == null && url == null && mapperClass !=
null) {
 Class<?> mapperInterface =
Resources.classForName(mapperClass);
 configuration.addMapper(mapperInterface);
 } else {
 throw new BuilderException("A mapper element may only
specify a url, resource or class, but not more than one.");
 } } } } }

4)parseConfiguration():

我们来看看解析Mapper.xml
// XMLMapperBuilder.parse()
public void parse() {
 if (!configuration.isResourceLoaded(resource)) {
 // 解析mapper属性
 configurationElement(parser.evalNode("/mapper"));
 configuration.addLoadedResource(resource);
 bindMapperForNamespace();
 }
 parsePendingResultMaps();
 parsePendingChacheRefs();
 parsePendingStatements();
}

5)configurationElement():

private void configurationElement(XNode context) {
 try {
 String namespace = context.getStringAttribute("namespace");
 if (namespace == null || namespace.equals("")) {
 throw new BuilderException("Mapper's namespace cannot be empty");
 }
 builderAssistant.setCurrentNamespace(namespace);
 cacheRefElement(context.evalNode("cache-ref"));
 // 最终在这⾥看到了关于cache属性的处理
 cacheElement(context.evalNode("cache"));
 parameterMapElement(context.evalNodes("/mapper/parameterMap"));
 resultMapElements(context.evalNodes("/mapper/resultMap"));
 sqlElement(context.evalNodes("/mapper/sql"));
 // 这⾥会将⽣成的Cache包装到对应的MappedStatement
 
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
 } catch (Exception e) {
 throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
 }
}

6)cacheElement():

// cacheElement()
private void cacheElement(XNode context) throws Exception {
 if (context != null) {
 //解析<cache/>标签的type属性,这⾥我们可以⾃定义cache的实现类,⽐如redisCache,
如果没有⾃定义,这⾥使⽤和⼀级缓存相同的PERPETUAL
 String type = context.getStringAttribute("type", "PERPETUAL");
 Class<? extends Cache> typeClass =
typeAliasRegistry.resolveAlias(type);
 String eviction = context.getStringAttribute("eviction", "LRU");
 Class<? extends Cache> evictionClass =
typeAliasRegistry.resolveAlias(eviction);
 Long flushInterval = context.getLongAttribute("flushInterval");
 Integer size = context.getIntAttribute("size");
 boolean readWrite = !context.getBooleanAttribute("readOnly", false);
 boolean blocking = context.getBooleanAttribute("blocking", false);
 Properties props = context.getChildrenAsProperties();
 // 构建Cache对象
 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval,
size, readWrite, blocking, props);
 }
}

7)MapperBuilderAssistant.useNewCache():

public Cache useNewCache(Class<? extends Cache> typeClass,
 Class<? extends Cache> evictionClass,
 Long flushInterval,
 Integer size,
 boolean readWrite,
 boolean blocking,
 Properties props) {
 // 1.⽣成Cache对象
 Cache cache = new CacheBuilder(currentNamespace)
 //这⾥如果我们定义了<cache/>中的type,就使⽤⾃定义的Cache,否则使⽤和⼀级缓存相
同的PerpetualCache
 .implementation(valueOrDefault(typeClass, PerpetualCache.class))
 .addDecorator(valueOrDefault(evictionClass, LruCache.class))
 .clearInterval(flushInterval)
 .size(size)
 .readWrite(readWrite)
 .blocking(blocking)
 .properties(props)
 .build();
 // 2.添加到Configuration中
 configuration.addCache(cache);
 // 3.并将cache赋值给MapperBuilderAssistant.currentCache
 currentCache = cache;
 return cache; }
我们看到⼀个Mapper.xml只会解析⼀次标签,也就是只创建⼀次Cache对象,放进configuration中,并将cache赋值给MapperBuilderAssistant.currentCache
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));将Cache包装到MappedStatement

8)buildStatementFromContext():

// buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list) {
 if (configuration.getDatabaseId() != null) {
 buildStatementFromContext(list, configuration.getDatabaseId());
 }
 buildStatementFromContext(list, null);
}
//buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list, String
requiredDatabaseId) {
 for (XNode context : list) {
 final XMLStatementBuilder statementParser = new
XMLStatementBuilder(configuration, builderAssistant, context,
requiredDatabaseId);
 try {
 // 每⼀条执⾏语句转换成⼀个MappedStatement
 statementParser.parseStatementNode();
 } catch (IncompleteElementException e) {
 configuration.addIncompleteStatement(statementParser);
 } }}

9)XMLStatementBuilder.parseStatementNode():

public void parseStatementNode() {
 String id = context.getStringAttribute("id");
 String databaseId = context.getStringAttribute("databaseId");
 ...
 Integer fetchSize = context.getIntAttribute("fetchSize");
 Integer timeout = context.getIntAttribute("timeout");
 String parameterMap = context.getStringAttribute("parameterMap");
 String parameterType = context.getStringAttribute("parameterType");
 Class<?> parameterTypeClass = resolveClass(parameterType);
 String resultMap = context.getStringAttribute("resultMap");
 String resultType = context.getStringAttribute("resultType");
 String lang = context.getStringAttribute("lang");
 LanguageDriver langDriver = getLanguageDriver(lang);
 ...
 // 创建MappedStatement对象
 builderAssistant.addMappedStatement(id, sqlSource, statementType,
sqlCommandType,
 fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass,
 resultSetTypeEnum, flushCache,
useCache, resultOrdered,
 keyGenerator, keyProperty, keyColumn,
databaseId, langDriver, resultSets);
}

10)builderAssistant.addMappedStatement():

public MappedStatement addMappedStatement(
 String id,
 ...) {
 if (unresolvedCacheRef) {
 throw new IncompleteElementException("Cache-ref not yet resolved");
 }
 id = applyCurrentNamespace(id, false);
 boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
 //创建MappedStatement对象
 MappedStatement.Builder statementBuilder = new
MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
 ...
CachingExecutor
 .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
 .useCache(valueOrDefault(useCache, isSelect))
 .cache(currentCache);// 在这⾥将之前⽣成的Cache封装到MappedStatement
 ParameterMap statementParameterMap =
getStatementParameterMap(parameterMap, parameterType, id);
 if (statementParameterMap != null) {
 statementBuilder.parameterMap(statementParameterMap);
 }
 MappedStatement statement = statementBuilder.build();
 configuration.addMappedStatement(statement);
 return statement; }
我们看到将Mapper中创建的Cache对象,加⼊到了每个MappedStatement对象中,也就是同⼀个  Mapper中所有的

源码分析(证实 二级缓存   》》 一级缓存  》》 数据库)

1)CachingExecutor:缓存执行器

// CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
 BoundSql boundSql = ms.getBoundSql(parameterObject);
 // 创建 CacheKey
 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
 return query(ms, parameterObject, rowBounds, resultHandler, key,
boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 throws SQLException {
 // 从 MappedStatement 中获取 Cache,注意这⾥的 Cache 是从MappedStatement中获取的
 // 也就是我们上⾯解析Mapper中<cache/>标签中创建的,它保存在Configration中
 // 我们在上⾯解析blog.xml时分析过每⼀个MappedStatement都有⼀个Cache对象,就是这⾥
 Cache cache = ms.getCache();
 // 如果配置⽂件中没有配置 <cache>,则 cache 为空
 if (cache != null) {
 //如果需要刷新缓存的话就刷新:flushCache="true"
 flushCacheIfRequired(ms);
 if (ms.isUseCache() && resultHandler == null) {
 ensureNoOutParams(ms, boundSql);
 // 访问⼆级缓存
 List<E> list = (List<E>) tcm.getObject(cache, key);
 // 缓存未命中
if (list == null) {
 // 如果没有值,则执⾏查询,这个查询实际也是先⾛⼀级缓存查询,⼀级缓存也没
有的话,则进⾏DB查询
 list = delegate.<E>query(ms, parameterObject, rowBounds,
resultHandler, key, boundSql);
 // 缓存查询结果
 tcm.putObject(cache, key, list);
 }
 return list;
 }
 }
 return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler,
key, boundSql);
}
如果设置了flushCache="true",则每次查询都会刷新缓存
<!-- 执⾏此语句清空缓存 -->
<select id="findbyId" resultType="com.lagou.pojo.user" useCache="true" flushCache="true" >
 select * from t_demo
</select>
如上,注意⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置
中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个
事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中
tcm 变量对应的类型。下⾯分析⼀下。
 
2)TransactionalCacheManager:
/** 事务缓存管理器 */
public class TransactionalCacheManager {
 // Cache 与 TransactionalCache 的映射关系表
 private final Map<Cache, TransactionalCache> transactionalCaches = new
HashMap<Cache, TransactionalCache>();
 public void clear(Cache cache) {
 // 获取 TransactionalCache 对象,并调⽤该对象的 clear ⽅法,下同
 getTransactionalCache(cache).clear();
 }
 public Object getObject(Cache cache, CacheKey key) {
 // 直接从TransactionalCache中获取缓存
 return getTransactionalCache(cache).getObject(key);
 }
 public void putObject(Cache cache, CacheKey key, Object value) {
 // 直接存⼊TransactionalCache的缓存中
getTransactionalCache(cache).putObject(key, value);
 }
 public void commit() {
 for (TransactionalCache txCache : transactionalCaches.values()) {
 txCache.commit();
 }
 }
 public void rollback() {
 for (TransactionalCache txCache : transactionalCaches.values()) {
 txCache.rollback();
 }
 }
 private TransactionalCache getTransactionalCache(Cache cache) {
 // 从映射表中获取 TransactionalCache
 TransactionalCache txCache = transactionalCaches.get(cache);
 if (txCache == null) {
 // TransactionalCache 也是⼀种装饰类,为 Cache 增加事务功能
 // 创建⼀个新的TransactionalCache,并将真正的Cache对象存进去
 txCache = new TransactionalCache(cache);
 transactionalCaches.put(cache, txCache);
 }
 return txCache;
 }
}
TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类
也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是⼀种缓
存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题正是由该类进⾏处理的。下⾯分
析⼀下该类的逻辑。
3)TransactionalCache:
public class TransactionalCache implements Cache {
 //真正的缓存对象,和上⾯的Map<Cache, TransactionalCache>中的Cache是同⼀个
 private final Cache delegate;
 private boolean clearOnCommit;
 // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
 private final Map<Object, Object> entriesToAddOnCommit;
 // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
 private final Set<Object> entriesMissedInCache;
 @Override
 public Object getObject(Object key) {
 // 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询
 Object object = delegate.getObject(key);
if (object == null) {
 // 缓存未命中,则将 key 存⼊到 entriesMissedInCache 中
 entriesMissedInCache.add(key);
 }
 if (clearOnCommit) {
 return null;
 } else {
 return object;
 }
 }
 @Override
 public void putObject(Object key, Object object) {
 // 将键值对存⼊到 entriesToAddOnCommit 这个Map中中,⽽⾮真实的缓存对象
delegate 中
 entriesToAddOnCommit.put(key, object);
 }
 @Override
 public Object removeObject(Object key) {
 return null;
 }
 @Override
 public void clear() {
 clearOnCommit = true;
 // 清空 entriesToAddOnCommit,但不清空 delegate 缓存
 entriesToAddOnCommit.clear();
 }
 public void commit() {
 // 根据 clearOnCommit 的值决定是否清空 delegate
 if (clearOnCommit) {
 delegate.clear();
 }
 
 // 刷新未缓存的结果到 delegate 缓存中
 flushPendingEntries();
 // 重置 entriesToAddOnCommit 和 entriesMissedInCache
 reset();
 }
 public void rollback() {
 unlockMissedEntries();
 reset();
 }
 private void reset() {
clearOnCommit = false;
 // 清空集合
 entriesToAddOnCommit.clear();
 entriesMissedInCache.clear();
 }
 private void flushPendingEntries() {
 for (Map.Entry<Object, Object> entry :
entriesToAddOnCommit.entrySet()) {
 // 将 entriesToAddOnCommit 中的内容转存到 delegate 中
 delegate.putObject(entry.getKey(), entry.getValue());
 }
 for (Object entry : entriesMissedInCache) {
 if (!entriesToAddOnCommit.containsKey(entry)) {
 // 存⼊空值
 delegate.putObject(entry, null);
 }
 }
 }
 private void unlockMissedEntries() {
 for (Object entry : entriesMissedInCache) {
 try {
 // 调⽤ removeObject 进⾏解锁
 delegate.removeObject(entry);
 } catch (Exception e) {
 log.warn("...");
 }
 }
 }
}
存储⼆级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,
但是每次查询的时候是直接从TransactionalCache.delegate中去查询的,
所以这个⼆级缓存查询数据库后,设置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate 会导致脏数据问题

为何只有SqlSession提交或关闭之后?

1)SqlSession:

@Override
public void commit(boolean force) {
 try {
 // 主要是这句
 executor.commit(isCommitOrRollbackRequired(force));
 dirty = false;
 } catch (Exception e) {
 throw ExceptionFactory.wrapException("Error committing transaction. 
Cause: " + e, e);
 } finally {
 ErrorContext.instance().reset();
 }
}

2)CachingExecutor.commit():

@Override
public void commit(boolean required) throws SQLException {
 delegate.commit(required);
 tcm.commit();// 在这⾥
}

3)TransactionalCacheManager.commit():

public void commit() {
 for (TransactionalCache txCache : transactionalCaches.values()) {
 txCache.commit();// 在这⾥
 }
}

4)TransactionalCache.commit():

public void commit() {
 if (clearOnCommit) {
 delegate.clear();
 }
 flushPendingEntries();//这⼀句
 reset();
}

5)TransactionalCache.flushPendingEntries():

private void flushPendingEntries() {
 for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
 // 在这⾥真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,⼆
级缓存才真正的⽣效
 delegate.putObject(entry.getKey(), entry.getValue());
 }
 for (Object entry : entriesMissedInCache) {
 if (!entriesToAddOnCommit.containsKey(entry)) {
 delegate.putObject(entry, null);
 }
 }
}

⼆级缓存的刷新

1)SqlSession.update():

public int update(String statement, Object parameter) {
 int var4;
 try {
 this.dirty = true;
 MappedStatement ms = this.configuration.getMappedStatement(statement);
 var4 = this.executor.update(ms, this.wrapCollection(parameter));
 } catch (Exception var8) {
 throw ExceptionFactory.wrapException("Error updating database. Cause:
" + var8, var8);
 } finally {
 ErrorContext.instance().reset();
 }
 return var4; }
public int update(MappedStatement ms, Object parameterObject) throws
SQLException {
 this.flushCacheIfRequired(ms);
 return this.delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
 //获取MappedStatement对应的Cache,进⾏清空
 Cache cache = ms.getCache();
 //SQL需设置flushCache="true" 才会执⾏清空
 if (cache != null && ms.isFlushCacheRequired()) {
 this.tcm.clear(cache);
 }
}
MyBatis⼆级缓存只适⽤于不常进⾏增、删、改的数据,
⽐如国家⾏政区省市区街道数据。⼀但数据变更,MyBatis会清空缓存。
因此⼆级缓存不适⽤于经常进⾏更新的数据。
 
总结:
在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的装饰器。
 
  1.⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
 
  2.⼆级缓存具有丰富的缓存策略。
 
  3.⼆级缓存可由多个装饰器,与基础缓存组合⽽成
 
  4.⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor和 ⼀个事务型预缓存TransactionalCache完成。

延迟加载源码剖析

什么是延迟加载?

问题:
在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我
们所说的延迟加载
/** 在⼀对多中,当我们有⼀个⽤户,它有个100个订单
 1.在查询⽤户的时候,要不要把关联的订单查出来?
 2.在查询订单的时候,要不要把关联的⽤户查出来?
 
 回答
 1.在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。
 2.在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。**/

实现

1)sqlMapConfig.xml

<!--引入映射配置文件-->
    <mappers>
        <mapper resource="LazyIUserMapper.xml"></mapper>
    </mappers>

2).User  Orders

public class User {
    private Integer id;
    private String username;
    private List<Order> orderList;
}

public class Order   {
    private Integer id;
    private String orderTime;
    private Double total;
}

3)LazyIUserMapper

public interface LazyIUserMapper
{
    /* 一对多 */
    List<User> selectById();

}

4) Lazy***.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mytest.mapper.LazyIUserMapper">

    <!-- 延迟加载一对多映射 -->
    <resultMap id="lazyUserMap" type="com.mytest.pojo.lazy.User">
        <result property="id" column="id"></result>
        <result property="username" column="username"></result>
        <collection property="orderList" ofType="com.mytest.pojo.lazy.Order"
        select="com.mytest.mapper.LazyIUserMapper.selectOrderByUid" column="id" >
            <result property="id" column="oid"></result>
            <result property="orderTime" column="orderTime"></result>
            <result property="total" column="total"></result>
        </collection>
    </resultMap>
    <select id="selectById" resultMap="lazyUserMap" useCache="true">
        select * from user where id =#{id}
     </select>
    <select id="selectOrderByUid" resultType="com.mytest.pojo.lazy.Order">
        select * from ORDERS where uid =#{uid}
    </select>
</mapper>

5)测试

 

局部延迟加载
    在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
 <!-- 延迟加载一对多映射 -->
    <resultMap id="lazyUserMap" type="com.mytest.pojo.lazy.User">
        <result property="id" column="id"></result>
        <result property="username" column="username"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
-->
        <collection property="orderList" ofType="com.mytest.pojo.lazy.Order"
                    select="com.mytest.mapper.LazyIUserMapper.selectOrderByUid"
                    column="id"
                fetchType="lazy"
        >
            <result property="id" column="oid"></result>
            <result property="orderTime" column="orderTime"></result>
            <result property="total" column="total"></result>
        </collection>
    </resultMap>
    <select id="selectById" resultMap="lazyUserMap" useCache="true">
        select * from user where id =#{id}
     </select>
    <select id="selectOrderByUid" resultType="com.mytest.pojo.lazy.Order">
        select * from ORDERS where uid =#{uid}
    </select>

全局延迟加载

 

延迟加载原理实现

它的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。
当调⽤代理对象的延迟加载属性的 getting ⽅法时,进⼊拦截器⽅法。
 
⽐如:调⽤ a.getB().getName() ⽅法,
 
>>>>  进⼊拦截器的invoke(...) ⽅法,
 
>>>>  发现 a.getB() 需要延迟加载时,
 
>>>>  那么就会单独发送事先保存好的查询关联 B对象的 SQL ,
 
>>>>  把 B 查询上来,
 
>>>>  然后调⽤ a.setB(b) ⽅法,
 
>>>>  于是 a 对象 b 属性就有值了,
 
接着完成 a.getB().getName() ⽅法的调⽤。
 
这就是延迟加载的基本原理
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。

延迟加载原理

 

 

 

 
posted @ 2021-02-04 13:14  trueAndFalse  阅读(53)  评论(0编辑  收藏  举报