前文传送门:
mybatis源码学习:从SqlSessionFactory到代理对象的生成
mybatis源码学习:一级缓存和二级缓存分析

下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一起来看看它的内部是如何实现的。

  1. User user1 = userDao1.findById(41);

一、动态代理:执行代理对象的方法时拦截,进行方法增强。

  1. /**
  2. * 作用:执行被代理对象的任何接口方法都会经过该方法
  3. * @param proxy : 代理对象的引用
  4. * @param method : 当前执行的方法
  5. * @param args : 当前执行方法所需的参数
  6. * @return : 和被代理对象有相同的返回值
  7. * @throws Throwable
  8. */
  9. @Override
  10. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  11. try {
  12. //判断它是否为类
  13. if (Object.class.equals(method.getDeclaringClass())) {
  14. //如果是的话,直接调用该方法并返回
  15. return method.invoke(this, args);
  16. } else if (isDefaultMethod(method)) {
  17. //判断该方法是不是default方法
  18. return invokeDefaultMethod(proxy, method, args);
  19. }
  20. } catch (Throwable t) {
  21. throw ExceptionUtil.unwrapThrowable(t);
  22. }
  23. //对msqlcommand和method进行封装,并以method:mapperMethod的形式加入methodCache
  24. final MapperMethod mapperMethod = cachedMapperMethod(method);
  25. //返回mapperMethod的execute的返回结果
  26. return mapperMethod.execute(sqlSession, args);
  27. }

可以看看这个MapperMethod具体是个啥玩意儿:

  1. //缓存思想的体现
  2. private MapperMethod cachedMapperMethod(Method method) {
  3. //从methodCache这个Map中取method对应的mapperMethod
  4. MapperMethod mapperMethod = methodCache.get(method);
  5. //如果里面没有,就创建一个
  6. if (mapperMethod == null) {
  7. mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  8. //以method:mapperMethod的形式加入methodCache
  9. methodCache.put(method, mapperMethod);
  10. }
  11. //如果有就直接返回
  12. return mapperMethod;
  13. }

MapperMethod的构造器,sqlCommand和methodSignature是他的两个静态内部类:

  1. public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  2. this.command = new SqlCommand(config, mapperInterface, method);
  3. this.method = new MethodSignature(config, mapperInterface, method);
  4. }

在这里插入图片描述

二、接着执行MapperMethod对象的execute方法,其实源码还是通俗易懂的,无非就是按照不同的sql语句的类别进行不同的数据结果的封装,值得注意的是,insert,update和delete其实底层都是调用了update方法,但为了语义清晰,所以区分类别。

之前command封装了sql语句的类别,我们这是SELECT对吧,

  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object result;
  3. switch (command.getType()) {
  4. case INSERT: {
  5. Object param = method.convertArgsToSqlCommandParam(args);
  6. result = rowCountResult(sqlSession.insert(command.getName(), param));
  7. break;
  8. }
  9. case UPDATE: {
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = rowCountResult(sqlSession.update(command.getName(), param));
  12. break;
  13. }
  14. case DELETE: {
  15. Object param = method.convertArgsToSqlCommandParam(args);
  16. result = rowCountResult(sqlSession.delete(command.getName(), param));
  17. break;
  18. }
  19. case SELECT:
  20. if (method.returnsVoid() && method.hasResultHandler()) {
  21. executeWithResultHandler(sqlSession, args);
  22. result = null;
  23. } else if (method.returnsMany()) {
  24. result = executeForMany(sqlSession, args);
  25. } else if (method.returnsMap()) {
  26. result = executeForMap(sqlSession, args);
  27. } else if (method.returnsCursor()) {
  28. result = executeForCursor(sqlSession, args);
  29. } else {
  30. //将Args转换为SqlCommand参数,简单理解就是获取了参数41,这里就不深入了
  31. Object param = method.convertArgsToSqlCommandParam(args);
  32. //调用selectOne方法,这部分可以发现,无论是使用代理dao还是定义sqlsession实现类,本质上都调用了这些方法,因为这里的command。getName就是具体定义的sql的namespace.id
  33. result = sqlSession.selectOne(command.getName(), param);
  34. }
  35. break;
  36. case FLUSH:
  37. result = sqlSession.flushStatements();
  38. break;
  39. default:
  40. throw new BindingException("Unknown execution method for: " + command.getName());
  41. }
  42. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  43. throw new BindingException("Mapper method \'" + command.getName()
  44. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  45. }
  46. return result;
  47. }

三、当然本例以findById为例,这里调用的是SelectOne方法,接收com.smday.dao.IUserDao.findById41

  1. @Override
  2. public <T> T selectOne(String statement, Object parameter) {
  3. //根据参数select List
  4. List<T> list = this.<T>selectList(statement, parameter);
  5. if (list.size() == 1) {
  6. //获取列表的一个元素
  7. return list.get(0);
  8. } else if (list.size() > 1) {
  9. //个数超过一抛出异常
  10. throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  11. } else {
  12. //个数为0返回null
  13. return null;
  14. }
  15. }

四、调用selectList的方法,实现如下:

  1. @Override
  2. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  3. try {
  4. //获取MappedStatement
  5. MappedStatement ms = configuration.getMappedStatement(statement);
  6. //wrapCollection方法是对集合类型或者数组类型的参数做特殊处理
  7. //通过执行器调用query方法
  8. return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  9. } catch (Exception e) {
  10. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  11. } finally {
  12. ErrorContext.instance().reset();
  13. }
  14. }

五、获取MappedStatement对象,该对象代表一个增删改查标签的详细信息。

在这里插入图片描述

六、默认执行CachingExecutor.query(ms,xxx,x)方法,获取boundsql,该对象包含sql的具体信息,创建缓存key。

在这里插入图片描述

七、先去二级缓存中查询数据,如果二级缓存中没有,则去一级缓存(localCache)中查询,接着数据库(queryFromDatabase)一条龙服务,这部分就不赘述了。最终调用的是Executor的doQuery方法,list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

八、创建StatementHandler对象,默认为PreparedStatementHandler,用以操作statement执行操作。

ps:StatementHandler定义了一些主要的方法:预编译相关prepare、查询query、设置参数parameterize等等。

  1. @Override
  2. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3. Statement stmt = null;
  4. try {
  5. //从mappedStatement中获取配置信息对象
  6. Configuration configuration = ms.getConfiguration();
  7. //创建StatementHandler对象,处理sql语句的对象,默认为PreparedStatementHandler
  8. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  9. //创建prepareStatement对象
  10. stmt = prepareStatement(handler, ms.getStatementLog());
  11. return handler.<E>query(stmt, resultHandler);
  12. } finally {
  13. closeStatement(stmt);
  14. }
  15. }
  1. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  2. //RoutingStatementHandler并不是真实的服务对象,将会通过适配器模式找到对应的Statementhandler
  3. StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  4. //拦截链对方法进行拦截
  5. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  6. return statementHandler;
  7. }

Executor和Statement分为三种:Simple、Prepared、Callable。

SqlSession四大对象在创建的时候都会被拦截器进行拦截,我们之后再做学习。

九、在创建StatementHandler的时候,我们会发现,它还初始化创建了另外两个重要的对象:

  1. //用于参数处理
  2. this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  3. //用于封装结果集
  4. this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

十、在创建prepareStatement对象的时候,其实还通过parameterHandler的prepare()对statement进行了参数的预编译:

  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  2. Statement stmt;
  3. Connection connection = getConnection(statementLog);
  4. //预编译(基础配置)
  5. stmt = handler.prepare(connection, transaction.getTimeout());
  6. //设置参数
  7. handler.parameterize(stmt);
  8. return stmt;
  9. }
  10. //statementhandler的方法
  11. public Statement prepare(Connection connection, Integer transactionTimeout)
  12. Statement statement = null;
  13. //预编译
  14. statement = instantiateStatement(connection);
  15. //设置超时
  16. setStatementTimeout(statement, transactionTimeout);
  17. //设置获取最大行数
  18. setFetchSize(statement);
  19. return statement;

还通过handler.parameterize(stmt);对参数进行设置,最终通过parameterHandler的setParameters的方法实现了该操作,其中还创建TypeHandler对象完成数据库类型和javaBean类型的映射。

  1. @Override
  2. public void setParameters(PreparedStatement ps) {
  3. //。。。省略对value值的操作
  4. //创建TypeHandler对象完成数据库类型和javaBean类型的映射
  5. TypeHandler typeHandler = parameterMapping.getTypeHandler();
  6. JdbcType jdbcType = parameterMapping.getJdbcType();
  7. if (value == null && jdbcType == null) {
  8. jdbcType = configuration.getJdbcTypeForNull();
  9. }
  10. //设置参数
  11. typeHandler.setParameter(ps, i + 1, value, jdbcType);
  12. }

十一、获取了ps参数之后,就可以执行statementHandler的query方法进行查询了

  1. //PreparedStatementHandler.java
  2. @Override
  3. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  4. //转为PreparedStatement对象
  5. PreparedStatement ps = (PreparedStatement) statement;
  6. ps.execute();
  7. //利用结果集处理对象对结果集进行处理:封装并返回。
  8. return resultSetHandler.<E> handleResultSets(ps);
  9. }

总结:

反射技术运用广泛,基于反射的动态代理模式使我们操作的不再是真实的服务,而是代理对象,正是基于动态代理,mybatis可以在真实对象的基础上,提供额外的服务,我们也可以利用这一特性去自定义一些类,满足我们的需求。

  • 通过动态代理调用代理对象的方法。

  • 通过sqlSession执行sql操作的方法:insert|delete|select|update

  • 利用Executor对象对其他三大对象进行调度。

  • PreparedStatementHandler对sql进行预编译,并进行了基础配置,接着设置参数,并执行sql语句。

  • ParameterHandler负责对参数进行设置,其中TypeHandler负责数据库类型和javabean类型的映射。

  • 最后查询结果由ResultHandler封装。

版权声明:本文为summerday152原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/summerday152/p/12773141.html