Mybatis-Spring:SqlSessionFactoryBean

环境:mybatis-spring 2.0.3

可以通过如下方式向在Spring中集成Mybatis,通过向Spring注册SqlSessionFactoryBean@MapperScan启用Mybatis的功能

@Configuration
@MapperScan("com.holybell.mybatis.mapper") // Mapper接口扫描路径
public class Config {
    /**
     * 通过SqlSessionFactoryBean获得SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }

    @Bean
    public DataSource dataSource() {
        // ... 省略创建一个DataSource
    }
}

Mybatis利用Spring提供的BeanFactory接口,实现了SqlSessionFactoryBean,简单来说它就是一个构建SqlSessionFactory的工厂类,虽然注册的是SqlSessionFactoryBean,但是它生产的对象也会注册到Spring容器中

public class SqlSessionFactoryBean 
        implements FactoryBean<SqlSessionFactory>, InitializingBean, 
                   ApplicationListener<ApplicationEvent> {

    // spring的资源加载器
    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER 
                                                    = new PathMatchingResourcePatternResolver();
    // spring的类注解解析器
    private static final MetadataReaderFactory METADATA_READER_FACTORY 
                                                    = new CachingMetadataReaderFactory();

    private SqlSessionFactory sqlSessionFactory;

    /**
     * FactoryBean向Spring容器注册之后,
     * Spring会回调这个方法获得它生产的bean
     */
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            afterPropertiesSet();    
        }
        return this.sqlSessionFactory;
    }
    /**
     * Spring会在bean初始化完成之后回调这个方法
     */
    public void afterPropertiesSet() throws Exception {
        // 省略Assert校验...
        this.sqlSessionFactory = buildSqlSessionFactory();
    }
    /**
     * 构造Mybatis的SqlSessionFactory
     */
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
       final Configuration targetConfiguration;

        // 省略其他属性的装配

        // 扫描路径,向mybatis注册TypeHandler
        if (hasLength(this.typeHandlersPackage)) {
            scanClasses(this.typeHandlersPackage, 
                    TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
                    .filter(clazz -> !clazz.isInterface())
                    .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
                    .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
        }

        // 如果注册SqlSessionFactoryBean的时候配置了Mapper文件地址
        // 那么就会去解析Mapper文件,生成Configruation中的各种配置信息,如MapperStatement
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }

            try {
                // 这里就和不集成Spring的Mybatis操作一样
                // 解析Mapper文件的配置,生产MappedStatement等信息
                XMLMapperBuilder xmlMapperBuilder 
                        = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                configuration, mapperLocation.toString(), 
                                configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                // ...省略异常
            } finally {
              ErrorContext.instance().reset();
            }
        }
        // ...
        return this.sqlSessionFactoryBuilder.build(configuration);
    }
    /**
     * 从指定的路径packagePatterns加载指定assignableType注解的类
     */
    private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType)
          throws IOException {

        Set<Class<?>> classes = new HashSet<>();
        String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packagePattern : packagePatternArray) {
            // 加载类资源,每个Resource对象表示一个被加载的文件资源,此处为class文件
            Resource[] resources = RESOURCE_PATTERN_RESOLVER
                    .getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                            + ClassUtils.convertClassNameToResourcePath(packagePattern) 
                            + "/**/*.class");
            // 遍历每个加载到的Class文件,解析注解信息
            for (Resource resource : resources) {
                try {
                    // 解析类的注解信息
                    ClassMetadata classMetadata = METADATA_READER_FACTORY
                            .getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
                        // 符合条件的类加入集合返回
                        classes.add(clazz);
                    }
                } catch (Throwable e) {
                    // ... 省略异常日志
                }
            }
        }
        return classes;
    }
}

简而言之,Mybatis借助SqlSessionFactoryBean完成了在不集成Spring的情况下SqlSessionFactory的创建,同时可以借助这个类装配InterceptorTypeHandler等组件

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容