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?
Mapper XML is an important file in MyBatis, which contains a set of statements to configure various SQL statements such as select, insert, update, and delete. These statements are known as Mapped Statements or Mapped SQL Statements. All the statements have unique id.
MyBatis is an open source persistence framework which simplifies the implementation of database access in Java applications. It provides the support for custom SQL, stored procedures and different types of mapping relations. Simply put, it's an alternative to JDBC and Hibernate.
It executes SQL safely and abstracts away all the intricacies of JDBC. It maps parameter objects to JDBC prepared statement parameters. It maps rows in JDBC result sets to objects. It can generate dynamic SQL with special tags in XML, or through the use of various templating engines.
resultMap – The most complicated and powerful element that describes how to load your objects from the database result sets.
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;
}
I used the following way without @MapperScan as follows :
1) Setup mybatis-config.xml just as step 2 above
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// ....
sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
// ....
return sessionFactory;
}
2) Setup CategoryDao
@Bean
public CategoryDao getCategoryDao() throws Exception{
SqlSessionTemplate sessionTemplate = new SqlSessionTemplate(sqlSessionFactoryBean());
return sessionTemplate.getMapper( CategoryDao.class );
}
3) Setup within mybatis-config.xml
<?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>
<settings>
<setting name="logImpl" value="COMMONS_LOGGING"/>
</settings>
<mappers>
<mapper class="CategoryMapper.xml"/>
</mappers>
</configuration>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With