Integration of Spring Source Parsing Mybatis

https://blog.csdn.net/heroqiang/article/details/79135500

  • Integration package version: 1.3.1
  • The use of /*/ annotation in the article will provide in-depth analysis and in-depth analysis.

text

First, let's look at Spring's configuration for integrating Mybatis:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="com.xxx.entity"/>
    <property name="mapperLocations" value="classpath:mapper/* .xml"/>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xxx.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Let's start with configuration analysis. First, let's look at the hierarchy of SqlSession FactoryBean.

We find two interfaces that we care about, FactoryBean and InitializingBean. We know that beans that implement FactoryBean call its getObject method to create beans, beans that implement InitializingBean call its afterPropertiesSet method after property filling is completed, and we will analyze this side. Law:
SqlSessionFactoryBean:

public void afterPropertiesSet() throws Exception {
    // Attribute Check
    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");
    /* Building SqlSessionFactory */
    this.sqlSessionFactory = buildSqlSessionFactory();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

SqlSessionFactoryBean:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder = null;
    // Several Configuration Configurations
    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);
        }
    }
    // objectFactory configuration
    if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
    }
    // objectWrapperFactory configuration
    if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }
    // vfs configuration
    if (this.vfs != null) {
        configuration.setVfsImpl(this.vfs);
    }
    // TypeeAliases Package configuration
    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");
            }
        }
    }
    // TypeeAliases configuration
    if (!isEmpty(this.typeAliases)) {
        for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type alias: '" + typeAlias + "'");
            }
        }
    }
    // plugins configuration
    if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered plugin: '" + plugin + "'");
            }
        }
    }
    // TypeeHandlers Package configuration
    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");
            }
        }
    }
    // TypeeHandlers configuration
    if (!isEmpty(this.typeHandlers)) {
        for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type handler: '" + typeHandler + "'");
            }
        }
    }
    // Database IdProvider configuration
    if (this.databaseIdProvider != null) {
        try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException e) {
            throw new NestedIOException("Failed getting a databaseId", e);
        }
    }
    // cache configuration
    if (this.cache != null) {
        configuration.addCache(this.cache);
    }
    if (xmlConfigBuilder != null) {
        try {
            // If configLocation is configured, the configuration file is parsed
            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) {
        // If there is no transactionFactory configuration, apply Spring Managed TransactionFactory
        this.transactionFactory = new SpringManagedTransactionFactory();
    }
    // Setting up the environment for Configuration
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    // mapperLocations configuration
    if (!isEmpty(this.mapperLocations)) {
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }
        <span class="token keyword">try</span> <span class="token punctuation">{</span>
            <span class="token comment">// Analytical mapper configuration</span>
            XMLMapperBuilder xmlMapperBuilder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XMLMapperBuilder</span><span class="token punctuation">(</span>mapperLocation<span class="token punctuation">.</span><span class="token function">getInputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                configuration<span class="token punctuation">,</span> mapperLocation<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> configuration<span class="token punctuation">.</span><span class="token function">getSqlFragments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            xmlMapperBuilder<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NestedIOException</span><span class="token punctuation">(</span><span class="token string">"Failed to parse mapping resource: '"</span> <span class="token operator">+</span> mapperLocation <span class="token operator">+</span> <span class="token string">"'"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
            ErrorContext<span class="token punctuation">.</span><span class="token function">instance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">reset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>LOGGER<span class="token punctuation">.</span><span class="token function">isDebugEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            LOGGER<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Parsed mapper file: '"</span> <span class="token operator">+</span> mapperLocation <span class="token operator">+</span> <span class="token string">"'"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>LOGGER<span class="token punctuation">.</span><span class="token function">isDebugEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        LOGGER<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Property 'mapperLocations' was not specified or no matching resources found"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// Building SqlSessionFactory</span>
<span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactoryBuilder<span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span>configuration<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146

Students familiar with Mybatis must easily understand this method, that is, to set up and analyze various configurations of Mybatis and build SqlSession Factory. Those who are not familiar with Mybatis, please see my article on Mybatis source code parsing. One step in the method is to configure Mybatis's transactionFactory as Spring ManagedTransactionFactory by default if the user does not configure the transactionFactory. When we analyze Mybatis source code, we see that Mybatis uses ManagedTransactionFactory as a transactionFactory when the user does not specify the transactionFactory configuration. The default Transaction Factory, Mybatis, when creating SqlSession, needs to add an Executor executor for it. The Transaction object needed to build an Executor executor is created through the new Transaction method of TransactionFactory, and the getConnection method of Transaction is used when subsequent Executors execute sql commands. Get the database connection. What is the role of Spring Managed Transaction Factory added here? We can think about that Spring needs to get database connection when it opens a transaction and Mybatis also needs to get database connection when it executes. If Spring's transaction is to be used in conjunction with Mybatis's database operation during a call, they are sure in the process of this call. To share the same database connection, otherwise the transaction cannot take effect, Spring Managed Transaction Factory can solve the problem of shared database connection. Let's analyze the process:

public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
}
  • 1
  • 2
  • 3

SpringManagedTransactionFactory Of newTransaction Method returns SpringManagedTransaction,therefore Executor When a database connection is acquired, it is invokedSpringManagedTransactionOf getConnection Method:

public Connection getConnection() throws SQLException {
    if (this.connection == null) {
        openConnection(); /* Open database connection */
    }
    return this.connection;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

SpringManagedTransaction:

private void openConnection() throws SQLException {
    /* Get the database connection */
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug(
            "JDBC Connection ["
            + this.connection
            + "] will"
            + (this.isConnectionTransactional ? " " : " not ")
            + "be managed by Spring");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

DataSourceUtils:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
    try {
        /* Get the database connection */
        return doGetConnection(dataSource);
    }
    catch (SQLException ex) {
        throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

DataSourceUtils:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");
    // Get the bound ConnectionHolder from the current thread
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    // If ConnectionHolder is not null and holds database connections or synchronizes with transactions
    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
        // Number of ConnectionHolder requests + 1
        conHolder.requested();
        // If ConnectionHolder does not hold a database connection, get the database connection and put it in ConnectionHolder
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(dataSource.getConnection());
        }
        // Return to the database connection
        return conHolder.getConnection();
    }
    logger.debug("Fetching JDBC Connection from DataSource");
    // The current thread does not have a Connection Holder to get a database connection
    Connection con = dataSource.getConnection();
    // Determine whether the transaction synchronization of the current thread is active
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        logger.debug("Registering transaction synchronization for JDBC Connection");
        // Using the same connection in a transaction to further JDBC operations, thread-bound objects are deleted synchronously when the transaction is completed
        ConnectionHolder holderToUse = conHolder;
        if (holderToUse == null) {
            // ConnectionHolder creates a new ConnectionHolder for null
            holderToUse = new ConnectionHolder(con);
        }
        else {
            // Put the database connection into Connection Holder if it is not null
            holderToUse.setConnection(con);
        }
        holderToUse.requested(); // Number of ConnectionHolder requests + 1
        // Register a new transaction synchronization for the current thread
        TransactionSynchronizationManager.registerSynchronization(
            new ConnectionSynchronization(holderToUse, dataSource));
        // Mark SqlSession Holder as transaction synchronization
        holderToUse.setSynchronizedWithTransaction(true);
        if (holderToUse != conHolder) {
            // If it is a new ConnectionHolder, it is bound to the current thread
            TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
        }
    }
    return con;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

Here we see the Connection Holder, Transaction Synchronization Manager class, which we have seen when analyzing Spring transaction source code, is used to manage resource and transaction synchronization for each thread by calling the getResource method of Transaction Synchronization Manager, which maintains a lot of Th internally. ReadLocal variable to save some thread-related resources. In this method, we find that there is a process of registering transaction synchronization. What is the purpose of transaction synchronization? When we analyze Spring transaction source code, we mentioned that the related methods of transaction synchronization will be activated when Spring transaction rollback, commit, hang and so on. The main function of transaction synchronization added here is to set the number of requests of ConnectionHolder to 0 and some attributes to 0 after Spring transaction commit, rollback and other operations. Initial state, putting database connection back into connection pool, releasing database connection, etc.

Let's analyze another bean, MapperScanner Configurer, which scans user-configured packages to automatically register MapperFactoryBean, so that we don't need to register mapper manually. Let's first look at its hierarchy:

We found that it also implements InitializingBean, but its implementation of the afterPropertiesSet method only checks whether the basePackage attribute is null. Let's look at another interface that it implements, BeanDefinition Registry PostProcessor, which we introduced when we analyzed Spring context initialization source code to add or change BeanDefinition before creating bean s. An example of how it works is MapperScanner Configurer. To see the implementation of relevant methods:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        /* Processing properties configuration */
        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(); /* Registration filter */
    /* scanning */
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Why do I give you the option to handle the properties configuration here? Is the property configuration not done by the Property Placeholder Configurer bean? (If the reader is not familiar with Property Placeholder Configurer, you can go to any project using Spring to see the configuration of properties. Whether it is directly configuring Property Placeholder Configurer or using the <context: property-placeholder/> tag, it is related to Property Placeholder Configurer.) Translate the annotation of the processPropertyPlaceHolders method to get the answer: "BeanDefinition Registries will be invoked at application startup and earlier than BeanFactoryPostProcessors, which means that PropertyResourceConfigurers have not been loaded yet, so any reference to the property file will be invalid. To avoid this, this method manually finds the defined Property Resource Configurers and calls them in advance to ensure that the reference to the property works properly.
MapperScannerConfigurer:

private void processPropertyPlaceHolders() {
    Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
    if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
        BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)
        .getBeanFactory().getBeanDefinition(beanName);
        // Property Resource Configurer does not expose any method of explicitly performing attribute placeholder substitution, instead creating a BeanFactory that contains only the current mapper scanner and the post-processing factory
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        factory.registerBeanDefinition(beanName, mapperScannerBean);
        for (PropertyResourceConfigurer prc : prcs.values()) {
            // Load properties by executing the postProcessBeanFactory method of PropertyResourceConfigurer ahead of time
            prc.postProcessBeanFactory(factory);
        }
        PropertyValues values = mapperScannerBean.getPropertyValues();
        // Update attributes that need to be replaced
        this.basePackage = updatePropertyValue("basePackage", values);
        this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
        this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

ClassPathMapperScanner:

public void registerFilters() {
    boolean acceptAllInterfaces = true;
    // If annotation Class configuration is specified, annotation type filters are added, using the given annotation and/or markup interfaces
    if (this.annotationClass != null) {
        addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        acceptAllInterfaces = false;
    }
    // If the markerInterface configuration is specified, a filter assignable to a given type is added, rewriting the Assignable Type Filter ignores matches on the actual marker interface.
    if (this.markerInterface != null) {
        addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
            @Override
            protected boolean matchClassName(String className) {
                return false;
            }
        });
        acceptAllInterfaces = false;
    }
    // If the above two are not configured, add filters that accept all interfaces
    if (acceptAllInterfaces) {
        addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
    }
    // Add filters to filter out package-info.java
    addExcludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            String className = metadataReader.getClassMetadata().getClassName();
            return className.endsWith("package-info");
        }
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

ClassPathScanningCandidateComponentProvider:

public void addIncludeFilter(TypeFilter includeFilter) {
    this.includeFilters.add(includeFilter);
}
  • 1
  • 2
  • 3

ClassPathScanningCandidateComponentProvider:

public void addExcludeFilter(TypeFilter excludeFilter) {
    this.excludeFilters.add(0, excludeFilter);
}
  • 1
  • 2
  • 3

Adding inclusion and exclusion filters is adding related filters to the corresponding set, and the use of these filters will be introduced later. After the relevant property settings are completed, you can start scanning the files:
ClassPathBeanDefinitionScanner:

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    doScan(basePackages); /* Packet Scanning */
    if (this.includeAnnotationConfig) {
        // Register Annotation Processor
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

ClassPathMapperScanner:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // Scanning by calling the doScan method of the parent class
    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 {
        /* Processing scanned BeanDefinition */
        processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

In fact, we have analyzed the scanning process when we analyzed the source code of Spring annotation scanning. The scanning process here is also a reuse process. The filter used in the process is the filter registered above. This process is no longer repeated here. Here is a link to the article. Readers can review this process. Each process, Portal This is the case. Let's analyze the processing of the scanned BeanDefinition:
ClassPathMapperScanner:

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 '" + definiti..                                           on.getBeanClassName() + "' mapperInterface");
        }
        // The interface of mapper is the original class of the bean, but the actual bean class is MapperFactoryBean.
          definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
          definition.setBeanClass(this.mapperFactoryBean.getClass());
          definition.getPropertyValues().add("addToConfig", this.addToConfig);
          boolean explicitFactoryUsed = false;
          // Here are some additional attributes
          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);
          }
      }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

We see that here we set BeanClass to MapperFactoryBean for the scanned BeanDefinition. Let's look at the hierarchy of this class:

We find that it also implements InitializingBean and FactoryBean. We analyze the related implementation methods:
DaoSupport:

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    checkDaoConfig(); /* Check the dao configuration */
    try {
        initDao(); // Subclass extension initializes dao, default null implementation
    }
    catch (Exception ex) {
        throw new BeanInitializationException("Initialization of DAO failed", ex);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

MapperFactoryBean:

protected void checkDaoConfig() {
    super.checkDaoConfig(); /* Calling parent class methods for dao configuration checking */
    // Check that the mapper interface cannot be null
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            // If Mybatis Configuration configuration does not have the current mapper, add
            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();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

SqlSessionDaoSupport:

protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
  • 1
  • 2
  • 3

It is judged that the sqlSession attribute can not be null. When we analyzed the process of processing the scanned BeanDefinition in the previous section, we did not see the addition of sqlSession attribute to the mapper's BeanDefinition. Should this attribute be null after the creation of MapperFactoryBean? Isn't it impossible to pass the assertion here? As we know, SqlSession was created through SqlSession Factory, and the sqlSession Factory attribute was added to the scanned Bean Definition when it was processed, so we tried to find the answer from the setter method of sqlSession Factory:
SqlSessionDaoSupport:

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
        /* Create SqlSession Template */
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Sure enough, the assignment of the sqlSession attribute is here.
SqlSessionTemplate:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    /* Call overload construction method */
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
  • 1
  • 2
  • 3
  • 4

SqlSessionTemplate:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    /* Call overload construction method */
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(
            sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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;
    // Create sqlSession proxy
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

We found that a sqlSession agent was created here. What's the use of it? We know that we can use SqlSession to execute mapper, or we can use it to obtain mapper. SqlSession Template implements SqlSession and implements related methods. The functions of the methods are delegated to sqlSession Proxy. eg:
SqlSessionTemplate:

public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
  • 1
  • 2
  • 3

The Invocation Handler role of dynamic proxy is SqlSession Interceptor. Let's look at its invoke method:
SqlSessionInterceptor:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    /* Get sqlSession */
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
        // The corresponding method of executing sqlSession
        Object result = method.invoke(sqlSession, args);
        // If sqlSession is not managed by Spring, submit sqlSession
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            // Force submission of sqlSession because some databases need to commit / rollback before calling the close method.
            sqlSession.commit(true);
        }
        return result;
    } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);  // Close SqlSession
            sqlSession = null;
            Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
            if (translated != null) {
                unwrapped = translated;
            }
        }
        throw unwrapped;
    } finally {
        if (sqlSession != null) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); // Close SqlSession
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

SqlSessionUtils:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    // Get the SqlSession Holder for the current thread binding
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    /* Getting SqlSession from SqlSession Holder */
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session; // The current thread exists and returns directly
    }
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Creating a new SqlSession");
    }
    // No new SqlSession was obtained through SqlSession Factory
    session = sessionFactory.openSession(executorType);
    /* Bind SessionHolder to the current thread */
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

SqlSessionUtils:

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
    SqlSession session = null;
    // If SqlSession Holder is not null and synchronizes with transactions
    if (holder != null && holder.isSynchronizedWithTransaction()) {
        if (holder.getExecutorType() != executorType) {
            throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
        }
        // Number of SqlSession Holder requests + 1
        holder.requested();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
        }
        // Get SqlSession return
        session = holder.getSqlSession();
    }
    return session;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

SqlSessionUtils:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    // Determine whether the transaction synchronization of the current thread is active
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        Environment environment = sessionFactory.getConfiguration().getEnvironment();
        // When creating SqlSession Factory above, it was judged that if there is no transactionFactory configuration, Spring ManagedTransactionFactory should be applied
        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
            }
            // Create SqlSession Holder to hold SqlSession
            holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
            // Binding to the current thread
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
            // Register a new transaction synchronization for the current thread
            TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
            // Mark SqlSession Holder as transaction synchronization
            holder.setSynchronizedWithTransaction(true);
            // Number of SqlSession Holder requests + 1
            holder.requested();
        } else {
            if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
                }
            } else {
                throw new TransientDataAccessResourceException(
                    "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
            }
        }
    } else {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

The role of transaction synchronization added here is mainly to call SqlSession commit, close and other methods to close and submit the Spring transaction after committing and rollback operations.

Let's continue to analyze MapperFactoryBean's implementation of the FactoryBean interface getObject method, that is, the method of creating bean s:

public T getObject() throws Exception {
    /* Get mapper */
    return getSqlSession().getMapper(this.mapperInterface);
}
  • 1
  • 2
  • 3
  • 4

The getSqlSession method here returns the SqlSession Template that we analyzed above.
SqlSessionTemplate:

public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}
  • 1
  • 2
  • 3

We should be familiar with the content of this method. When analyzing Mybatis source code, we have seen that calling the getMapper method of Mybatis Configuration object to get mapper. Unfamiliar students can read the author's article on Mybatis source code analysis, which has a very detailed description, as well as the source of mapper execution process. Code analysis. At this point, the whole Spring integration Mybatis source code analysis is completed.

                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-e44c3c0e64.css" rel="stylesheet">
                </div>

Keywords: Spring Mybatis Database Session

Added by scott212 on Sat, 20 Jul 2019 17:03:19 +0300