Mybatis-spring 原理之 Mapperbean自动注入,mapper代理

1. 问题背景

使用mybatis + mapper配置的方式,在业务代码中

1.1 定义,自动导入Dao类

 @Resource
    private ViewpointPkgDao viewpointPkgDao;

1.2 使用,直接使用ViewpointPkgdao.listViewpointPkg()

// 单独在粉丝端过滤出没有关联组合code的观点包
List<ViewpointPkgFullInfo> noRelated = viewpointPkgDao.listViewpointPkg(qryForm).stream().
                    filter(vp -> StringUtil.isEmpty(vp.getCombiCode())).collect(Collectors.toList());
viewpointPkgList = CommonTool.genCopyBeanList(noRelated,

listViewpointPkg方法在ViewpointPkgdaoMapper.xml中有定义,即sql实现了该方法,指定了出参和入参,

<select id="listViewpointPkg"
            parameterType="xxxx.ViewpointPkgDataBaseQryForm"
            resultType="xxxxx.ViewpointPkgFullInfo">
        SELECT
            a.viewpoint_pkg_id AS viewpointPkgId,
            a.name,
            a.broker_id AS brokerId,
            a.broker_manager_id AS brokerManagerId,
            a.status,
            ...
        FROM
            <include refid="filterViewpointPkgFullInfoSql"/>
        <if test="orderBySql != null">
            ORDER BY ${orderBySql}
        if>
        <if test="limitSql != null">
            LIMIT ${limitSql}
        if>
    select>

为啥,通过MyBatis对ViewpointPkgDaomapper.xml自动生成工具代码后,本质MyBatis和我们都没有实现ViewpointPkgDao接口,业务代码中能直接调用ViewpointPkgDao接口的相关方法?

答案是: spring容器bean注入+动态代理

从调试结果也证明了这一动态代理猜想,org.apache.ibatis.binding.MapperProxy@6304a864
Mybatis-spring 原理之 Mapperbean自动注入,mapper代理_第1张图片

2. spring 注入mapperBean

https://zhuanlan.zhihu.com/p/196744982
Mybatis-spring 原理之 Mapperbean自动注入,mapper代理_第2张图片

3. 动态代理

3.1 定义

org.apache.ibatis.binding.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;
  }

3.2 被创建

org.apache.ibatis.binding.MapperProxyFactory#newInstance
通过mapper代理工厂创建mapper代理

  // jdk动态代理
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  
  // 创建了
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

3.2.1 getMapper

接着newInstanceorg.apache.ibatis.binding.MapperRegistry#getMapper调用

public class MapperRegistry {

  private Configuration config;
  // 保存了每个接口类,对应mapper代理工厂
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();


  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 {
    // 通过动态代理工厂生成maper代理实例。
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

紧接着mapperRegistry.getMapperorg.apache.ibatis.session.Configuration#getMapper调用

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

configuration.getMapper最终可被这3处调用
Mybatis-spring 原理之 Mapperbean自动注入,mapper代理_第3张图片
其中sqlSessionManager,DefaultSqlsession是原生mybatis的调用,而sqlSessionTemplatemybatis-spring框架的调用

这也是为什么我们可以这样使用:

  • main函数调用
public static void main(String[] args) {
	InputStream inputStream =
		Resources.getResourceAsStream("sqlMapConfig.xml");
    // 这一步很关键,用于加载配置添加mapper,所以后面我们才能直接获取到mapper
	SqlSessionFactory factory = new
	SqlSessionFactoryBuilder().build(inputStream);
	SqlSession sqlSession = factory.openSession();
	//这里不再调用SqlSession的api,而是获得了接口对象,调用接口中的方法。
	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	List<User> list = mapper.getUserByName("tom");
}

org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
  • mybatis-spring框架中集成了

MapperFactoryBean对应我们的xxxMapper接口,本质上是mapper代理。

org.mybatis.spring.mapper.MapperFactoryBean#getObject

 public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

org.mybatis.spring.SqlSessionTemplate#getMapper

  public <T> T getMapper(Class<T> type) {
    // 这里就调用了configuration的getmapper
    return getConfiguration().getMapper(type, this);
  }

3.2.2 addMapper

这就引申出 何处添加了 mapper,
org.apache.ibatis.binding.MapperRegistry#addMapper

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 解析mapper.xml文件,构建出每个method的statment,添加到configuration的mappedStatements(Map
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

org.apache.ibatis.binding.MapperRegistry#addMappers
org.apache.ibatis.binding.MapperRegistry#addMappers
org.apache.ibatis.session.Configuration#addMappers
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
Mybatis-spring 原理之 Mapperbean自动注入,mapper代理_第4张图片
最终parse 被一下2处调用,用于构建mapper,生成mapper代理

  • org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 给spring-mybatis框架用
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      configuration.setVariables(this.configurationProperties);
    }
     // 省略不重要部分
    ...

    if (xmlConfigBuilder != null) {
      try {
        // 这一句调用了添加addMapper操作
        xmlConfigBuilder.parse();

        if (logger.isDebugEnabled()) {
          logger.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

    if (this.databaseIdProvider != null) {
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (logger.isDebugEnabled()) {
          logger.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet

  // 进一步在spring容器创建SqlSessionFactoryBean时,便构建好了mapper工厂bean所依赖的xxxmapper
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }
  • org.apache.ibatis.session.SqlSessionFactoryBuilder#build 纯mybatis框架用

3.2.3 MappedStatement

如上 addMapper所述,mapper.xmlmethod转化为MappedStatement存储在configurationmappedStatements

mappedStateMent何时使用呢?
先说结论: 执行时使用

先看另原始调用

Inputstream inputstream = Resources.getResourceAsStream("mybatisconfig.xml");
//这一行代码正是初始化工作的开始。
SqlSessionFactory factory = new
	SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
List<User> list =
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList就涉及到了mappedStateMent

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 执行器接受mapStatement,参数,执行sql,获得结果
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

至于执行器底层如何绑定参数,执行sql,详见下一章。

你可能感兴趣的:(java,spring,mybatis-spring,java)