5

I am working with mybatis and spring-boot.

I have been suffered for several hours with follow message.

2018-02-18 15:25:13.774 ERROR 77556 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for org.owls.mybatis.mapper.TestMapper.selectTimestamp
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for org.owls.mybatis.mapper.TestMapper.selectTimestamp] with root cause

java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for org.owls.mybatis.mapper.TestMapper.selectTimestamp

My source is based on this(Spring boot get started with project) and add Mybatis settings.

I think somehow mybatis could not find the mapper information, So I would like to print registered mybatis mapper list in the console. (Is it possible?)

FYI, follow is the things I implemented.

I uses application.yml to add mybatis

mybatis:
    type-aliases-package: org.owls.mybatis.model
    type-handlers-package: org.owls.mybatis.handler
    mapper-locations: classpath:/resources/mybatis/mapper/*_mapper.xml 
    configuration:
        map-underscore-to-camel-case: true
        default-fetch-size: 100
        default-statement-timeout: 30

And this is /resources/mybatis/mapper/test_mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.owls.mybatis.mapper.TestMapper">
    <select id="selectTimestamp" resultType="map">
        SELECT NOW() FROM DUAL
    </select>
</mapper>

From here is Java codes.

First, Mapper interface TestMapper which in org.owls.mybatis.mapper package is

public interface TestMapper {
    @Select("SELECT NOW() FROM DUAL")
    public Map selectTimestamp() throws Exception;
}

Second, I made a Service which generate SqlSessionTemplate. Here is the code

@Service
public class MybatisService implements ApplicationContextAware {

    private Logger logger = Logger.getLogger(MybatisService.class);
    private ApplicationContext context;

    @Autowired
    DBService dbService;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }


    // Register a Object with a name, if not exists
    private void registerBean(String beanName, Object newBean){
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry)  context.getAutowireCapableBeanFactory();
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(newBean.getClass());
        BeanDefinition def = builder.getBeanDefinition();
        if(!registry.containsBeanDefinition(beanName)) registry.registerBeanDefinition(beanName, def);
    }


    /*
    * Managing SqlSessionFactory
    * */
    private SqlSessionFactory generateSqlSessionFactory(String dsId, DataSource ds) throws Exception {
        String beanName = dsId + "SqlSessionFactory";

        SqlSessionFactory sqlSessionFactory = null;
        try {
            Object registeredBean = context.getBean(beanName);
            logger.info("Found SqlSessionFactory bean [ " + beanName + " ]. Returns the instance");
            sqlSessionFactory = (SqlSessionFactory) registeredBean;
        } catch (NoSuchBeanDefinitionException e) {
            logger.info("Not found SqlSessionFactory bean [ " + beanName + " ]. Generate new one");
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(ds);
            sqlSessionFactory = (SqlSessionFactory) sqlSessionFactoryBean.getObject();
            registerBean(beanName, sqlSessionFactory);
        }
        return sqlSessionFactory;
    }


    /*
    * Managing SqlSessionTemplate
    * */
    private SqlSessionTemplate generateSqlSessionTemplate(String dsId, SqlSessionFactory sqlSessionFactory) throws Exception {
        String beanName = dsId + "SqlSessionTemplate";
        SqlSessionTemplate sqlSessionTemplate = null;
        try {
            Object registeredBean = context.getBean(beanName);
            logger.info("Found SqlSessionTemplate bean [ " + beanName + " ]. Returns the instance");
            sqlSessionTemplate = (SqlSessionTemplate) registeredBean;
        } catch (NoSuchBeanDefinitionException e) {
            logger.info("Not found SqlSessionTemplate bean [ " + beanName + " ]. Generate new one");
            sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
            registerBean(beanName, sqlSessionFactory);
        }
        return sqlSessionTemplate;
    }


    public SqlSessionTemplate getSqlSessionTemplate(String dsId) {
        SqlSessionTemplate template = null;
        try {
            DataSource ds = dbService.getDS(dsId);
            SqlSessionFactory sqlSessionFactory = generateSqlSessionFactory(dsId, ds);
            template = generateSqlSessionTemplate(dsId, sqlSessionFactory);
        } catch (Exception e) {
            logger.error("Failed to fetch SqlSessionFactory. returns null value ", e);
        }
        return template;
    }
}

Finally, in the Controller, I call it with ...

@Controller
@RequestMapping(value = {"/test"})
public class TestController {

    @Autowired
    MybatisService mybatisService;
    private SqlSessionTemplate sqlSessionTemplate;

    private Logger logger = Logger.getLogger(TestController.class);

    @PostConstruct
    public void init(){
        sqlSessionTemplate = mybatisService.getSqlSessionTemplate("test");
    }

    @RequestMapping(value = {"getCurrentTimestamp"})
    public @ResponseBody String getCurrentTimestamp() throws Exception {

        logger.info("=== SqlSessionTemplate Info in Controller :: " + sqlSessionTemplate);

        Map<String, Object> data = (Map) sqlSessionTemplate.selectOne("org.owls.mybatis.mapper.TestMapper.selectTimestamp");
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(data);
    }
};

I think the error message is little bit vague. (I could not find any clue which is wrong among settings - mapper xml, application.yml, db connection)

Thanks for answers. Access full source code

======================== Edit #1. 19 FEB =============================

I found it out that SqlSessionFactory Configuration does not have any mapper at all.

I could print mapper list via follow way. In code where generates SqlSessionFactory ...

// FOR TEST 0219
MapperRegistry registry = sqlSessionFactory.getConfiguration().getMapperRegistry();


logger.info("=== Printing Mapper Registry starts");
registry.getMappers().forEach(mapper -> {
    logger.info("Mapper Registry :: " +  mapper.getName());
});
logger.info("=== Printing Mapper Registry ends");
// FOR TEST 0219

In my case, it prints nothing, but it is able to add mapper or package through MapperRegistry instance.

Here is what I thought

  1. SqlSessionFactory(provided by ibatis) requires DataSource
  2. SqlSessionTemplate(provided by ibatis) is endpoint and it requires SqlSessionFactory
  3. In conclusion, SqlSessionTemplate depends on SqlSessionFactory which depends on DataSource.

If so, how could I properly generate SqlSession* as bean in a project which uses dynamic Datasource?

In my upper code(edited one), I was able to add mapper as bean but every SqlSessionFactory has same Mappers(duplicated, since MapperRegistry get a package name as a parameter)

How could I set different mappers to corresponding SqlSessionFactory?

Juneyoung Oh
  • 7,318
  • 16
  • 73
  • 121

0 Answers0