`

mybatis 源码分析之 Mapper接口

 
阅读更多

 

 
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //从mapperRegister找,在解析Mapper配置文件时,根据nameSpace 注册mapper接口道mapperRegister
    return mapperRegistry.getMapper(type, sqlSession);
  }

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    if (!knownMappers.contains(type))
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
  //返回mapper接口类代理
      return MapperProxy.newMapperProxy(type, sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

 所有调接口的方法都会被代理类拦截处理我们看具体实现

 

 

 @SuppressWarnings("unchecked")
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class[]{mapperInterface};
    //这个是处理的handle
    MapperProxy proxy = new MapperProxy(sqlSession);
    return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
  }

}
//MapperProxy 回调的入口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (!OBJECT_METHODS.contains(method.getName())) {
      //找到方法所属的接口
      final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
      final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
      final Object result = mapperMethod.execute(args);
      if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
        throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
      }
      return result;
    }
    return null;
  }

public Object execute(Object[] args) {
    Object result = null;
    if (SqlCommandType.INSERT == type) {
      Object param = getParam(args);
      result = sqlSession.insert(commandName, param);
    } else if (SqlCommandType.UPDATE == type) {
      Object param = getParam(args);
      result = sqlSession.update(commandName, param);
    } else if (SqlCommandType.DELETE == type) {
      Object param = getParam(args);
      result = sqlSession.delete(commandName, param);
    } else if (SqlCommandType.SELECT == type) {
      if (returnsVoid && resultHandlerIndex != null) {
        executeWithResultHandler(args);
      } else if (returnsList) {
        result = executeForList(args);
      } else if (returnsMap) {
        result = executeForMap(args);
      } else {
        Object param = getParam(args);
        result = sqlSession.selectOne(commandName, param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + commandName);
    }
    return result;
  }

 

 

上面这些方法最终都会由sqlSession调用Executer来执行

来看个查询返回List例子

 

 private List executeForList(Object[] args) {
    List result;
    Object param = getParam(args);
    if (rowBoundsIndex != null) {
      RowBounds rowBounds = (RowBounds) args[rowBoundsIndex];
      result = sqlSession.selectList(commandName, param, rowBounds);
    } else {
      result = sqlSession.selectList(commandName, param);
    }
    return result;
  }
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    if (ms != null) {
      Cache cache = ms.getCache();
      if (cache != null) {
        flushCacheIfRequired(ms);
        cache.getReadWriteLock().readLock().lock();
        try {
          if (ms.isUseCache() && resultHandler == null) {
            CacheKey key = createCacheKey(ms, parameterObject, rowBounds);
            final List cachedList = (List) cache.getObject(key);
            if (cachedList != null) {
              return cachedList;
            } else {
              List list = delegate.query(ms, parameterObject, rowBounds, resultHandler);
              tcm.putObject(cache, key, list);
              return list;
            }
          } else {
            return delegate.query(ms, parameterObject, rowBounds, resultHandler);
          }
        } finally {
          cache.getReadWriteLock().readLock().unlock();
        }
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler);
  }

 从上面我们可以看到先回从缓存找找不到在从delegate类找,delete就是普通的Executor,

普通Executor3类,他们都继承于BaseExecutorBatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。下面以SimpleExecutor为例:

 

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler);
      stmt = prepareStatement(handler);
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

 

 

StatementHandler

 

Executor将指挥棒交给StatementHandler后,接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。

 

创建

 

    publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,  
            ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {  
       StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);  
       statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);  
       returnstatementHandler;  
    }  
 

 

可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandlerSimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的,本文作者也实现了一个分页拦截器,在后续的章节会分享给大家,敬请期待。

 

初始化

 

StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。代码如下:

 

    private StatementprepareStatement(StatementHandler handler) throwsSQLException {  
        Statementstmt;  
        Connectionconnection = transaction.getConnection();  
        stmt =handler.prepare(connection);  
        handler.parameterize(stmt);  
        return stmt;  
    }  
 

 

statement的开启和参数设置没什么特别的地方,handler.parameterize倒是可以看看是怎么回事。handler.parameterize通过调用ParameterHandlersetParameters完成参数的设置,ParameterHandler随着StatementHandler的创建而创建,默认的实现是DefaultParameterHandler

 

    publicParameterHandler newParameterHandler(MappedStatement mappedStatement, ObjectparameterObject, BoundSql boundSql) {  
       ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);  
       parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  
       returnparameterHandler;  
    }  
 

 

ExecutorStatementHandler一样,ParameterHandler也是可以被拦截的。

 

参数设置

 

DefaultParameterHandler里设置参数的代码如下:

 

[java] view plaincopy

    publicvoidsetParameters(PreparedStatement ps) throwsSQLException {  
       ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId());  
       List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
        if(parameterMappings != null) {  
           MetaObject metaObject = parameterObject == null ? null :configuration.newMetaObject(parameterObject);  
            for (int i = 0; i< parameterMappings.size(); i++) {  
               ParameterMapping parameterMapping = parameterMappings.get(i);  
                if(parameterMapping.getMode() != ParameterMode.OUT) {  
                   Object value;  
                   String propertyName = parameterMapping.getProperty();  
                   PropertyTokenizer prop = newPropertyTokenizer(propertyName);  
                   if (parameterObject == null) {  
                       value = null;  
                   } elseif (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){  
                       value = parameterObject;  
                   } elseif (boundSql.hasAdditionalParameter(propertyName)){  
                       value = boundSql.getAdditionalParameter(propertyName);  
                   } elseif(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)  
                            && boundSql.hasAdditionalParameter(prop.getName())){  
                       value = boundSql.getAdditionalParameter(prop.getName());  
                       if (value != null) {  
                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));  
                       }  
                   } else {  
                       value = metaObject == null ? null :metaObject.getValue(propertyName);  
                   }  
                   TypeHandler typeHandler = parameterMapping.getTypeHandler();  
                   if (typeHandler == null) {  
                       thrownew ExecutorException("Therewas no TypeHandler found for parameter " + propertyName  + " of statement " + mappedStatement.getId());  
                    }  
                   typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType());  
                }  
      
            }  
      
        }  
    }  
 

 

这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法里有这么一句:

 

this.boundSql= mappedStatement.getBoundSql(parameterObject);

 

它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等

 

参数设置完毕后,执行数据库操作(update或query)。如果是query最后还有个查询结果的处理过程。

 

结果处理

 

结果处理使用ResultSetHandler来完成,默认的ResultSetHandler是FastResultSetHandler,它在创建StatementHandler时一起创建,代码如下

 

    publicResultSetHandler newResultSetHandler(Executor executor, MappedStatementmappedStatement,  
    RowBoundsrowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSqlboundSql) {  
       ResultSetHandler resultSetHandler =mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);  
       resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);  
       returnresultSetHandler;  
    }  
 

 

可以看出ResultSetHandler也是可以被拦截的,可以编写自己的拦截器改变ResultSetHandler的默认行为。

 

 
  1. ResultSetHandler内部一条记录一条记录的处理,在处理每条记录的每一列时会调用TypeHandler转换结果,如下:
  2.     protectedbooleanapplyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {  
            booleanfoundValues = false;  
            for (StringcolumnName : unmappedColumnNames) {  
                final Stringproperty = metaObject.findProperty(columnName);  
                if (property!= null) {  
                    final ClasspropertyType =metaObject.getSetterType(property);  
                    if (typeHandlerRegistry.hasTypeHandler(propertyType)) {  
                       final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType);  
                       final Object value = typeHandler.getResult(rs,columnName);  
                       if (value != null) {  
                           metaObject.setValue(property, value);  
                           foundValues = true;  
                       }  
                    }  
                }  
            }  
            returnfoundValues;  
       }  
     

 

从代码里可以看到,决断TypeHandler使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。

 

分享到:
评论

相关推荐

    Mybatis源码分析.md

    - Mybatis源码分析 - 1. 解析配置文件,创建SQLSessionFactory - 2. 开启java程序和数据库之间的会话: - 3. 获取mapper代理对象: - 4. 执行mapper接口方法: - mybatis源码总结 &lt;!-- /TOC --&gt; Mybatis源码...

    动力节点SSM课件源码分析教程配套资料分享

    SSM源码分析课程简介: 编程人员技术提升最快的方式是阅读和理解优秀的代码,通过阅读和理解优秀开源框架源码,掌握开源框架是如何一步一步实现的,深入理解其底层实现原理,领悟大师级设计思想,开阔视野,让思想...

    Mybatis – 执行流程分析(源码分析)

    Resources ——》InputStream(解析xml)——》SqlSessionFactoryBuilder().build(inputStream)——》SqlSessionFactory——》SqlSession——》动态代理Mapper接口 package com.learn.util; import org.apache.ibatis....

    Java毕业设计-基于SSM+MySQL的医院预约挂号系统源码+数据库.zip

    - 根据需求分析和系统设计,编写实体类、DAO接口和Mapper文件。 - 实现系统的业务逻辑,包括用户注册、登录、预约挂号等功能。 - 开发系统的用户界面,使用HTML、CSS和JavaScript等前端技术。 4. 系统测试和调试...

    单点登录源码

    - 统一下单(统一下单接口、统一扫码)、订单管理、数据分析、财务报表、商户管理、渠道管理、对账系统、系统监控。 ![统一扫码支付](project-bootstrap/zheng-pay.png) &gt; zheng-ucenter 通用用户管理系统, 实现...

    基于SpringBoot实现的留学信息管理与分析系统源码+数据库+项目说明.zip

    sais-mapper:映射模块,提供mybatis操作的接口 sais-service:服务模块,提供服务 sais-web:web服务模块,admin对应后台模块,college对应系统展示部分,microblog对应论坛部分,common为公共部分

    积分管理系统java源码-knowledge:这是我的知识,包括我所有已知的

    积分管理系统java源码 基础知识 java基础 基本类型(占用的内存)和包装类型 数组和对象 程序控制语句,if、switch...分析Mybatis动态代理的真正实现 手动实现Mini版本的Mybatis 分布式 分布式原理 分布式架构的演进过

    Petstore:很久以前用java写的ssm+maven+semanticUI 宠物商店

    前端:semanticUI | JQuery | Ajax | jsp 后端:Spring | SpringMVC | Mybatis | MybatisGenerator | Mysql 整个项目用 maven管理,但还是有点大,等写完了我优化一下前端的资源总结用mybatis自动生成mapper的时候...

    JAVA毕业设计之基于springboot的的学生干部管理系统(springboot+mysql)完整源码.zip

    整个系统分为前端界面和后端接口两大部分,实现了学生干部信息的增删改查、活动记录的管理以及通讯录的查询等功能。系统功能模块如下:用户登录与注册:用户可以通过输入用户名和密码进行登录,未注册的用户可以先...

    t淘淘商城项目 商城项目 视频和源码教程 详细

    除了项目构建,Maven最核心的功能是软件包的依赖管理,能够自动分析项目所需要的依赖软件包,并到Maven中心仓库去下载。 A)管理依赖的jar包 B)管理工程之间的依赖关系。 3.2. Maven本地仓库 在当前系统用户的...

    J2eeFAST企业级快速开发平台-其他

    J2eeFAST是一个Java EE企业级快速开发平台,基于经典技术组合(Spring Boot、Spring MVC、Apache Shiro、MyBatis-Plus、Freemarker、Bootstrap、AdminLTE)采用经典开发模式,让初学者能够更快的入门并投入到团队...

Global site tag (gtag.js) - Google Analytics