重构Mybatis与Spring集成的SqlSessionFactoryBean(2)

三、代码重构

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");
            }
        }
}

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

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();
    }
}

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);
}

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

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();
    }
}

(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();
}
时间: 2024-10-07 03:10:56

重构Mybatis与Spring集成的SqlSessionFactoryBean(2)的相关文章

重构Mybatis与Spring集成的SqlSessionFactoryBean(上)

一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改.但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试错的心态,二来也的确是有现实需要. 先说明两点: 通常来讲,重构是指不改变功能的情况下优化代码,但本文所说的重构也包括了添加功能 本文使用的主要jar包(版本):spring-*-4.3.3.RELEASE.jar.mybatis-3.4.1.jar.mybatis-spring-1.3.0.jar

Mybatis与Spring集成(易百教程)

整个Mybatis与Spring集成示例要完成的步骤如下: 1.示例功能描述 2.创建工程 3.数据库表结构及数据记录 4.实例对象 5.配置文件 6.测试执行,输出结果 1.示例功能描述 在本示例中,需要完成这样的一个简单功能,即,指定一个用户(ID=1),查询出这个用户的基本信息,并关联查询这个用户的所有订单. 2.创建工程 首先创建一个工程的名称为:mybatis07-spring,在 src 源代码目录下建立文件夹 config,并将原来的 mybatis 配置文件 Configurat

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

Mybatis是java开发者非常熟悉的ORM框架,Spring集成Mybatis更是我们的日常开发姿势. 本篇主要讲Mybatis与Spring集成所做的事情,让读过本文的开发者对Mybatis和Spring的集成过程,有清晰的理解. 以mybatis-spring-2.0.2为例,工程划分六个模块. 一.annotation 模块 定义了@MapperScan和@MapperScans,用于注解方式扫描mapper接口.以及mapper扫描注册器(MapperScannerRegistrar

mybatis与Spring集成(Aop整合PagerAspect插件)

目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

深入浅出MyBatis:MyBatis与Spring集成及实用场景

本系列是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记. 本篇是「深入浅出MyBatis」系列的最后一篇,主要介绍与Spring的集成,以及工作中的一些实用场景. 介绍之前,先整体总结下该系列的内容和写作思路. MyBatis是一个框架,封装了数据库相关的操作,给我们开发人员带来了极大地便利,相对于Hibernate,有很大的灵活性和扩展性,在高并发高性能应用中,这点很重要. 首先介绍了JDBC的规范,了解我们最原始最熟悉的操作数据库的方式,MyBatis就是在此基础上进行封装和抽象.

MyBatis与Spring集成

beans.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" xmlns:aop="http://www.springframework.o

Java Persistence with MyBatis 3(中文版) 第五章 与Spring集成

MyBatis-Spring是MyBatis框架的子模块,用来提供与当前流行的依赖注入框架Spring的无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程(Aspect Oriented Programming,AOP)的Java框架,鼓励使用基于POJO的编程模型.另外,Spring提供了声明式和编程式的事务管理能力,可以很大程度上简化应用程序的数据访问层(data access layer)的实现.在本章中,我们将看到在基于Spring的

Spring集成MyBatis框架

Java在写数据库查询时,我接触过四种方式: 1.纯Java代码,引用对应的数据库驱动包,自己写连接与释放逻辑(可以用连接池) 这种模式实际上性能是非常不错的,但是使用起来并不是非常方便:一是要手工为Connection做获取与释放,大量的冗余代码也容易出错:另一个是,复杂的SQL用字符串写起来简直不可维护(换行.可视长度.参数都是问题). 2.使用Spring JdbcTemplate 这个其实还是挺不错的,配置比较简单,功能丰富上比手工管理Connection要舒服多了,而且代码也比较简洁.

Unit08: Spring集成mybatis

Unit08: Spring集成mybatis 1. Spring集成mybatis (1)方式一 step1. 导包. spring-webmvc,mybatis,mybatis-spring, ojdbc,dbcp,spring-jdbc,junit. step2. 添加spring的配置文件. 注:集成之后,不再需要mybatis的配置文件了,之前的配置信息 用一个bean(SqlSessionFactoryBean)来代替. step3.实体类. step4.映射文件. step5.Ma