Mybatis源码研读(二)—— 从接口到SQL

从接口如何调用到SQL

上文中,我们了解了我们的存放在XML里面的SQL是如何被解析到Mybatis的框架中了。
但是我们实际的Spring项目中使用是定义的一个接口,然后与通过这个接口中的方法来调用的实际的SQL。那在这其中Mybatis又为我们做了哪写事儿呢。

修改项目为一个Spring项目

主要的修改:

  1. 增加SpringBoot 的主类
  2. Mapper.java中增加标签@Repository
  3. 增加application.yaml

主入口类:

package com.example.mybatis.mybatiddemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.mybatis.mybatiddemo.mapper")
public class MybatiddemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatiddemoApplication.class, args);
    }

}

Mapper类:

package com.example.mybatis.mybatiddemo.mapper;

import com.example.mybatis.mybatiddemo.module.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {

    User getUser(@Param("id") Integer id);

    User getUserName(@Param("id") Integer id);
}

application.yaml:

server:
  port: 8080

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapping/*Mapper.xml
  type-aliases-package: com.example.mybatis.mybatiddemo.module

#showSql
logging:
  level:
    com:
      example:
        mapper: debug

Controller类

package com.example.mybatis.mybatiddemo.controller;

import com.example.mybatis.mybatiddemo.mapper.UserMapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserMapper userMapper;

    @RequestMapping("")
    public Object userInfo() {
        return userMapper.getUser(1);
    }
}

启动这个demo,访问localhost:8080/user 可以获取到结果数据。

代码分析

在这个Demo中,在Application类中有一个标签

@MapperScan("com.example.mybatis.mybatiddemo.mapper")

以及Mapper中心增加的标签:

@Repository

MapperScan标签的处理类为 org.mybatis.spring.annotation.MapperScannerRegistrar, 这个类中生成 org.mybatis.spring.mapper.MapperScannerConfigurer 的Configuration处理类。

接着来看 org.mybatis.spring.mapper.MapperScannerConfigurer 这个类

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  private String basePackage;
  private boolean addToConfig = true;
  private String lazyInitialization;
  private SqlSessionFactory sqlSessionFactory;
  private SqlSessionTemplate sqlSessionTemplate;
  private String sqlSessionFactoryBeanName;
  private String sqlSessionTemplateBeanName;
  private Class annotationClass;
  private Class markerInterface;
  private Class mapperFactoryBeanClass;
  private ApplicationContext applicationContext;
  private String beanName;
  private boolean processPropertyPlaceHolders;
  private BeanNameGenerator nameGenerator;

这个Configuration的类实现了几个接口,最为重要的是BeanDefinitionRegistryPostProcessor。它可以 动态的注册Bean信息,方法为postProcessBeanDefinitionRegistry()。

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    //构建ClassPathMapperScanner用于扫描Mapper包
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    //设置Scanner的Filter
    scanner.registerFilters();
    //扫描包
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

而ClassPathMapperScanner继承自Spring中的ClassPathBeanDefinitionScanner,所以在上面的scan方法,实际上是调用的父类的ClassPathBeanDefinitionScanner.scan方法。在scan方法中又会回调子类的doScan 方法,也就是 ClassPathMapperScanner.doScan .

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  public Set doScan(String... basePackages) {
    Set beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }
}

其中会调用父类的 super.doScan() 方法,获取BeanDefinitionHolder,也就是扫描到带有 @Repository 的类。
然后在 processBeanDefinitions(beanDefinitions) 中对Holder进行实例化,初始化。

Mybatis始化Mapper类

ClassPathMapperScanner.processBeanDefinitions(beanDefinitions)
截取出比较重要的:

  private void processBeanDefinitions(Set beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
    }
  }

其中最重要的点是:

// 设置mapper接口的名称到构造参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
// 设置BeanDefinition的class
definition.setBeanClass(this.mapperFactoryBeanClass);
//设置属性addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
//设置属性sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  1. 设置beanClass
    设置BeanDefinition对象的BeanClass为MapperFactoryBean。这意味着什么呢?以UserMapper为例,意味着当前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那么在IOC初始化的时候,实例化的对象就是MapperFactoryBean对象( 这一点是关键点 ).这说明在Spring实例化该对象的时候是去创建这个对象。

  2. 设置参数 sqlSessionFactory , 这个参数会在初始化Mapper的时候使用到。

生成代理Bean

上面初始化BeanDefinition的时候说到BeanDefinition对象就是beanClass为MapperFactoryBean,所以我肯定要去看这个类。

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
  private Class mapperInterface;
  private boolean addToConfig = true;
  public MapperFactoryBean() {
    // intentionally empty
  }
  //初始化的使用调用这个方法
  public MapperFactoryBean(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

在实例化UserMapper的时候,就会调用到MapperFactoryBean的构造方法。

而MapperFactoryBean它继承自FactoryBean,自然也实现了接口方法getObject() , 于是spring会通过这个FactoryBean调用getObject 创建的Bean实例.

其中接口调用getSqlSession 。
看到这里又可以回到上一节中,这个SqlSession就是上节中的SqlSessionFactory。

这里的调用链就是。
MapperFactoryBean.getObject
-> SqlSessionTemplate.getMapper
-> MapperRegistry.getMapper
-> MapperProxyFactory.newInstance
-> Proxy.newProxyInstance

public class MapperProxyFactory {
    protected T newInstance(MapperProxy mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

所以最终获取到的Mapper类是由Proxy生成的代理类,代理的接口是 UserMapper,而代理类为MapperProxy。

MapperProxy代理处理

接着看这个MapperProxy代理类。

public class MapperProxy implements InvocationHandler, Serializable {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
  }
}

会先获取MapperMethodInvoker ,然后调用invoke。
而MapperMethodInvoker这个类封装了MapperMethod。在构造MapperMethod的过程可以看如下:

  private MapperMethodInvoker cachedInvoker(Method method) {
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
        return invoker;
      }
      return methodCache.computeIfAbsent(method, m -> {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      });
  }

在构造MapperMethod的参数中传入了,sqlSession.getConfiguration

  public MapperMethod(Class mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

其中构造了 SqlCommand

    public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
      //获取方法名
      final String methodName = method.getName();      
      final Class declaringClass = method.getDeclaringClass();
      //从Config中获取MappedStatement 。这个就是第二节中,我们解析xml后保存在config中的数据了。
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
    }

接下来就是调用invoke的时候会去执行MapperMethod的execute函数。

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;
    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

在execute方法中根据Command的类型,路由到不同的调用方法。比如下面是Select中的路由调用。

    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);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;

接下来以SelectOne为实例
然后开始调用SqlSessionTemplate.selectOne -> DefaultSqlSession.selectOne -> DefaultSqlSession.selectList

  @Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  }

这里也很熟悉。也是第二节中保存在Config中的MappedStatement。然后通过statement获取到之后用于Executor来执行。

接下来才是找到重点

  @Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

在上面的代码中,通过ms以及参数获取到了BoundSql,这个BoundSql也就是在第二节中,通过xml解析出来的sql 。

这时候,SQL也解析出来了。就可以最后调用

  public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;    
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    return list;
  }

可以看到这个queryFromDatabase ,看着名字就知道终于找到调用数据库的了。

你可能感兴趣的:(Mybatis源码研读(二)—— 从接口到SQL)