• MyBatis 与 Spring 是如何结合在一起工作的——mybatis-spring(version:1.2.2)


    在MyBatis-Spring的项目中,我们一般会为MyBatis配置两个配置文件 beans-mybatis.xml mybatis-config.xml
    其中 beans-mybatis.xml 中配置的是MyBatis 和 Spring结合使用时委托给 spring 管理的 bean。
    mybatis-config.xml 中是MyBatis 自身的配置。

    例:beans-mybatis.xml 

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml"
            p:mapperLocations="classpath:mapper/**/*.xml" />
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager" />
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.cn.kvn.usage.dao" />

    mybatis-config.xml:

    <configuration>
        <settings>
            <setting name="lazyLoadingEnabled" value="false" />
            <!-- logback日志指定打印sql用 -->
            <setting name="logPrefix" value="dao." />
        </settings>
        <typeAliases>
            <!-- 别名定义 -->
        </typeAliases>
        <!-- 插件 -->
        <plugins>
            <plugin interceptor="com.cn.kvn.framework.jdbc.mybatis.interceptor.PageInterceptor">
                <property name="dialectClassName" value="com.cn.kvn.framework.jdbc.mybatis.interceptor.MySQLDialect" />
            </plugin>
        </plugins>
    </configuration>

    mybatis-spring的入口就在bean的定义那里(beans-mybatis.xml ): SqlSessionFactoryBean 、MapperScannerConfigurer

    #####1. SqlSessionFactoryBean

    SqlSessionFactoryBean实现了 InitializingBean ,在 afterPropertiesSet() 中会对 mybatis的配置 p:configLocation="classpath:mybatis-config.xml" 、 p:mapperLocations="classpath:mapper/**/*.xml" 进行解析。

    org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory():

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        Configuration configuration; // MyBatis 的配置,存放 mybatis-config.xml 中的配置
        XMLConfigBuilder xmlConfigBuilder = null; // mybatis-config.xml 的解析器,解析出来的内容放在 Configuration 中
        if (this.configLocation != null) {
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          configuration = xmlConfigBuilder.getConfiguration();
        } else {
          configuration = new Configuration();
          configuration.setVariables(this.configurationProperties);
        }
    
        if (this.objectFactory != null) {
          configuration.setObjectFactory(this.objectFactory);
        }
    
        if (this.objectWrapperFactory != null) {
          configuration.setObjectWrapperFactory(this.objectWrapperFactory);
        }
    
        if (hasLength(this.typeAliasesPackage)) { // typeAlias : 类型别名,用于sqlmap中类型(parameterType、resultType)的配置
          String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
          for (String packageToScan : typeAliasPackageArray) {
            configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); // 注册 typeAliases
          }
        }
    
        if (!isEmpty(this.typeAliases)) { // typeAlias : 类型别名,用于sqlmap中类型(parameterType、resultType)的配置
          for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias); // 注册 typeAliases
          }
        }
    
        if (!isEmpty(this.plugins)) { // plugins : mybatis 的插件,即 MyBatis 的拦截器和扩展点。可以针对Executor 、 StatementHandler 、 ResultSetHandler 和 PameterHandler 进行拦截扩展
          for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin); // 添加 MyBatis Interceptor
          }
        }
    
        if (hasLength(this.typeHandlersPackage)) { // typeHandler : 类型转换器,对java类型和sqlmap中的类型进行转换
          String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
          for (String packageToScan : typeHandlersPackageArray) {
            configuration.getTypeHandlerRegistry().register(packageToScan); // 注册 typeHandler
          }
        }
    
        if (!isEmpty(this.typeHandlers)) { // typeHandler : 类型转换器,对java类型和sqlmap中的类型进行转换
          for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler); // 注册 typeHandler
          }
        }
    
        if (xmlConfigBuilder != null) {
          try {
            xmlConfigBuilder.parse(); // xmlConfigBuilder解析 mybatis-spring.xml 配置
          } catch (Exception ex) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
          } finally {
            ErrorContext.instance().reset(); // 重置 ErrorContext
          }
        }
    
        if (this.transactionFactory == null) {
          this.transactionFactory = new SpringManagedTransactionFactory(); // 默认的 transactionFactory
        }
    
        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)); // DatabaseId 用于对多数据库的支持
          } 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(); // xmlMapperBuilder:解析 mapperLocation 中指定的 sqlmap 文件,即解析 sql 语句
            } catch (Exception e) {
              throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
              ErrorContext.instance().reset(); // 重置 ErrorContext
            }
    
          }
        }
        return this.sqlSessionFactoryBuilder.build(configuration); // 将 configuration 配置设置到 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 中
    }

    附:ErrorContext: 解析配置文件时,用于存储异常信息(ThreadLocal实现的)

    跟配置解析相关的类:

    XMLConfigBuilder : 解析 mybatis-config.xml 中 MyBatis 自身的配置。
    XmlMapperBuilder : 解析 sqlmap 中的配置,包括 <resultMap> 和 sql 语句
      ResultMapResolver : 解析 sqlmap 中的 <resultMap> 节点
      XMLStatementBuilder : 解析 sql 语句(<insert>、<delete>、<update>、<select>)

    举例:mybatis-config.xml 的解析是通过 XMLConfigBuilder 来完成的

    mybatis-config.xml中能够配置的属性:(XMLConfigBuilder#parseConfiguration(XNode))

    private void parseConfiguration(XNode root) {
        try {
          propertiesElement(root.evalNode("properties")); //issue #117 read properties first
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          settingsElement(root.evalNode("settings"));
          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

    其中<settings>标签可以配置的属性如下:(XMLConfigBuilder#settingsElement(XNode))

    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));

    #####2. MapperScannerConfigurer

    MapperScannerConfigurer 的作用是扫描 java 的 Dao 接口,来与 mapper 做映射关系

    org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        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.registerFilters(); // java类型过滤器,排除一些不符合要求的 Dao
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); // 进行 Dao 扫描,扫描 basePackage 包下面的类
    }

    org.mybatis.spring.mapper.ClassPathMapperScanner#scan(String... basePackages):

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); // 调用 ClassPathBeanDefinitionScanner#doScan()解析出bean
        for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
    
        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); // mapperInterface 是 Dao 对应的 bean 的原始类型 
        definition.setBeanClass(MapperFactoryBean.class); // MapperFactoryBean 是 Dao 对应的 bean 的实际类型。也就是所有的 Dao 对应的 bean ,最后都是 MapperFactoryBean,通过 MapperFactoryBean 来做 Dao 的代理,将CRUD操作分发到具体的 sqlmap 中的 sql 去执行。
    
        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;
        }
    
        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }
    
        if (!explicitFactoryUsed) {
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 让 MapperFactoryBean 可以通过 @Autowired 注解来进行注入。即注入 xxxDao
        }
    
        return beanDefinitions;
    }

    #####3. MapperFactoryBean

    MapperFactoryBean 继承了SqlSessionDaoSupport,默认使用了 org.mybatis.spring.SqlSessionTemplate 来操作CRUD方法。

    org.mybatis.spring.SqlSessionTemplate 的构造方法:

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
          PersistenceExceptionTranslator exceptionTranslator) {
        notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator; // 默认使用 MyBatisExceptionTranslator ,对 SQL 异常进行友好转换
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());
    }

    SqlSessionTemplate 又是通过 sqlSessionProxy 来操作CRUD方法的。
    sqlSessionProxy 是JDK 动态代理,通过 SqlSessionInterceptor 来拦截 org.apache.ibatis.session.SqlSession 接口里面的 CRUD 操作。

    org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor:

    /**
       * Proxy needed to route MyBatis method calls to the proper SqlSession got
       * from Spring's Transaction Manager
       * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
       * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
       */
      private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          SqlSession sqlSession = getSqlSession(
              SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType,
              SqlSessionTemplate.this.exceptionTranslator); // 从当前线程中获取一个 SqlSession ,如果没有,就创建一个新的 SqlSession。(org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(ExecutorType execType))。这样 spring 和 mybatis 就结合在一起了
          try {
            Object result = method.invoke(sqlSession, args); // 执行 SqlSession 的方法(CRUD操作)。
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
              // force commit even on non-dirty sessions because some databases require
              // a commit/rollback before calling close()
              sqlSession.commit(true);
            }
            return result;
          } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t); // 对 Method#invoke(Object, Object...) 的异常进行解封装
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
              // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
              sqlSession = null;
              Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); // 对 SQL 异常进行友好转换
              if (translated != null) {
                unwrapped = translated;
              }
            }
            throw unwrapped;
          } finally {
            if (sqlSession != null) {
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
          }
        }
      }

    至此,我们还有一个问题没有解决:我们在调用 xxDao 中的方法 xxMethod 时,mybatis-spring.jar 是如何找到 xxMethod 对应的配置文件 xxMapper.xml 中的 sql 语句的?

    它是通过多个 JDK 动态代理,去拦截 mapperInterface(xxDao)中的方法来实现的。具体可以看下图:

    通过上图,前面的疑问就迎刃而解了。^_^

    附:MapperFactoryBean 中的 sqlSessionFactory 是如何注入的?

    在 Spring 容器里面 IJobBeforehandRetryDao.java 对应的 beanName = "IJobBeforehandRetryDao"

    相应的 RootBeanDefinition为:

    Root bean: class [org.mybatis.spring.mapper.MapperFactoryBean]; scope=singleton; abstract=false;
     lazyInit=false; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false;
     factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
     defined in file [E:gitWorkspacedal-jobdal-job-core	argetclassescomkvndalcoredaoIJobBeforehandRetryDao.class]

    IJobBeforehandRetryDao 对应的 bean 是一个 FactoryBean。Spring 对 FactoryBean 里面的属性有特殊处理。

    AbstractAutowireCapableBeanFactory#autowireByType(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs)

    MapperFactoryBean#setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)会被调用。

    这样,MapperFactoryBean 中的属性 sqlSession就会被赋值。 this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

  • 相关阅读:
    C#中的委托(delegate)用法简介 dodo
    SqlServer2000日志文件过大问题处理 dodo
    prototype.js 显示等待状态 dodo
    linux常用命令 dodo
    关于NavigateUrl中绑定Eval()方法时出现"服务器标记的格式不正确"的解决方法 dodo
    DataGridViewRowHeadersWidthSizeMode属性和ColumnHeadersHeightSizeMode属性 dodo
    注销时跳出框架 dodo
    DriveInfo类取得计算机的磁盘信息 dodo
    类序列化 dodo
    CutEditor在线编辑器的使用 dodo
  • 原文地址:https://www.cnblogs.com/kevin-yuan/p/7229777.html
Copyright © 2020-2023  润新知