Today I was preparing an example using Spring Boot and using MyBatis for the data access communication next to Spring-MyBatis. Here is the relevant project configuration (using maven):
src/main/java
- edu.home.ltmj.controller
+ CategoryController.java
- edu.home.ltmj.dao
+ CategoryDao.java
- edu.home.ltmj.domain
+ Category.java
src/main/resources
- edu.home.ltmj.dao
+ CategoryMapper.xml
Relevant content of the files:
CategoryDao.java:
package edu.home.ltmj.dao;
public interface CategoryDao {
List<Category> getAllCategories();
}
CategoryMapper.xml:
<mapper namespace="edu.home.ltmj.dao.CategoryDao">
<resultMap id="categoryMap"
type="edu.home.ltmj.domain.Category">
<id property="id" column="id" />
<result property="name" column="name" />
</resultMap>
<select id="getAllCategories" resultMap="categoryMap">
SELECT id, nombre
FROM category
</select>
</mapper>
Then, I inject an instance of this dao in a request controller (for testing purposes), like this:
package edu.home.ltmj.controller;
@RestController
public class CategoryController {
@Autowired
private CategoryDao dao;
@RequestMapping(value="/category/all",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public List<Categoria> getAllCategories() {
return dao.getAllCategories();
}
}
I run my project and test the execution by using curl localhost:8080/category/all
and then expected to see the results in JSON format, but I got this exception instead:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): edu.home.ltmj.dao.CategoryDao.getAllCategories
at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:189)
at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:43)
at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:58)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:51)
at com.sun.proxy.$Proxy45.getAllCategories(Unknown Source)
at edu.home.ltmj.controller.CategoryRestController.getAllCategories(CategoryRestController.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
(...)
I don't understand the cause of this. There's an interface CategoryDao
and it has the proper method getAllCategories
that matches with <select id="getAllCategories">
. After some time of playing with this, I've changed the name of the dao interface to CategoryMapper
and updated the namespace in CategoryMapper.xml. After I did this, everything worked normally. Also, after having the same name for class and xml, I moved the dao class and the xml mapper into different packages (stil using the same name for both: CategoryMapper.), updated the namespace in the xml file, and got the same exception, with the message updated to show the name of the package of the dao interface. But then again, I moved both files to the same package and everything worked again.
So, my question is: why does MyBatis need that the interface and the xml mapper file to have the same name and be in the same package? Is this MyBatis design or an issue in Spring MyBatis?
Do you also have a MyBatis Config file?
If I remember correctly the same name same location for the XML file as the interface is when you want to have a setup that just works with no extra configuration.
If you have the XML mappers somewhere else you can manually specify the classpath of the XML files using a <mappers>
element inside MyBatis configuration.
From the Injecting Mappers documentation:
If the UserMapper has a corresponding MyBatis XML mapper file in the same classpath location as the mapper interface, it will be parsed automatically by the MapperFactoryBean. There is no need to specify the mapper in a MyBatis configuration file unless the mapper XML files are in a different classpath location. See the SqlSessionFactoryBean's configLocation property for more information.
So try this:
Create a mybatis-config.xml
file inside src/main/resources
with this in it:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<mappers>
<mapper resource="com/test/path/etc/etc/WhateverNameYouWant.xml"/>
</mappers>
</configuration>
Where WhateverNameYouWant.xml
contains what your CategoryMapper.xml
contained.
Set the location of the config file (Java configuration as below or bean in the applicationContext
file):
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// ....
sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
// ....
return sessionFactory;
}