Why must the interface and xml mapper file be in same package and have the same name?
Asked Answered
N

2

8

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?

Nuthouse answered 15/5, 2015 at 7:15 Comment(0)
S
7

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:

  1. 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.

  2. 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;
    }
    
Shipp answered 16/5, 2015 at 12:49 Comment(3)
I use @MapperScan and indicate the base packages where MyBatis can scan all the mappers by itself. This annotation works like <mybatis:scan /> to auto scan the files and thus don't use <mappers/>.Nuthouse
@LuiggiMendoza: @MapperScan looks for interfaces. See my edit to the answer.Shipp
This is interesting. In the end, it's a MyBatis thing the whole separation of concerns between the interface and the mapper, while @MapperScan helps on the autowiring of beans of the dao interfaces. Thanks for the link to proper documentation and the example.Nuthouse
T
1

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>
Triggerhappy answered 17/5, 2015 at 1:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.