资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

怎么进行MyBatis源码分析

怎么进行MyBatis源码分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

目前成都创新互联已为1000+的企业提供了网站建设、域名、网站空间成都网站托管、企业网站设计、白河网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。

MyBatis源码分析

  • What is MyBatis?

官方描述:
    MyBatis是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
    MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
    MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
  • 首先来一个简单的基础的mybatis工程

pom.xml

    
    
        mysql
        mysql-connector-java
        5.1.47
    

    
    
        org.mybatis
        mybatis
        3.5.5
    
mybatis-config.xml



    

    
        
            
            
                
                
                
                
            
        
    
    
        
    
UserMapper.xml





    
        select * from tb_user where id = #{id}
    
UserMapper.java
public interface UserMapper {

    User findById(Integer id);
}
MyBatisTest.java
public static void main(String[] args) throws Exception {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = sqlSessionFactory.openSession();

    UserMapper userMapper = session.getMapper(UserMapper.class);
    User user = userMapper.findById(20);
}
  • 开始分析

// 点击.getMapper 进去
UserMapper userMapper = session.getMapper(UserMapper.class);

-------------------------------------
MapperRegistry.class:
public  T getMapper(Class type, SqlSession sqlSession) {
    // 分析这里
    // MapperProxyFactory: 看名字先猜测是一个 Mapper的代理工厂类
    // 
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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.class:
public T newInstance(SqlSession sqlSession) {
    // MapperProxy: 点这个进去
    final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}


// 实现了 InvocationHandler 所以这个类是动态代理类
// 代理了谁?
// 前面的代码: UserMapper userMapper = session.getMapper(UserMapper.class);
// 所以是代理了 UserMapper
// 问题? UserMapper是一个抽象类,而且没有实现
// 看起来就是一个动态代理模式,但是jdk动态代理抽象接口必须要有实现类
// 所以这里什么情况?
public class MapperProxy implements InvocationHandler, Serializable { }
  • JDK动态代理 和 这里的mybatis"动态代理?" 对比

## Jdk正宗版本:
IProxyInterface     InvocationHandler
      |                     |
ProxyImpl     <-    ProxyDemoProxy

---------------------

## Mybatis版本:
UserMapper          InvocationHandler
     |                    |
    ???       <-    MapperProxy
  • 接着分析

// 上面的流程我们知道 这里是通过动态代理拿到的一个 UserMapper 的实例
// 打断点可以看到这里的 userMapper 的实例化内存地址是:
// org.apache.ibatis.binding.MapperProxy@3f197a46
UserMapper userMapper = session.getMapper(UserMapper.class);

// 接着怎么使用的呢?
// MapperProxy类实现了InvocationHandler,所以会执行里面的 invoke 方法
// 然后我们看看 invoke 里面做了什么 
// MapperProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 这里考虑一下  是走 if 还是走 else ?
        // 因为上面的分析得出 mybatis不是一个纯正的动态代理
        // 所以这里一般会走 else  
        // 既然不会走这里为什么还有这行代码? 不能因为不是它就不要它(有点绕口)
        if (Object.class.equals(method.getDeclaringClass())) {
            // 什么时候会进这里?
            // UserMapper userMapper = session.getMapper(UserMapper.class);
            // userMapper.toString(); 这种情况会进这里
            return method.invoke(this, args);
        } 
        else {
            // 接着我们看看 else 里面做了什么? 点 invoke 进去看 
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}


// 这个抽象类 有两个实现类 PlainMethodInvoker 和 DefaultMethodInvoker
// 进哪个? 
// 看上面的 return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
// 发现最后的走到了 return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
// 所以看 PlainMethodInvoker
interface MapperMethodInvoker {
    // 抽象类,快捷键进入实现
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}


private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
        super();
        this.mapperMethod = mapperMethod;
    }

    // 直接看 invoke 方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        // 在看 execute 
        return mapperMethod.execute(sqlSession, args);
    }
}


// MapperMethod.class
// 上面我们调用的是 findById 是一个 select 语句,所以直接看 case SELECT:
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case SELECT:
            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);

                // 断点走到了这里,再看 selectOne 
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
    }
    return result;
}


// DefaultSqlSession.class
public  T selectOne(String statement, Object parameter) {

    // 看 selectList 里面 
    List list = this.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;
    }
}


@Override
public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        // executor: 顶层接口,定义了执行器的一些基本操作
        // BaseExecutor:实现了Executor接口,实现了执行器的基本功能。
        // 具体使用哪一个Executor则是可以在 mybatis 的 config.xml 中进行配置的。默认为SimpleExecutor;
        // 
        //       
        // 
        // 看 BaseExecutor
        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();
    }
}


// BaseExecutor.class
@Override
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建一级缓存的键对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用下面的 query 方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

@Override
public  List 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 list;
    try {
        queryStack++;
        // 先在缓存中查询,缓存命中失败再去数据库查询
        list = resultHandler == null ? (List) 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 (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}


private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 到了这里,点下去看
        // 点进去是个抽象的,看实现,上面提到的默认使用的 SimpleExecutor
        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.class
// 注意这里面的逻辑 
public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();

        // RoutingStatementHandler.delegate = PreparedStatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        
        // com.mysql.jdbc.JDBC42PreparedStatement@757acd7b: select * from tb_user where id = 20
        stmt = prepareStatement(handler, ms.getStatementLog());

        // handler = PreparedStatementHandler
        // 点进去继续看 
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}


// PreparedStatementHandler.class
@Override
public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 这几行代码是不是很熟悉?
    // 就是jdbc的执行代码 
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    
    // resultSetHandler 结果集处理器 
    return resultSetHandler.handleResultSets(ps);
}
  • 总结

1.分析得出mybatis的最底层还是封装的jdbc代码。

2.看起来很像动态代理,
  其实是根据 UserMapper的抽象方法名去对应的在mybatis的xml配置文件中查找对应的id()而获得到sql语句
  (为什么UserMapper.xml中的  标签中的 namespace 属性的作用就在这里)
  到到了sql语句,最底层用jdbc代码执行sql。

3.下面这两行代码在build的时候就把xml里面的sql语句解析出来出来放在
    DefaultSqlSessionFactory(SqlSessionFactory)中了 (build最后返回了 new DefaultSqlSessionFactory(config))

  InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

关于怎么进行MyBatis源码分析问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。


新闻标题:怎么进行MyBatis源码分析
本文路径:http://www.cdkjz.cn/article/gscjpd.html
多年建站经验

多一份参考,总有益处

联系快上网,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

大客户专线   成都:13518219792   座机:028-86922220