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

14,611

Solution 1

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;
    }
    

Solution 2

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>
Share:
14,611

Related videos on Youtube

Luiggi Mendoza
Author by

Luiggi Mendoza

🤯 Wild software engineer unleashed in a crazy world 📺 One Piece, FMAB 🎮 Souls games 🧑🏽‍🍳 Peruvian &amp; keto food (kinda) 🌱 Gardener wannabe You can contact me anytime to [email protected]. My LinkedIn Profile.

Updated on July 22, 2022

Comments

  • Luiggi Mendoza
    Luiggi Mendoza almost 2 years

    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?

  • Luiggi Mendoza
    Luiggi Mendoza almost 9 years
    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/>.
  • Bogdan
    Bogdan almost 9 years
    @LuiggiMendoza: @MapperScan looks for interfaces. See my edit to the answer.
  • Luiggi Mendoza
    Luiggi Mendoza almost 9 years
    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.