美文网首页
mybatis整合spring

mybatis整合spring

作者: 拥抱孤独_to | 来源:发表于2020-07-14 18:14 被阅读0次

    mybatis篇

    再来看下mybatis在spring中是如何使用的,mybatis为整合spring提供了个新的包mybatis-spring,所以我们先引入依赖

     <!-- mybatis-spring -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.5</version>
            </dependency>
            <!-- Compile dependencies -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.5</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.20</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.6.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </dependency>
    
            <!--单元测试-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
                <scope>test</scope>
            </dependency>
    
    

    添加application.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.xsd" default-autowire="byName">
    
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
              destroy-method="close">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.174.128:3306/mybatis?characterEncoding=utf-8&amp;autoReconnect=true&amp;allowMutiQueries=true&amp;serverTimezone=Asia/Shanghai"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
            <!-- 初始化连接大小 -->
            <property name="initialSize"  value="0"/>
            <!-- 连接池最大数量 -->
            <property name="maxActive" value="20"/>
            <!-- 连接池最小空闲 -->
            <property name="minIdle" value="1"/>
            <!-- 获取连接最大等待时间 -->
            <property name="maxWait" value="60000"/>
        </bean>
    
        <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <!-- mapping.xml文件地址 -->
            <property name="mapperLocations" value="classpath:mapper/*.xml"/>
            <property name="typeHandlers">
                <array>
                    <bean class="spring.mybatis.typehandle.SexHandler"/>
                </array>
            </property>
        </bean>
    
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 接口包地址 -->
            <property name="basePackage" value="spring.mybatis.dao"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
    
    
        <!-- (事务管理)transaction manager-->
        <bean id="transactionManager"
              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    

    在这里我们看到,多出了个SqlSessionFactoryBean以及MapperScannerConfigurer这两个类,所以我们要研究mybatis是如何整合spring的就主要看这两个类

    public static void main(String[] args) {
            ClassPathXmlApplicationContext configApplicationContext =
                    new ClassPathXmlApplicationContext("classpath:application.xml");
            MybatisUserinfoMapper bean = configApplicationContext.getBean(MybatisUserinfoMapper.class);
            MybatisUserinfoModel model = bean.selectByKey(4);
            System.out.println(model);
        }
    
    image.png

    可以看到,mybatis能够正常执行

    • SqlSessionFactoryBean

        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();
        }
      
        protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
      
          Configuration configuration;
      
          XMLConfigBuilder xmlConfigBuilder = null;
          if (this.configuration != null) {
            configuration = this.configuration;
            if (configuration.getVariables() == null) {
              configuration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
              configuration.getVariables().putAll(this.configurationProperties);
            }
          } else if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
          } else {
            if (LOGGER.isDebugEnabled()) {
              LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            }
            configuration = new Configuration();
            if (this.configurationProperties != null) {
              configuration.setVariables(this.configurationProperties);
            }
          }
      
          if (this.objectFactory != null) {
            configuration.setObjectFactory(this.objectFactory);
          }
      
          if (this.objectWrapperFactory != null) {
            configuration.setObjectWrapperFactory(this.objectWrapperFactory);
          }
      
          if (this.vfs != null) {
            configuration.setVfsImpl(this.vfs);
          }
      
          if (hasLength(this.typeAliasesPackage)) {
            String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeAliasPackageArray) {
              configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                      typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
              }
            }
          }
      
          if (!isEmpty(this.typeAliases)) {
            for (Class<?> typeAlias : this.typeAliases) {
              configuration.getTypeAliasRegistry().registerAlias(typeAlias);
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type alias: '" + typeAlias + "'");
              }
            }
          }
      
          if (!isEmpty(this.plugins)) {
            for (Interceptor plugin : this.plugins) {
              configuration.addInterceptor(plugin);
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered plugin: '" + plugin + "'");
              }
            }
          }
      
          if (hasLength(this.typeHandlersPackage)) {
            String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeHandlersPackageArray) {
              configuration.getTypeHandlerRegistry().register(packageToScan);
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
              }
            }
          }
      
          if (!isEmpty(this.typeHandlers)) {
            for (TypeHandler<?> typeHandler : this.typeHandlers) {
              configuration.getTypeHandlerRegistry().register(typeHandler);
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type handler: '" + typeHandler + "'");
              }
            }
          }
      
          if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
            try {
              configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException e) {
              throw new NestedIOException("Failed getting a databaseId", e);
            }
          }
      
          if (this.cache != null) {
            configuration.addCache(this.cache);
          }
      
          if (xmlConfigBuilder != null) {
            try {
              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();
          }
      
          configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
      
          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);
        }
      
      
        public SqlSessionFactory getObject() throws Exception {
          if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
          }
      
          return this.sqlSessionFactory;
        }
      

      SqlSessionFactoryBean 实现了FactoryBean以及InitializingBean,从容器中获取该Bean实际上得到的是DefaultSqlSessionFactory类。

    • MapperFactoryBean

        public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
            this.checkDaoConfig();
    
            try {
                this.initDao();
            } catch (Exception var2) {
                throw new BeanInitializationException("Initialization of DAO failed", var2);
            }
        }
    
      protected void checkDaoConfig() {
        super.checkDaoConfig();
    
        notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    
        Configuration configuration = getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
          try {
            configuration.addMapper(this.mapperInterface);
          } catch (Exception e) {
            logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
            throw new IllegalArgumentException(e);
          } finally {
            ErrorContext.instance().reset();
          }
        }
      }
    
      @Override
      public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
      }
    

    MapperFactoryBean 同样也是实现了FactoryBean,最终返回的就是实现了mapperInterface接口的MapperProxy类,

    所以通过context.getBean(UserMapper.class)能找到实现了该类的子类MapperProxy进行工作。

    通过上述的配置每一个mapper都要配置一个MapperFactoryBean就太繁琐了,所以出现了

    • MapperScannerConfigurer

      该类实现了BeanDefinitionRegistryPostProcessor,所以在加载Beandefinition的过程中会调用postProcessBeanDefinitionRegistry方法

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

    该方法有下面几个作用:

    1. 扫描指定包下面的所有接口

    2. 将所有接口的BeanClass设置为MapperFactoryBean.class,并将构造参数设置为该接口的class

    这样就把该包下所有的接口对应起了独自MapperFacotyBean。

      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;
      }
    
        protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
            Assert.notEmpty(basePackages, "At least one base package must be specified");
            Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
            for (String basePackage : basePackages) {
                // 主要步骤
                Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
                for (BeanDefinition candidate : candidates) {
                    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                    candidate.setScope(scopeMetadata.getScopeName());
                    String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                    if (candidate instanceof AbstractBeanDefinition) {
                        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                    }
                    if (candidate instanceof AnnotatedBeanDefinition) {
                        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                    }
                    if (checkCandidate(beanName, candidate)) {
                        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                        definitionHolder =
                                AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                        beanDefinitions.add(definitionHolder);
                        registerBeanDefinition(definitionHolder, this.registry);
                    }
                }
            }
            return beanDefinitions;
        }
    
        private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
            Set<BeanDefinition> candidates = new LinkedHashSet<>();
            try {
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
                Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
                boolean traceEnabled = logger.isTraceEnabled();
                boolean debugEnabled = logger.isDebugEnabled();
                for (Resource resource : resources) {
                    if (traceEnabled) {
                        logger.trace("Scanning " + resource);
                    }
                    if (resource.isReadable()) {
                        try {
                            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                            // 原本这个方法只扫描带有@component注解的类,ClasspathMapperScanner重写了该方法
                            if (isCandidateComponent(metadataReader)) {
                                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                                sbd.setResource(resource);
                                sbd.setSource(resource);
                                if (isCandidateComponent(sbd)) {
                                    if (debugEnabled) {
                                        logger.debug("Identified candidate component class: " + resource);
                                    }
                                    candidates.add(sbd);
                                }
                                else {
                                    if (debugEnabled) {
                                        logger.debug("Ignored because not a concrete top-level class: " + resource);
                                    }
                                }
                            }
                            else {
                                if (traceEnabled) {
                                    logger.trace("Ignored because not matching any filter: " + resource);
                                }
                            }
                        }
                        catch (Throwable ex) {
                            throw new BeanDefinitionStoreException(
                                    "Failed to read candidate component class: " + resource, ex);
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not readable: " + resource);
                        }
                    }
                }
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
            }
            return candidates;
        }
    
    
    
    
      @Override
      protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
      }
    

    ClasspathMapperScanner重写了该方法,所以会扫描为接口的类装载成BeanDefinition.

    最后调用processBeanDefinitions方法

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
    
          if (logger.isDebugEnabled()) {
            logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
          }
    
          // the mapper interface is the original class of the bean
          // but, the actual class of the bean is MapperFactoryBean
          definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
          definition.setBeanClass(this.mapperFactoryBean.getClass());
    
          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)) {
            if (explicitFactoryUsed) {
              logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionTemplate != null) {
            if (explicitFactoryUsed) {
              logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
          }
    
          if (!explicitFactoryUsed) {
            if (logger.isDebugEnabled()) {
              logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            }
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
          }
        }
      }
    

    这个方法中最关键的地方

          definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
          definition.setBeanClass(this.mapperFactoryBean.getClass());
    
    

    实际上就是将实际类型设置为MapperFactoryBean类型,也就是每个类型是MapperFactoryBean对象,同时将构造参数mapperInterface设置成了实际的接口类型,所以这里就等同于配置了MapperFactoryBean配置。
    MapperFactoryBean又是一个FactoryBean对象,所以最终Ioc容器中存在的对象是该类getObject方法产生的对象

        public MapperFactoryBean(Class<T> mapperInterface) {
            this.mapperInterface = mapperInterface;
        }
    
        public T getObject() throws Exception {
            return this.getSqlSession().getMapper(this.mapperInterface);
        }
    
        public SqlSession getSqlSession() {
            return this.sqlSessionTemplate;
        }
    

    可以看到,最终又来到了mybatis最初的源码了,返回的该接口的代理,也就是每个接口对应的实际类是一个MapperProxy的对象。
    而这里实际上还有个对象我们需要注意,此时Sqlsession不在是DefaultSqlsession,而是SqlSessionTemplate,最终的语句执行也将由这个类执行,而最终又会由sqlSessionProxy的类执行

        public <T> T selectOne(String statement, Object parameter) {
            return this.sqlSessionProxy.selectOne(statement, parameter);
        }
    
        public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
            Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
            Assert.notNull(executorType, "Property 'executorType' is required");
            this.sqlSessionFactory = sqlSessionFactory;
            this.executorType = executorType;
            this.exceptionTranslator = exceptionTranslator;
            this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
        }
    

    该类又是一个代理类SqlSessionInterceptor,所以执行最终又会走到该类的invoke方法

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    
                Object unwrapped;
                try {
                    Object result = method.invoke(sqlSession, args);
                    if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                        sqlSession.commit(true);
                    }
    
                    unwrapped = result;
                } catch (Throwable var11) {
                    unwrapped = ExceptionUtil.unwrapThrowable(var11);
                    if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                        SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                        sqlSession = null;
                        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                        if (translated != null) {
                            unwrapped = translated;
                        }
                    }
    
                    throw (Throwable)unwrapped;
                } finally {
                    if (sqlSession != null) {
                        SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    }
    
                }
    
                return unwrapped;
            }
    

    而就在这里我们发现如果该方法没有开启事务,每次调用改方法都会新建一个SqlSession并且执行完后提交事务,如果开启了事务,Sqlsession就会从TheadLocal中取出,因此,我们可以得出个结论,mybatis整合spring的话,如果没有开启事务,调用我们接口的任意方法都会创建SqlSession,所以就不会存在一级缓存,每次都会去执行Sql,而如果开启了事务,在同一个事务中就会共享同一个SqlSession,此时会存在一级缓存。

    相关文章

      网友评论

          本文标题:mybatis整合spring

          本文链接:https://www.haomeiwen.com/subject/pgufhktx.html