spring的mybatis整合笔记(二)

spring在和mybatis整合时,有两个主要的配置项;

一是sqlSessionFactory的配置,在这个配置里会配置数据库连接池,mapper的xml的路径,比如

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(boneCPDataSource());
        // 配置mapper的扫描,找到所有的mapper.xml映射文件
        Resource[] resources = new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml");
        factoryBean.setMapperLocations(resources);
        return factoryBean;
    }

    @Bean(destroyMethod="close")
    public BoneCPDataSource boneCPDataSource(){
        //配置数据库连接池对象
        BoneCPDataSource boneCPDataSource=new BoneCPDataSource();
        boneCPDataSource.setDriverClass("com.mysql.jdbc.Driver");
        boneCPDataSource.setUsername("root");
        boneCPDataSource.setPassword("root");
        boneCPDataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mytest?serverTimezone=GMT%2B7");
        return boneCPDataSource;
    }

二是mapper类路径的配置,这个路径用一个注解就可以了,在启动的配置类里添加

@MapperScan("com.cn.spring.mybatis.mapper")

此处,我们先看下sqlSessionFactory都做了哪些事

     1、说下SqlSessionFactoryBean和SqlSessionFactory的不同。SqlSessionFactoryBean实现了FactoryBean的接口,我们都知道在创建spring的bean时,有一种方式就是实现此接口。所以SqlSessionFactoryBean和SqlSessionFactory本质上是一样的。

    2、SqlSessionFactoryBean还实现了InitializingBean接口,这个是在spring中bean的生命周期中被回调的,具体是在bean初始化完之后回调InitializingBean的afterPropertiesSet方法,就是下面这个方法


  /**
   * {@inheritDoc}
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

3、上面的这个方法,主要是调用了buildSqlSessionFactory()这个方法。这个方法里有很多配置属性的解析,主要是配置的

configLocation里的XML的属性。当然这次我们比较关心mapper.xml的解析,所以暂时先放过它们,看下mapper的解析。
 targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

4、上面的主要是读取了配置了mapperLocations下的文件。然后遍历解析。调用的方法是xmlMapperBuilder.parse();我们看下这个方法的实现

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    //解析resultMap
    parsePendingResultMaps();
    //解析cacheref
    parsePendingCacheRefs();
    //解析statements
    parsePendingStatements();
  }

这个方法里,主要看bindMapperForNamespace();这个是注册所有mapper的映射类

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

最终是注册到了一个map里,

  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<>(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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

这个map是knownMappers,记住这个map,后期会用到的,至此sqlSessionFactory在初始化阶段的工作基本完成。

 

二,我们再看下@MapperScan都做了哪些事

   1、在这个注解里引入了另一个组件@Import(MapperScannerRegistrar.class),这个也是整合spring的惯用套路。

   2、MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar,接口,这个也是注册bean的一个常用方法。可以实现

registerBeanDefinitions这个方法,然后在此方法里注册bean。看下它注册了什么bean
 @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
	//其它代码删除了,只留了这一行,因为MapperScannerConfigurer是接下来的最重要的一步
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

可以看到这个类注册了MapperScannerConfigurer类,从名字,也可以看出这个类,主要是用来扫描mapper的类的。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这也是spring生命周期中,比较重要的一个后置处理器。是在beanFactory初始完之后调用的。此处可以对bean容器进行修改,包括添加自己的bean组件。

而mybatis也是在这个后置处理器里添加自己的mapper组件的。看下他在postProcessBeanDefinitionRegistry这个方法里,扫描并注册了所有的mapper类。

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    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.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

3、接下来看下ClassPathMapperScanner,这个扫描类,他继承了spring的扫描类ClassPathBeanDefinitionScanner,重写了它的doScan方法。

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> 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;
  }

 

4、这里mybatis也是用了spring的扫描功能,然后调用了processBeanDefinitions这个方法,来处理自己的逻辑,看下这个方法的实现,方法进行了一些删减。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
	  //1、注意此处,选择了只有一个参数的构造方法,
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
	  //2、注意此处,把原来的mapper的类的类型修改了,统一改成了MapperFactoryBean这个类
      definition.setBeanClass(this.mapperFactoryBeanClass);

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

      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
		//3、注意此处,类的注入方法修改成了AUTOWIRE_BY_TYPE,此时只要有set方法,就会实现按类型进行spring的注入
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
	  //4、注意此处,lazyInitialization为false,在初始化时,进行赋值的
      definition.setLazyInit(lazyInitialization);
    }
  }

此处把mapper的类统一修改成了MapperFactoryBean,我们看下它的实现

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>

1)、它继承了SqlSessionDaoSupport,这个类里有下面这个方法,所以可以完成sqlSessionFactory的自动注入(可以看到上面代码第3点的说明)及sqlSessionTemplate的创建。

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

2)、实现了FactoryBean接口,这个接口上面说过,是注册bean的一个常用方法,当获取bean时,会调用这个的getObect方法,现在看下它的getObject方法。

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

这个是从那里获取的呢?我们找下根源,根据调用路径找到了下面的类,就是下面的 getMapper这个方法


public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
    }
  }

//删除了多余的代码


}

1)、从这里我们看到了熟悉的knownMappers 这个就是刚才在解析SqlSessionFactoryBean时,最后mapper.xml里信息存储的地方,现在在获取mapper类信息时,也最终到了这里, 从这里也就实现了mapper.xml和mapper类的汇聚。

2)、再接下来调用了mapperProxyFactory.newInstance(sqlSession);来返回对象,我们再看下这个方法

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  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<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

这个类的代码很少,都是干货。这个代码里,我们可以看到newInstance返回的是一个代码里,而代理方式就是jdk自带的动态代理的实现。这是返回的就是一个代理对象。

我们看下这个动态代码的调用的 invoke方法。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

 1)、final MapperMethod mapperMethod = cachedMapperMethod(method);是获取mapper.xml里的解析出来的方法,

2)、mapperMethod.execute(sqlSession, args)调用并返回结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值