奇怪的问题-注入服务后DataSource发生了变化

时间:2019-06-11 23:40:21

标签: java spring-boot dependency-injection mybatis

这真的让我感到困惑。我有一个Spring Boot(2.1.2)应用程序,通过MyBatis和Spring管理两个数据源。我有多个MyBatis映射器,每个映射器都配置为使用特定的数据源。该配置的代码如下:

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class MyBatisConfig {

    private static final String POSTGRES_SESSION_FACTORY = "postgresSessionFactory";
    private static final String MYSQL_SESSION_FACTORY = "mySqlDbSessionFactory";

    @Bean(name = POSTGRES_SESSION_FACTORY, destroyMethod = "")
    @Primary
    public SqlSessionFactoryBean postgresSessionFactory(
            @Qualifier(DataSourceConfig.PRIMARY_DATA_SOURCE) final DataSource oneDataSource,
            ApplicationContext applicationContext) throws Exception {
        final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
        sqlSessionFactoryBean.setDataSource(oneDataSource);
        SqlSessionFactory sqlSessionFactory;
        sqlSessionFactory = sqlSessionFactoryBean.getObject();
        sqlSessionFactory.getConfiguration().addMapper(Mapper1.class);
        sqlSessionFactory.getConfiguration().addMapper(Mapper2.class);

        return sqlSessionFactoryBean;
    }

    @Bean(name = MYSQL_SESSION_FACTORY, destroyMethod = "")
    public SqlSessionFactoryBean mySqlSessionFactory(
            @Qualifier(DataSourceConfig.SECONDARY_DATA_SOURCE) final DataSource anotherDataSource,
            ApplicationContext applicationContext) throws Exception {
        final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis-config-sql.xml"));
        sqlSessionFactoryBean.setDataSource(anotherDataSource);
        final SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
        sqlSessionFactory.getConfiguration().addMapper(Mapper3.class);
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperFactoryBean<Mapper1> accountMapperFactory(@Qualifier(POSTGRES_SESSION_FACTORY) final SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        MapperFactoryBean<Mapper1> factoryBean = new MapperFactoryBean<>(Mapper1.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactoryBean.getObject());
        return factoryBean;
    }

    @Bean
    public MapperFactoryBean<Mapper2> domainMapperFactory(@Qualifier(POSTGRES_SESSION_FACTORY) final SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        MapperFactoryBean<Mapper2> factoryBean = new MapperFactoryBean<>(Mapper2.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactoryBean.getObject());
        return factoryBean;
    }

    @Bean
    public MapperFactoryBean<Mapper3> usageMapperFactory(@Qualifier(MYSQL_SESSION_FACTORY) final SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        MapperFactoryBean<Mapper3> factoryBean = new MapperFactoryBean<>(Mapper3.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactoryBean.getObject());
        return factoryBean;
    }
}

如果使用调试器,则可以在初始化这些bean时将它们全部指向正确的数据源。 (Mapper1和Mapper2的SqlSessionFactorys连接到postgres数据源,Mapper3的SqlSessionFactory连接到mysql数据源。

但是奇怪的是,当它们被注入到服务中时,所有三个Mappers都连接到了postgres数据源。在这一点上,我感到很困惑。

服务和注入非常简单:

@Autowired private Mapper1 mapper1;
@Autowired private Mapper2 mapper2;
@Autowired private Mapper3 mapper3;

但是,当我调用该服务并用调试器停止它时,我可以看到mapper3连接到错误的数据源(postgres)。

有什么想法吗?需要更多信息吗?

1 个答案:

答案 0 :(得分:0)

当使用MyBatis配置多个数据源时,我具有相同的效果。在运行时,所有映射器都使用了最后定义的数据源连接凭据,尽管在启动过程中一切看起来都很好。

我可以将问题缩小到factoryBean定义并设置配置。看起来,配置对象在所有数据源之间共享(正如我在此处设置的那样),因为我从MyBatisProperties中设置了相同的实例。在运行时,配置也是MyBatis从Environment属性获取数据源的地方。

“魔术”发生在factoryBean.getObject()factoryBean.buildSqlSessionFactory()上。在这里,数据源被设置为配置。如果未显式设置配置,则会创建一个新的默认配置。否则,配置对象将被重用。因此,当使用相同的配置对象初始化多个工厂bean时,配置对象将注入每个数据源,而最后一个数据源将保留。

这可能是一个错误,因为作为Mybatis用户,我希望Mybatis使用factoryBean.setDataSource(aDatasource)提供的数据源。因此,当我注释掉factoryBean.setConfiguration时,一切都会按预期进行,但是当然我的配置未应用。

@Bean("sqlSessionFactoryA")
public SqlSessionFactory sqlSessionFactory(@Qualifier("datasourceA") DataSource aDataSource) throws Exception {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(aDataSource);
  factoryBean.setConfigurationProperties(properties.getConfigurationProperties());
//    factoryBean.setConfiguration(properties.getConfiguration());
  return factoryBean.getObject();
}

作为解决方案,我将MyBatisProperties设置为 prototype 范围,以便在每次注入时创建一个具有新配置对象的新属性对象。这是必要的,因为我不想编写一个复制方法,并且org.apache.ibatis.session.Configuration不提供一个复制构造函数。

@Bean
@Primary
@Scope("prototype")
public MybatisProperties myBatisProperties() {
  return new MybatisProperties();
}

现在我也可以应用配置,并且一切正常。

org.apache.ibatis.session.Configuration tmpConfiguration = properties.getConfiguration();
factoryBean.setConfiguration(tmpConfiguration);