Search code examples
javaspringspring-bootmybatisspring-mybatis

MyBatis mapper class not registered in Spring Boot application with two datasources


We have a Spring Boot application that should access stored procedures from two different databases, DB2 and Oracle, through MyBatis mappers

We have created two DB2 context classes, e.g. for DB2

@Configuration
@MapperScan({ "...mapper.mybatis.db2" })
public class Db2Context {

  @Primary
  @Bean(name = "db2DataSource")
  public DataSource getDataSource() { ...

  @Primary
  @Bean(name = "db2SqlSessionFactory")
  public SqlSessionFactory getSqlSessionFactory() {...

The MyBatis beans look like

public interface Db2Mapper extends MyBatisMapper<SomeType> {

  @Override
  @Select(value = ...)
  @Options(statementType = StatementType.CALLABLE)
  @Results({...}) 
  List<SomeType> select(Map<String, Object> parameters);

And the SqlSessionFactory beans are injected into the respective DAO classes with the appropriate qualification, e.g.

@Repository
public class Db2Dao {

  @Autowired
  @Qualifier("db2SqlSessionFactory")
  SqlSessionFactory sqlSessionFactory;

  ...

  try(SqlSession session= sqlSessionFactory.openSession(true);) {
    Db2Mapper mapper = session.getMapper(Db2Mapper.class);
    resultSet = mapper.select(parameters);

We have the identical config, mapper and DAO for Oracle as well, except that in that config the DataSource and SqlSessionFactory beans are not annotated with @Primary. This was necessary as per described in the Spring Boot reference: http://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/htmlsingle/#howto-two-datasources; without that the Spring Boot application startup would result in NoUniqueBeanDefinitionException

With this configuration the Spring Boot application starts up succesfully, and during the startup there are even INFO log printouts indicating that both mapper classes have been succesfully identified

INFO BeanPostProcessorChecker : Bean 'db2Mapper' of type [class org.mybatis.spring.mapper.MapperFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
INFO BeanPostProcessorChecker : Bean 'oracleMapper' of type [class org.mybatis.spring.mapper.MapperFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

However, in runtime we have a problem. First Db2Dao is executed, and with that everything goes perfectly, the DB2 stored procedure is getting executed, and the retrieved results are stored through Db2Mapper. Then comes OracleDao; however after the Oracle SP execution the following exception is received

ERROR Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception 
[Request processing failed; ... Type interface com....mapper.mybatis.oracle.OracleMapper is not 
known to the MapperRegistry.]

We have been fighting with this issue for some while now, but could not find a resolution. Possibly the usage of @Primary might have something to do with it, but without that we are not even able to start up the application. Our researches actually seem to indicate that different library versions might even provide different behaviour: our stack is Java 1.8, Spring Boot 1.2.6, Spring 4.1.7, MyBatis 3.2.5, MyBatis-Spring 1.2.2


Solution

  • First of all, I would suggest not to autowire SqlSessionFactory into your DAOs at all. In fact you can get rid of DAOs completely and use your mappers in service layer as spring beans.

    So you do something like this:

    public interface Db2Mapper extends MyBatisMapper<SomeType> {
    
      @Override
      @Select(value = ...)
      @Options(statementType = StatementType.CALLABLE)
      @Results({...}) 
      List<SomeType> select(Map<String, Object> parameters);
    }
    
    @Service
    public class Db2Service{
      @Autowired
      private Db2Mapper db2Mapper;
    
      //...
    }
    

    Secondly, the key to have various datasources integrated with mybatis-spring is in sqlSessionFactoryRef attribute of @MapperScan annotation. With that you can narrow down which SqlSessionFactory instance is used for witch @MapperScan. Something like this:

    @Configuration
    @MapperScan(value = { "...mapper.mybatis.db2" }, sqlSessionFactoryRef = "db2SqlSessionFactory")
    public class Db2Context {
    
      @Primary
      @Bean(name = "db2DataSource")
      public DataSource getDataSource() { ...
    
      @Primary
      @Bean(name = "db2SqlSessionFactory")
      public SqlSessionFactory getSqlSessionFactory() {...
    
    @Configuration
    @MapperScan(value = { "...mapper.mybatis.other" }, sqlSessionFactoryRef = "otherSqlSessionFactory")
    public class OtherContext {
    
      @Bean(name = "otherDataSource")
      public DataSource getDataSource() { ...
    
      @Bean(name = "otherSqlSessionFactory")
      public SqlSessionFactory getSqlSessionFactory() {...
    

    Obviously you shouldn't scan same packages with these two @MapperScan annotations.