扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
怎么进行MyBatis源码分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
目前成都创新互联已为1000+的企业提供了网站建设、域名、网站空间、成都网站托管、企业网站设计、白河网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
What is MyBatis?
官方描述: MyBatis是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。 MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
首先来一个简单的基础的mybatis工程
mysql mysql-connector-java 5.1.47 org.mybatis mybatis 3.5.5
public interface UserMapper { User findById(Integer id); }
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: publicT 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 publicT 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(
关于怎么进行MyBatis源码分析问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流