6

我一直在使用 Spring 和 MyBatis,它在单个数据库中运行得非常好。我在尝试添加另一个数据库时遇到了困难(请参阅Github 上的可重现示例)。

我正在使用 Spring Java 配置(即不是 XML)。我见过的大多数示例都展示了如何使用 XML 来实现这一点。

我有两个这样的数据配置类(A & B):

@Configuration
@MapperScan("io.woolford.database.mapper")
public class DataConfigDatabaseA {

    @Bean(name="dataSourceA")
    public DataSource dataSourceA() throws SQLException {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriver(new com.mysql.jdbc.Driver());
        dataSource.setUrl("jdbc:mysql://" + dbHostA + "/" + dbDatabaseA);
        dataSource.setUsername(dbUserA);
        dataSource.setPassword(dbPasswordA);
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSourceA());
        return sessionFactory.getObject();
    }
}

两个映射器和一个自动连接映射器的服务:

@Service
public class DbService {

    @Autowired
    private DbMapperA dbMapperA;

    @Autowired
    private DbMapperB dbMapperB;

    public List<Record> getDabaseARecords(){
        return dbMapperA.getDatabaseARecords();
    }

    public List<Record> getDabaseBRecords(){
        return dbMapperB.getDatabaseBRecords();
    }

}

应用程序无法启动:

Error creating bean with name 'dataSourceInitializer': 
  Invocation of init method failed; nested exception is 
    org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
      No qualifying bean of type [javax.sql.DataSource] is defined: 
        expected single matching bean but found 2: dataSourceB,dataSourceA

我读过可以使用@Qualifier注释来消除自动装配的歧义,尽管我不确定在哪里添加它。

你能看出我哪里错了吗?

4

4 回答 4

2

如果您想同时使用两个数据源并且它们不是主要和次要的,您应该在您的应用程序上禁用DataSourceAutoConfigurationby@EnableAutoConfiguration(excludes = {DataSourceAutoConfiguration.class})注释 by @SpringBootApplication。之后,您可以创建自己的SqlSessionFactory并捆绑自己的DataSource. 如果你也想使用DataSourceTransactionManager,你也应该这样做。

在这种情况下,你没有禁用DataSourceAutoConfiguration,所以 spring 框架会尝试@Autowired只有一个DataSource但有两个,会发生错误。

正如我之前所说,您应该DataSourceAutoConfiguration手动禁用和配置它。

您可以禁用数据源自动配置,如下所示:

@SpringBootApplication
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class YourApplication implements CommandLineRunner {
    public static void main (String... args) {
        SpringApplication.run(YourApplication.class, args);
    }
}

如果你真的想同时使用多个数据库,我建议你手动注册适当的bean,例如:

package xyz.cloorc.boot.mybatis;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
public class SimpleTest {

    private DataSource dsA;
    private DataSource dsB;

    @Bean(name = "dataSourceA")
    public DataSource getDataSourceA() {
        return dsA != null ? dsA : (dsA = new BasicDataSource());
    }

    @Bean(name = "dataSourceB")
    public DataSource getDataSourceB() {
        return dsB != null ? dsB : (dsB = new BasicDataSource());
    }

    @Bean(name = "sqlSessionFactoryA")
    public SqlSessionFactory getSqlSessionFactoryA() throws Exception {
        // set DataSource to dsA
        return new SqlSessionFactoryBean().getObject();
    }

    @Bean(name = "sqlSessionFactoryB")
    public SqlSessionFactory getSqlSessionFactoryB() throws Exception {
        // set DataSource to dsB
        return new SqlSessionFactoryBean().getObject();
    }
}

@Repository
public class SimpleDao extends SqlSessionDaoSupport {

    @Resource(name = "sqlSessionFactoryA")
    SqlSessionFactory factory;

    @PostConstruct
    public void init() {
        setSqlSessionFactory(factory);
    }

    @Override
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        super.setSqlSessionFactory(sqlSessionFactory);
    }

    public <T> T get (Object id) {
        return super.getSqlSession().selectOne("sql statement", "sql parameters");
    }
}
于 2016-04-25T13:46:27.860 回答
1

最后,我们将每个映射器放在自己的文件夹中:

src/main/java/io/woolford/database/mapper/a/DbMapperA.java
src/main/java/io/woolford/database/mapper/c/DbMapperB.java

然后我们创建了两个DataConfig类,每个数据库一个。@MapperScan注释解决了这个问题expected single matching bean but found 2

@Configuration
@MapperScan(value = {"io.woolford.database.mapper.a"}, sqlSessionFactoryRef="sqlSessionFactoryA")
public class DataConfigDatabaseA {

有必要将@Primary注释添加到其中一个DataConfig类中的 bean:

@Bean(name="dataSourceA")
@Primary
public DataSource dataSourceA() throws SQLException {
    ...
}

@Bean(name="sqlSessionFactoryA")
@Primary
public SqlSessionFactory sqlSessionFactoryA() throws Exception {
    ...
}

感谢所有帮助过的人。毫无疑问,有不止一种方法可以做到这一点。我确实按照@eduardlofitskyi@Qualifier@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})@GeminiKeith 的建议进行了尝试,但这产生了一些进一步的错误。

如果它有用,对我们有用的解决方案在这里发布:https ://github.com/alexwoolford/mybatis-spring-multiple-mysql-reproducible-example

于 2016-05-12T16:23:07.493 回答
0

我遇到了同样的问题,无法启动我的 Spring Boot 应用程序,通过重命名有问题的类和处理它的所有层,奇怪的是应用程序成功启动了。

我有课UOMServiceUOMServiceImpl UOMRepositoryUOMRepositoryImpl。我将它们重命名为UomService, UomServiceImpl,UomRepositoryUomRepositoryImpl解决了问题!

于 2017-01-28T10:25:19.387 回答
0

You can use @Qualifier annotation

The problem is that you have two the same type beans in Spring container. And when you try autowire beans, Spring cannot resolve which bean inject to field

The @Qualifier annotation is the main way to work with qualifiers. It can be applied alongside @Autowired or @Inject at the point of injection to specify which bean you want to be injected.

So, your DbService should look like this:

    @Service
    public class DbService {

    @Autowired
    @Qualifier("dataSourceA")
    private DbMapperA dbMapperA;

    @Autowired
    @Qualifier("dataSourceB")
    private DbMapperB dbMapperB;

    public List<Record> getDabaseARecords(){
        return dbMapperA.getDatabaseARecords();
    }

    public List<Record> getDabaseBRecords(){
        return dbMapperB.getDatabaseBRecords();
    }

}
于 2016-05-10T17:38:57.197 回答