0
votes

I know there are many examples over internet for the same problem. But what I am trying to get help is on architecture level.

I have a simple spring project where in I have one configuration class.I am trying to configure two datasources (distDataSource, shipmentDataSource). I have two separate classes for two data sources (MyBatisDISTDataSource , MyBatisShipmentDataSource) mentioned below.

These two datasorces are working fine separately, but when I am trying to execute it together I get exception on console.

Console Exception Log

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'distDAOImpl': Unsatisfied dependency expressed through field 'distMapper'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'distMapper' defined in file [D:\eclipse\ShippingModule\shipment-module-v2\CustEquip-CourierShipmentService-PickupSvc\target\classes\com\shipment\mapper\DistMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.apache.ibatis.session.SqlSessionFactory' available: expected single matching bean but found 2: distSqlSessionFactory,shipmentSqlSessionFactory at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:84) at com.telus.shipment.app.starter.SchedulePickup.main(SchedulePickup.java:11) Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'distMapper' defined in file [D:\eclipse\ShippingModule\shipment-module-v2\CustEquip-CourierShipmentService-PickupSvc\target\classes\com\shipment\mapper\DistMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.apache.ibatis.session.SqlSessionFactory' available: expected single matching bean but found 2: distSqlSessionFactory,shipmentSqlSessionFactory at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1357) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1249) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585) ... 14 more Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.apache.ibatis.session.SqlSessionFactory' available: expected single matching bean but found 2: distSqlSessionFactory,shipmentSqlSessionFactory at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:173) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1342) ... 25 more

Configuration Class

@Component
@Configuration
@Profile("local")
public class EnvironmentConfigLocal implements EnvironmentConfig {

    @Autowired @Qualifier("DISTDataSource") private MyBatisDISTDataSource distDataSource;
    @Autowired @Qualifier("ShipmentDataSource") private MyBatisShipmentDataSource shipmentDataSource;

    @PostConstruct
    public void postConstruct() {
        System.out.println("Selected Profile : Local");
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer dataProperties(final Environment environment) {
        final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        final SpringProfileDocumentMatcher matcher = new SpringProfileDocumentMatcher();
        matcher.addActiveProfiles(environment.getActiveProfiles());
        yaml.setDocumentMatchers(matcher);
        yaml.setResources(new ClassPathResource("application.yaml"));
        propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
        return propertySourcesPlaceholderConfigurer;
    }

    @Bean
    public DataSourceTransactionManager distTransactionManager() throws SQLException {
        return new DataSourceTransactionManager(distDataSource);
    }

    @Bean
    public DataSourceTransactionManager shipmentTransactionManager() throws SQLException {
        return new DataSourceTransactionManager(shipmentDataSource);
    }

    @Bean
    @Override
    public SqlSessionFactory distSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean distSessionFactory = new SqlSessionFactoryBean();
        distSessionFactory.setDataSource(distDataSource);
        return distSessionFactory.getObject();
    }

    @Bean
    @Override
    public SqlSessionFactory shipmentSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean shipmentSessionFactory = new SqlSessionFactoryBean();
        shipmentSessionFactory.setDataSource(shipmentDataSource);
        return shipmentSessionFactory.getObject();
    }
}

MyBatisDISTDataSource

@Component
@Qualifier("DISTDataSource")
public class MyBatisDISTDataSource extends PooledDataSource {

    @Value("${dist.db.poolMaximumActiveConnections}") int poolMaximumActiveConnections;
    @Value("${dist.db.poolMaximumIdleConnections}") int poolMaximumIdleConnections;

    public MyBatisDISTDataSource(
            @Value("${dist.db.driver-class}") String driver, 
            @Value("${dist.db.url}") String url,
            @Value("${dist.db.user}") String username, 
            @Value("${dist.db.password}") String password) {
        super(driver, url, username, password);
        System.out.println("DIST DB Attr: \n\t" 
                        +driver+"\n\t"
                        +url+"\n\t"
                        +username+"\n\t"
                        +password+"\n\t");
    }

    @PostConstruct
    private void setDataSourceProperties() {
        this.setPoolMaximumActiveConnections(poolMaximumActiveConnections);
        this.setPoolMaximumIdleConnections(poolMaximumIdleConnections);
    }
}

MyBatisShipmentDataSource

@Component
@Qualifier("ShipmentDataSource")
public class MyBatisShipmentDataSource extends PooledDataSource {

    @Value("${shipment.db.poolMaximumActiveConnections}") int poolMaximumActiveConnections;
    @Value("${shipment.db.poolMaximumIdleConnections}") int poolMaximumIdleConnections;

    public MyBatisShipmentDataSource(
            @Value("${shipment.db.driver-class}") String driver, 
            @Value("${shipment.db.url}") String url,
            @Value("${shipment.db.user}") String username, 
            @Value("${shipment.db.password}") String password) {
        super(driver, url, username, password);
        System.out.println("Shipment DB Attr: \n\t" 
                        +driver+"\n\t"
                        +url+"\n\t"
                        +username+"\n\t"
                        +password+"\n\t");
    }

    @PostConstruct
    private void setDataSourceProperties() {
        this.setPoolMaximumActiveConnections(poolMaximumActiveConnections);
        this.setPoolMaximumIdleConnections(poolMaximumIdleConnections);
    }
}

DistMapper

@Mapper
@Component
public interface DistMapper {
    @Select({"select * "
            + "from CONTACT_ADDRESS CA, ADDRESS A"
            + "where CONTACTING_ID = '10001134' "
            + "and PROVINCE_CD ='ON' "
            + "and STREET_NUMBER = '15'"})
    @Results({@Result(column = "CONTACTING_ID", property = "contactingId", jdbcType = JdbcType.DECIMAL)})
    public List<OutletAddress> findAddressByOutletId();

}

ShipmentMapper

@Mapper
@Component
public interface ShipmentMapper {

    @Select({"select C.CONTACT_ID " 
            + "from SHIPMENT_EVENT_TRACKING SE, SHIPMENT S, CONTACT_ADDR CA, CONTACT C " 
            + "where SE.EVENT_CD = 'PICKUP' " 
            + "and SE.SHIPMENT_ID = s.shipment_id " 
            + "and S.SENDER_ADDR_ID = CA.CONTACT_ADDR_ID " 
            + "and CA.CONTACT_ID = c.contact_id " 
            + "and C.GROUP_CD = 'OT' " 
            + "and SE.EVENT_OCCURRED_IND = 'N' " 
            + "and S.CREATION_TS >= (select CURRENT_TIMESTAMP - interval '30' day from dual)" 
            + "and S.SCHEDULE_PICKUP_IND = 'Y'"})
    @Results({
        @Result(column = "CONTACT_ID", property = "contactId", jdbcType = JdbcType.DECIMAL)})
    public CopyOnWriteArrayList<EligibleShipment> findShipmentsByOutlet();
}
2
The problem is in DistMapper. But you haven't posted its code.JB Nizet
@JBNizet DistMapper, ShipmentMapper & Package structure added.Shamim Ahmad
Something, but I don't know what, creates beans from the mapper interface sannotated by Mapper I guess, and this thing expects a single bean of type SqlSessionFactory. Maybe it's customizable somehow, but I have no idea of which library you're using.JB Nizet

2 Answers

2
votes

@Primary on one of the SqlSessionFactory bean solved my problem.

    @Bean
    @Primary
    @Override
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean sessionFactory2 = new SqlSessionFactoryBean();
        sessionFactory2.setDataSource(dataSource2);
        return sessionFactory2.getObject();
    }
0
votes

With spring-mybatis you either register mappers one by one or you use mappers scanning.

If you are registering mappers manually make sure you pass correct SqlSessionFactory to every mapper. If you don't do this spring will try to autowire SqlSessionFactory and because you have two of them you would get the error that no single bean found.

If you are using scanning for mappers specify parameters for it so that appropriate mappers are using correct SqlSessionFactory. One way to do that is by placing mappers that should use different DataSources to different packages. In this case you need to create two MapperScannerConfigurer beans like this (assuming that mappers are in com.mycompany.myapp.distMappersPackage and com.mycompany.myapp.shipmentMappersPackage):

@Bean
public MapperScannerConfigurer distMapperScannerConfigurer() throws Exception {
    MapperScannerConfigurer configurer = new MapperScannerConfigurer();
    configurer.setBasePackage("com.mycompany.myapp.distMappersPackage");
    configurer.setSqlSessionFactoryBeanName("distSqlSessionFactory");
    return configurer;
}

@Bean
public MapperScannerConfigurer shipmentMapperScannerConfigurer() throws Exception {
    MapperScannerConfigurer configurer = new MapperScannerConfigurer();
    configurer.setBasePackage("com.mycompany.myapp.shipmentMappersPackage");
    configurer.setSqlSessionFactoryBeanName("shipmentSqlSessionFactory");
    return configurer;
}

Alternatively, you can create two different annotation and use them on mappers like this:

@DistMapperMarker
public interface DistMapper {
   ...
}

@ShipmentMapperMarker
public interface ShipmentMapper {
   ...
}

In this case mappers can be in one package but you specify annotationClass on MapperScannerConfigurer:

@Bean
public MapperScannerConfigurer distMapperScannerConfigurer() throws Exception {
    MapperScannerConfigurer configurer = new MapperScannerConfigurer();
    configurer.setAnnotationClass(DistMapperMarker.class);
    configurer.setSqlSessionFactoryBeanName("distSqlSessionFactory");
    return configurer;
}

@Bean
public MapperScannerConfigurer shipmentMapperScannerConfigurer() throws Exception {
    MapperScannerConfigurer configurer = new MapperScannerConfigurer();
    configurer.setAnnotationClass(ShipmentMapperMarker.class);
    configurer.setSqlSessionFactoryBeanName("shipmentSqlSessionFactory");
    return configurer;
}

For java base spring configuration you can also try to use MapperScan and specify parameters as attributes to it. This would require you to split spring configuration into at least two classes. I haven't used this approach so I'm not sure if it work fine.