重构Mybatis与Spring集成的SqlSessionFactoryBean

Posted 归去来兮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重构Mybatis与Spring集成的SqlSessionFactoryBean相关的知识,希望对你有一定的参考价值。

三、代码重构

1、先使用Eclipse把buildSqlSessionFactory()方法中众多的if换成小函数

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();
            configuration.setVariables(this.configurationProperties);
        }

        doResolveObjectFactory(configuration);
        doResolveObjectWrapperFactory(configuration);
        doResolveVfs(configuration);
        doResolveTypeAliasesPackage(configuration);
        doResolveTypeAliases(configuration);
        doResolvePlugins(configuration);
        doResolveTypeHandlersPackage(configuration);
        doResolveTypeHandlers(configuration);
        doResolveDatabaseIdProvider(configuration);
        doResolveCache(configuration);
        doParseConfig(xmlConfigBuilder);
        doResolveTransactionFactory();
        doResolveEnvironment(configuration);
        doParseSqlMapper(configuration);

        return this.sqlSessionFactoryBuilder.build(configuration);
}

说明一下:

  • 这里的重构全部使用Eclipse完成,操作步骤是选定需要重构的代码,右键选择Refactor—>Extract Method,然后输入新的方法名,点击OK完成
  • 新方法名规则:全部使用do开头,表示实际做某件事,对于解析XML的,使用doParse(如doParseConfig、doParseSqlMapper),其它的则使用doResolve为前缀
  • 新方法一开始全部为private,但是为了后续扩展性,可以根据需要修改为protected
  • 第一段的if语句,由于有两个变量需要返回,直接使用Eclipse重构不成功,先保持不变

看其中一个重构提取的方法:

protected void doParseSqlMapper(Configuration configuration) throws NestedIOException {
        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");
            }
        }
}
View Code

这里还可以再次实施重构:

protected void doParseSqlMapper(Configuration configuration) throws NestedIOException {
    if (!isEmpty(this.mapperLocations)) {
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }
            doParserSqlMapperResource(configuration, mapperLocation);
            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");
        }
    }
}

protected void doParserSqlMapperResource(Configuration configuration, Resource mapperLocation)
        throws NestedIOException {
    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();
    }
}
View Code

2、对于第一段的if语句,添加一个内部类,用来包装两个变量,然后再重构,相关代码如下:

private class ConfigurationWrapper{//定义一个内部类,包装两个变量
    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder;
}

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
    ConfigurationWrapper wrapper = doGetConfigurationWrapper();//将获取变量的代码重构为单独一个方法
    Configuration configuration = wrapper.configuration;//从内部包装对象中获取需要的变量
    XMLConfigBuilder xmlConfigBuilder = wrapper.xmlConfigBuilder;

    doResolveObjectFactory(configuration);
    doResolveObjectWrapperFactory(configuration);
    doResolveVfs(configuration);
    doResolveTypeAliasesPackage(configuration);
    doResolveTypeAliases(configuration);
    doResolvePlugins(configuration);
    doResolveTypeHandlersPackage(configuration);
    doResolveTypeHandlers(configuration);
    doResolveDatabaseIdProvider(configuration);
    doResolveCache(configuration);
    doParseConfig(xmlConfigBuilder);
    doResolveTransactionFactory();
    doResolveEnvironment(configuration);
    doParseSqlMapper(configuration);

    return this.sqlSessionFactoryBuilder.build(configuration);
}

protected ConfigurationWrapper doGetConfigurationWrapper() throws IOException {
    ConfigurationWrapper wrapper = new ConfigurationWrapper();
    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();
        configuration.setVariables(this.configurationProperties);
    }
    wrapper.configuration = configuration;
    wrapper.xmlConfigBuilder = xmlConfigBuilder;
    return wrapper;
}

这里的新方法由于需要返回值,将其命名为doGetConfigurationWrapper。

3、再预留一些方法,给子类覆盖留下空间

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
    ConfigurationWrapper wrapper = doGetConfigurationWrapper();//将获取变量的代码重构为单独一个方法
    Configuration configuration = wrapper.configuration;//从内部包装对象中获取需要的变量
    XMLConfigBuilder xmlConfigBuilder = wrapper.xmlConfigBuilder;

    onBeforeConfigurationPropertiesSet(configuration);//设置Configuration对象属性之前
    
    doResolveObjectFactory(configuration);
    doResolveObjectWrapperFactory(configuration);
    doResolveVfs(configuration);
    doResolveTypeAliasesPackage(configuration);
    doResolveTypeAliases(configuration);
    doResolvePlugins(configuration);
    doResolveTypeHandlersPackage(configuration);
    doResolveTypeHandlers(configuration);
    doResolveDatabaseIdProvider(configuration);
    doResolveCache(configuration);
    
    onBeforeParseConfig(configuration);//解析mybatis-config.xml配置之前
    doParseConfig(xmlConfigBuilder);
    doResolveTransactionFactory();
    doResolveEnvironment(configuration);
    
    onBeforeParseSqlMapper(configuration);//解析sql-mapper.xml脚本配置之前
    doParseSqlMapper(configuration);
    
    doCustomConfiguration(configuration);//其它个性化配置
    
    return this.sqlSessionFactoryBuilder.build(configuration);
}

protected void doCustomConfiguration(Configuration configuration){//其它个性化配置,用于子类覆盖
        
}

这里设置多少桩是就见仁见智了,一般来说,在每个相对独立的任务前后添加一些事件即可。

另外,也可以将这些桩方法提取为一个接口,然后在Spring中注入这个接口的一个或多个实现类,相关代码如下:

public interface ISqlSessionFactoryDecorate{
    void onBeforeConfigurationPropertiesSet(Configuration configuration);
    void onBeforeParseConfig(Configuration configuration);
    void onBeforeParseSqlMapper(Configuration configuration);
    void doCustomConfiguration(Configuration configuration);
}

private Set<ISqlSessionFactoryDecorate> decorates;

public Set<ISqlSessionFactoryDecorate> getDecorates() {
    return decorates;
}

public void setDecorates(Set<ISqlSessionFactoryDecorate> decorates) {
    this.decorates = decorates;
}

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
    ConfigurationWrapper wrapper = doGetConfigurationWrapper();//将获取变量的代码重构为单独一个方法
    Configuration configuration = wrapper.configuration;//从内部包装对象中获取需要的变量
    XMLConfigBuilder xmlConfigBuilder = wrapper.xmlConfigBuilder;
    
    Set<ISqlSessionFactoryDecorate> decorates = getDecorates();
    boolean hasDecorates = null != decorates && !decorates.isEmpty();
    
    if(hasDecorates){
        for(ISqlSessionFactoryDecorate decorate : decorates){
            decorate.onBeforeConfigurationPropertiesSet(configuration);//设置Configuration对象属性之前
        }
    }
    
    doResolveObjectFactory(configuration);
    doResolveObjectWrapperFactory(configuration);
    doResolveVfs(configuration);
    doResolveTypeAliasesPackage(configuration);
    doResolveTypeAliases(configuration);
    doResolvePlugins(configuration);
    doResolveTypeHandlersPackage(configuration);
    doResolveTypeHandlers(configuration);
    doResolveDatabaseIdProvider(configuration);
    doResolveCache(configuration);
    
    if(hasDecorates){
        for(ISqlSessionFactoryDecorate decorate : decorates){
            decorate.onBeforeParseConfig(configuration);//解析mybatis-config.xml配置之前
        }
    }
    
    doParseConfig(xmlConfigBuilder);
    doResolveTransactionFactory();
    doResolveEnvironment(configuration);
    
    if(hasDecorates){
        for(ISqlSessionFactoryDecorate decorate : decorates){
            decorate.onBeforeParseSqlMapper(configuration);//解析sql-mapper.xml脚本配置之前
        }
    }
    
    doParseSqlMapper(configuration);
    
    if(hasDecorates){
        for(ISqlSessionFactoryDecorate decorate : decorates){
            decorate.doCustomConfiguration(configuration);//其它个性化配置
        }
    }
    return this.sqlSessionFactoryBuilder.build(configuration);
}
View Code

这两种方式各有优缺点,使用子类的方式需要继承,但是可以访问父类中的一些属性和方法,而使用接口的方式,代码相对独立,逻辑比较清晰,可以实现多个不同逻辑的扩展,但是不能直接访问原有类中的属性,具体使用哪种方式,需要视情况而定。

4、添加一些获取方法

刚刚说使用子类的方式有一个优势就是可以直接访问父类中的属性和方法,但这只限于是protected和public级别的。我们看看SqlSessionFactoryBean这个类的OutLine:

可以看到,除了databaseIdProvider、vfs、cache这几个属性有get方法之外,其它的属性都是private并且没有提供get方法的。Mybatis为什么只给这三个属性提供get方法?当然可以解释为外界只需要访问这三个属性,然而在我看来,真正的原因其实是mybatis编码的随意性,起码到目前为止,我根本不需要访问这三个属性,而configuration、dataSource、transactionFactory、objectFactory等属性却是需要访问的,如果不做任何变更,那就只能通过反射的方式获取了,但是这里我们的目的就是重构,那不妨添加这几个属性的get方法。

5、添加组件工厂

再看源码,可以看到有很多地方直接使用new创建对象,把这些对象的创建提取出来,添加新的接口ISqlSessionComponentFactory,并编写默认实现类:

(1)接口

public interface ISqlSessionComponentFactory {

    public SqlSessionFactoryBuilder newSqlSessionFactoryBuilder();
    
    public XMLConfigBuilder newXMLConfigBuilder(InputStream inputStream, String environment, Properties props);
    
    public XMLMapperBuilder newXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments);
    
    public Configuration newConfiguration();
    
    public Environment newEnvironment(String id, TransactionFactory transactionFactory, DataSource dataSource);
    
    public TransactionFactory newTransactionFactory();
}

(2)默认实现

public class DefaultSqlSessionComponentFactory implements ISqlSessionComponentFactory{

    @Override
    public SqlSessionFactoryBuilder newSqlSessionFactoryBuilder() {
        return new SqlSessionFactoryBuilder();
    }

    @Override
    public XMLConfigBuilder newXMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        return new XMLConfigBuilder(inputStream, environment, props);
    }

    @Override
    public XMLMapperBuilder newXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
            Map<String, XNode> sqlFragments) {
        return new XMLMapperBuilder(inputStream, configuration, resource, sqlFragments);
    }

    @Override
    public Configuration newConfiguration() {
        return new Configuration();
    }

    @Override
    public Environment newEnvironment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
        return new Environment(id, transactionFactory, dataSource);
    }

    @Override
    public TransactionFactory newTransactionFactory() {
        return new SpringManagedTransactionFactory();
    }
}
View Code

(3)工厂支持类/静态帮助类

组件工厂可能会用于多个地方,因此可以添加一个Support类,可注入工厂实现类,然后其它应用继承这个Support类;也可以添加一个静态帮助类:

public class SqlSessionComponetFactorys {

    private static ISqlSessionComponentFactory factory = new DefaultSqlSessionComponentFactory();

    public static ISqlSessionComponentFactory getFactory() {
        return factory;
    }

    // 这里没有设置为static方法,主要是便于在Spring配置文件中注入新的工厂接口实现类
    public void setFactory(ISqlSessionComponentFactory factory) {
        if(null != factory){
            SqlSessionComponetFactorys.factory = factory;
        }
    }
    
    public static SqlSessionFactoryBuilder newSqlSessionFactoryBuilder() {
        return factory.newSqlSessionFactoryBuilder();
    }
    
    // 省略其它的方法
}

(4)应用,替换原来的new Xxx(),修改为SqlSessionComponetFactorys.newXxx()。

这里需要注意一点,看下面的情形:

private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

这种直接在类定义时就赋值的组件(需要初始化的类属性),因为SqlSessionComponetFactorys中的工厂接口可能在Spring启动时修改,因此不能简单的替换,而应采用延迟创建的方式,比如修改成如下形式:

private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;

@Override
public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property \'dataSource\' is required");
    if(null == this.sqlSessionFactoryBuilder){
        this.sqlSessionFactoryBuilder = SqlSessionComponetFactorys.newSqlSessionFactoryBuilder();
    }
    //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();
}

最终,SqlSessionFactoryBean重构成如下结构:

上面的数字即对应重构步骤中的操作,不包括最后一步的提取组件工厂。

以上是关于重构Mybatis与Spring集成的SqlSessionFactoryBean的主要内容,如果未能解决你的问题,请参考以下文章

动力节点Spring框架学习笔记-王鹤spring整合MyBatis

Mybatis与Spring集成时都做了什么?

spring与mybatis集成

spring与mybatis集成

mybatis与spring集成

Mybatis与Spring集成