一、前言
不知道大家平时在开发过程中有没有遇到这样的问题,同一条SQL语句,MyBatis 查询结果与 MySQL 执行结果居然不一致,具体说应该是MyBatis 查询结果比MySQL 执行结果的数据更少。不要不相信,如果不注意,这样的坑,你还真有可能会踩的。
二、代码演示
下面用代码简单演示一下:
- 创建两张用于测试的数据表:
- teacher表:
CREATE TABLE `teacher` (
`id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`scid` int(11) NULL DEFAULT NULL,
`code` int(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
测试数据:
INSERT INTO `teacher`(`id`, `name`, `scid`) VALUES (1, 'jack', 1);
INSERT INTO `teacher`(`id`, `name`, `scid`) VALUES (2, 'jack', 1);
INSERT INTO `teacher`(`id`, `name`, `scid`) VALUES (3, 'jack', 2);
INSERT INTO `teacher`(`id`, `name`, `scid`) VALUES (4, 'jack', 2);
- school表:
CREATE TABLE `school` (
`id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`code` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
测试数据:
INSERT INTO `school`(`id`, `name`, `code`) VALUES (1, '兰陵高中', 1001);
INSERT INTO `school`(`id`, `name`, `code`) VALUES (2, '城东高中', 1002);
- 创建一个springboot项目,项目结构如下:
- 导入maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 编写application.yml配置文件:
server:
port: 8070
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/bg-learnsp?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
logging:
level:
root: info
com.learn.mapper: debug
- 编写数据库相关实体类:
- Teacher类:
package com.learn.entity;
import lombok.Data;
@Data
public class Teacher {
private int id;
private String name;
private int scid;
private int code;
}
- School类:
package com.learn.entity;
import lombok.Data;
import java.util.List;
@Data
public class School {
private int id;
private int code;
private String name;
// School 与Teacher 是一对多的关系
private List<Teacher> teachers;
}
- 编写school相关mapper:
- SchoolMapper.java:
package com.learn.mapper;
import com.learn.entity.School;
import java.util.List;
public interface SchoolMapper {
List<School> selectAll();
}
- SchoolMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.mapper.SchoolMapper">
<resultMap id="BaseResultMap" type="com.learn.entity.School">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="code" jdbcType="INTEGER" property="code"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<collection property="teachers" ofType="com.learn.entity.Teacher">
<result column="tname" jdbcType="VARCHAR" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="BaseResultMap">
SELECT
s.id,
s.NAME,
t.NAME AS tname
FROM
school s,
teacher t
WHERE
s.id = t.scid
</select>
</mapper>
- SchoolService:
@Service
public class SchoolService {
@Autowired
private SchoolMapper schoolMapper;
public List<School> listAll() {
return schoolMapper.selectAll();
}
}
- SchoolController:
@RestController
public class SchoolController {
@Autowired
private SchoolService schoolService;
@GetMapping("/listAllSchool")
public List<School> listAll() {
return schoolService.listAll();
}
}
- 启动类添加相关配置:
@SpringBootApplication
@MapperScan("com.learn.mapper")
@EnableTransactionManagement
public class SpringbootDataerrorApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDataerrorApplication.class, args);
}
}
测试:
上面的school与teacher是一对多的关系,在SchoolMapper.xml文件中使用resultMap标签配置它们的对应映射关系。
启动项目,访问http://localhost:8070/listAllSchool:
- 浏览器显示:
- 日志输出:
- 数据库查询结果:
- 可以看到,每个高中应该都对应2个jack老师,日志输出的Total也是4,但是查询结果显示时候,每个高中只有一个jack老师了,这是为什么呢?
网上搜索了一下答案:mybaties映射时使用主键判断唯一性,如果查询出来的数据集在映射文件配置中没有设置主键或者主键存在相同的情况下,mybaties只映射其中一条。
解决方法:
知道原因以后,改造一下SchoolMapper.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.mapper.SchoolMapper">
<resultMap id="BaseResultMap" type="com.learn.entity.School">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="code" jdbcType="INTEGER" property="code"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<collection property="teachers" ofType="com.learn.entity.Teacher">
<!--增加teacher ID映射-->
<id column="tid" jdbcType="INTEGER" property="id"/>
<result column="tname" jdbcType="VARCHAR" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="BaseResultMap">
SELECT
s.id,
s.NAME,
t.NAME AS tname,
t.id as tid
FROM
school s,
teacher t
WHERE
s.id = t.scid
</select>
</mapper>
重新启动,访问:
可以看到,此时查询数据就是正确的了,但是如果我需要对应的表就是没有主键的呢,此时不能使用主键ID来对应呢?
利用teacher表中的code字段测试一下,修改SchoolMapper.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.mapper.SchoolMapper">
<resultMap id="BaseResultMap" type="com.learn.entity.School">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="code" jdbcType="INTEGER" property="code"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<collection property="teachers" ofType="com.learn.entity.Teacher">
<result column="tname" jdbcType="VARCHAR" property="name"/>
<!--使用code字段映射-->
<result column="tcode" jdbcType="INTEGER" property="code"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="BaseResultMap">
SELECT
s.id,
s.NAME,
t.NAME AS tname,
t.code as tcode
FROM
school s,
teacher t
WHERE
s.id = t.scid
</select>
</mapper>
- code字段数据相同:
程序查询结果: - code字段数据不同:
程序查询结果:
最后:
可以看到,mybatis在进行查询映射时,如果查询出来的字段值全部一样,会只保留一条数据,这种情况就是造成MyBatis 查询结果与 MySQL 执行结果不一致的主要原因,因此,在进行查询映射的时候,最好增加一个不会重复的字段,最好的选择就是主键,这样就能保证MyBatis 查询结果与 MySQL 执行结果是一致的了!