Sunday, July 7, 2013

Issue with MapperScannerConfigurer in mybatis-spring module

    Recantly I have encountered with interesting feature in mybatis-spring module. I have been developing persistance module for one project and need to write tests for it. Ofcourse I decided to use spring-test and also tried dbUnit. I wrote a base class for persistance test case which simply utilize dbUnit functionality(applying datasets). Also I've annotated that class with standart spring-test annotation:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@TransactionConfiguration(defaultRollback = true, transactionManager = "txManager")
@Transactional
which points to applicationContext and also uses transactionManager to roolback transactions(with dbUnit i clear the real table in test method, insert there test dataset, perform test and then rollback a transaction).
   All my persistance tests are focusing on testing mybatis mappers, so for that reason I used MapperScannerConfigurer to dynamically discover all mybatis mapper interfaces and create for each of them MapperFactoryBean instances which produce real instances of mapper interfaces. So, I configured a bean for MapperScannerConfigurer in applicationContext:
<bean id="mappeScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"
  p:basePackage="com.mypackage"
  p:sqlSessionFactory-ref="sqlSessionFactory" />
As it needs sqlSessionFactory I declare it also. SqlSessionFactory needs a datasource to fetch connections from it, so i declare a datasource like this:
<bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource"
   p:username="${jdbc.username}" 
   p:password="${jdbc.password}" 
   p:url="${jdbc.url}"
   p:driverClassName="${jdbc.driverclass}" p:validationQuery="SELECT sysdate FROM dual" />
Ofcourse I need some property-placeholder to resolve references to jdbc stuff and I declare it:
<context:property-placeholder location="classpath:props/datasource.properties" />
It seemed to me that it's enough, but when I try to run the test I got next error:
org.apache.commons.dbcp.SQLNestedException: Cannot load JDBC driver class '${jdbc.driverclass}'
It means that placeholder hasn't been resolved. but why?


The Solution

I've started to investigate the problem and discovered that MapperScannerConfigurer is a BeanDefinitionRegistryPostProcessor. Javadoc for that class says:
Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in
So  MapperScannerConfigurer instantiates before the PropertyPlaceHolderConfigurer and, as it depends on sqlSessionFactory which depends on datasource, datasource instance try to be instantiated with property holders in its properties.
We can fix that in several ways:

  1. By removing  p:sqlSessionFactory-ref="sqlSessionFactory" from the MapperScannerConfigurer definition will not trigger creation sqlSessionFactory object during creation of  MapperScannerConfigurer. But sqlSessionfactory will then be autowired into MapperFactoryBean which extends SqlSessionDaoSupport(NOTE: this works only for versions of mybatis-spring prior to 1.2.0, starting from 1.2.0 version @Autowired annotation was removed from setSqlSessionFactory and setSqlSessionTemplate methods)
  2. By specifying p:sqlSessionFactoryName="sqlSessionFactory" instead. But this only works for versions higher than 1.0.2. And if you are using version higher than 1.2.0 it is the only way to configure MapperScannerConfigurer(however, there is a tag <mybatis:scan/> which can be used instead of MapperScannerConfigurer)
So, my result configuration was:
<bean id="mappeScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"
  p:basePackage="com.mypackage"
  p:sqlSessionFactoryName="sqlSessionFactory" />
and I switched to use version 1.2.0. In this case I wrote a class that extends standart SqlSessionDaoSupport and overrides method setSqlSessionFactory with @Autowired annotation. It allowed me not to declare all my dao classes in xml, but do the component-scan on them

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Please correct the typo: mybatis-spring:scan to mybatis:scan

    ReplyDelete
  3. Fixed, thanks for attentiveness

    ReplyDelete