Mybatis文件位置引发的mappedStatements为空

前言

在不使用Spring进行初始化Bean, 单纯的使用Mybatis的时候, 遇到xml读取不到的问题。

准备环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--pom.xml -->
<dependencies>
<!--==============================日志============================-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--==============================日志============================-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
-- MySQL数据库
create database test;
use test;
create table user(
id int primary key auto_increment,
name varchar(50)
);
insert into user(name) values ('A'), ('B'), ('C');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<!-- mybatis-config.xml -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<!-- 配置数据库 -->
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<mappers>
<!-- 注册mapper的三种方式 -->
<package name="com.ahao.demo.mapper"/>
<!--<mapper resource="UserMapper.xml"/>-->
<!--<mapper class="com.ahao.demo.mapper.UserMapper"/>-->
</mappers>
</configuration>
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!-- UserMapper.xml -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离 注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址 -->
<mapper namespace="com.ahao.demo.mapper.UserMapper">
<select id="selectAll" resultType="Map">
select * from user
</select>
</mapper>
1
2
3
4
5
package com.ahao.demo.mapper;
import java.util.*;
public interface UserMapper {
List<Map<String, String>> selectAll();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.ahao.test;

import com.ahao.demo.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Reader;
import java.util.List;

public class MyTest {
private static final Logger logger = LoggerFactory.getLogger(MyTest.class);

private static SqlSessionFactory sqlSessionFactory;

// 1. 从 mybatis-config.xml 初始化 sqlSessionFactory
@BeforeClass
public static void init() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
}

@Test
public void testSelectAll(){
// 2. 读取并显示
try(SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper);
List user = userMapper.selectAll();
System.out.println(user);
}
}
}

分析

运行过后抛出一个异常, statement 没有找到。

1
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.ahao.demo.mapper.UserMapper.selectAll

通过debug看到sqlSessionFactory对象中的Configuration内有两个属性,mapperRegistrymappedStatements看起来是存储mapper类和对应的Statements的两个属性。

mapperRegistry注册成功了, 但是mappedStatements为空, 也就是Class加载成功了, xml加载失败。

xml自动扫描配置是包扫描, 那么切换成别的可行吗?

1
2
3
4
5
6
7
8
<mappers>
<!-- Invalid bound statement (not found) -->
<package name="com.ahao.demo.mapper"/>
<!-- 加载成功 -->
<mapper resource="UserMapper.xml"/>
<!-- Invalid bound statement (not found) -->
<mapper class="com.ahao.demo.mapper.UserMapper"/>
</mappers>

可以看到只有直接指定xml才能加载成功。
那就是通过Class自动去寻找对应xml的时候发生了异常。
翻阅了官方文档, 并没有相关资料。

解决方案

然后检查target文件夹的时候, 发现UserMapper.xmlUserMapper.class没有在同一个文件夹内, 于是将target下的UserMapper.xml拖到UserMapper.class同级目录
。调试发现mappedStatements有数据。成功了, 推测应该是通过包名转文件路径进行扫描的。

经过测试, 有两种解决方案

  1. 手动移动target文件夹内的mapper.xml到对应的mapper.class相同文件夹/WEB-INF/class/com/ahao/demo/mapper下。
  2. mapper.xml放在resource文件夹内, 对应的文件目录结构/resource/com/ahao/demo/mapper下。

总结

Mybatis太坑, 没有个file not found提示。在IDEA下, 就算一开始将mapper.xml文件放在src/java文件夹下, 编译后也不会将xml放到target的相同目录下。
不过有Spring就不用担心了, Spring可以指定扫描xml文件位置。实际开发一般都是和Spring整合的吧。