6、结果集映射ResultMap
6.1、如果类变量名和字段名不一样会发生什么
如果我们把 pojo 类里的 pwd 改成 passwd。就会导致属性名和字段名不一致(字段名是pwd)。
private int id;
private String name;
private String passwd;
如果我们执行测试代码,结果是:
User{id=1, name='dzy', pwd='null'}
获取不到密码,原因是 mybatis 把select * from mybatis.user where id = #{id}
解析成了
select id, name, pwd from mybatis.user where id = #{id}
pwd 和 passwd 没有关系,所以passwd不会被赋值。
如果我们修改 sql 为:
select id, name, pwd as passwd from mybatis.user where id = #{id}
再次执行,结果正确:
User{id=1, name='dzy', pwd='123456'}
6.2、resultMap
上面的在 sql 语句里起别名是一个解决方法,但是使用resultMap是更好的方法
在 mapper.xml 中使用 resultMap 标签,如:
<!-- namespace要绑定一个mapper接口 -->
<mapper namespace="com.dzy.dao.UserMapper">
<!-- 结果集映射 typer="User"是因为给实体类起了别名所以不写全路径-->
<resultMap id="userMap" type="User">
<!-- 列是数据库的字段,property是类的属性 -->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="passwd"/>
</resultMap>
<!-- resultMap和上面的resultMap标签里的id一致 -->
<select id="getUserById" parameterType="int" resultMap="userMap">
select * from mybatis.user where id = #{id}
</select>
</mapper>
执行测试,结果正确:
User{id=1, name='dzy', pwd='123456'}
resultMap
元素是 MyBatis 中最重要最强大的元素- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- 变量名和字段名一样的,不需要配置映射,所以前面的xml可以少些两行。
<mapper namespace="com.dzy.dao.UserMapper">
<!-- 结果集映射 typer="User"是因为给实体类起了别名所以不写全路径-->
<resultMap id="userMap" type="User">
<!-- 列是数据库的字段,property是类的属性 -->
<result column="pwd" property="passwd"/>
</resultMap>
<!-- resultMap和上面的resultMap标签里的id一致 -->
<select id="getUserById" parameterType="int" resultMap="userMap">
select * from mybatis.user where id = #{id}
</select>
</mapper>
只需要映射 pwd 这个不一样的,id 和 name 的变量名和字段名一样,就不用映射了。
6.3、准备多表查询
首先新建一个 teacher 表
use `mybatis`;
create table `teacher`(
`id` int(11) auto_increment,
`name` varchar(30) default null,
primary key (`id`)
) engine = INNODB default charset = utf8
向 teacher 表中插入数据
insert into mybatis.teacher (`id`, `name`) VALUES (1, '老师A')
再新建一个 student 表
create table `student`(
`id` int(11) auto_increment,
`name` varchar(30) default null,
`tid` int(11) default null,
primary key (`id`),
key `fktid` (`tid`),
constraint `fktid` foreign key (`tid`) references `teacher` (`id`)
)
向 student 表中插入数据
insert into mybatis.student (id, name, tid) VALUES
(1, '小明', 1),
(2, '小红', 1),
(3, '小张', 1),
(4, '小李', 1),
(5, '小王', 1)
创建实体类 teacher ,使用 lombok 的注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
创建实体类 student
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
// 直接用引用关联,int在这里不合适
// 因为int总得有个值,那不关联应该写几呢
private Teacher teacher;
}
6.4、多对一的处理
刚刚建好的数据表中,多个学生对应一个老师,那么获取所有学生及其对应老师就是多对一的查询
6.4.1、按照查询嵌套处理
设计接口:
public interface StudentMapper {
// 查询所有学生和对应的老师
public List<Student> getStudents();
}
配置 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">
<!-- namespace要绑定一个mapper接口 -->
<mapper namespace="com.dzy.dao.StudentMapper">
<resultMap id="studentMap" type="Student">
<!-- id标签是特殊的result,只能用于主键映射,result通用 -->
<id property="id" column="id" />
<result property="name" column="name" />
<!-- result只能映射简单的类型,复杂的对象要单独处理 -->
<!-- 对象使用association,集合使用collection -->
<!-- javaType是子查询的返回类型,select是子查询的id -->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getStudents" resultMap="studentMap">
select * from mybatis.student
</select>
<select id="getTeacher" resultType="Teacher">
<!-- 这里#{}里可以随便写,最好和association标签里的colum一致 -->
select * from mybatis.teacher where id = #{tid}
</select>
</mapper>
测试:
public class StudentDaoTest {
@Test
public void TestGetStudents() {
try(SqlSession session = MybatisUtils.getSqlSession()) {
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudents();
for (Student s : studentList){
System.out.println(s);
}
}
}
}
结果:
Opening JDBC Connection
Created connection 1423768154.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@54dcfa5a]
==> Preparing: select * from mybatis.student
==> Parameters:
<== Columns: id, name, tid
<== Row: 1, 小明, 1
====> Preparing: select * from mybatis.teacher where id = ?
====> Parameters: 1(Integer)
<==== Columns: id, name
<==== Row: 1, 老师A
<==== Total: 1
<== Row: 2, 小红, 1
<== Row: 3, 小张, 1
<== Row: 4, 小李, 1
<== Row: 5, 小王, 1
<== Total: 5
Student(id=1, name=小明, teacher=Teacher(id=1, name=老师A))
Student(id=2, name=小红, teacher=Teacher(id=1, name=老师A))
Student(id=3, name=小张, teacher=Teacher(id=1, name=老师A))
Student(id=4, name=小李, teacher=Teacher(id=1, name=老师A))
Student(id=5, name=小王, teacher=Teacher(id=1, name=老师A))
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@54dcfa5a]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@54dcfa5a]
Returned connection 1423768154 to pool.
6.4.2、按照结果嵌套处理
<mapper namespace="com.dzy.dao.StudentMapper">
<resultMap id="studentMap" type="Student">
<!-- id标签是特殊的result,只能用于主键映射,result通用 -->
<id property="id" column="sid" />
<result property="name" column="sn" />
<!-- result只能映射简单的类型,复杂的对象要单独处理 -->
<!-- 对象使用association,集合使用collection -->
<!-- javaType是子查询的返回类型,select是子查询的id -->
<association property="teacher" javaType="Teacher">
<id property="id" column="tid"/>
<result property="name" column="tn" />
</association>
</resultMap>
<select id="getStudents" resultMap="studentMap">
select s.id sid, s.name sn, t.id tid, t.name tn from student s, teacher t where s.tid = t.id
</select>
</mapper>
这种方法只需要一个 sql,但是association标签里要写的东西多一些。
之前的方法对应子查询,这种方法对应联表查询。
6.5、一对多处理
6.5.1、按结果(联表)
首先修改实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
// 直接用引用关联,int在这里不合适
// 因为int总得有个值,那不关联应该写几呢
private Teacher teacher;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
// 一个老师关联多个学生
private List<Student> studentList;
}
然后是 DAO 接口 TeacherMapper.java
public interface TeacherMapper {
Teacher getTeacherById(@Param("tid") int id);
}
然后是 TeacherMapper.xml
<mapper namespace="com.dzy.dao.TeacherMapper">
<resultMap id="teacherMap" type="Teacher">
<id property="id" column="tid" />
<result property="name" column="tn"/>
<!-- 集合中的泛型使用ofType -->
<collection property="studentList" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sn"/>
</collection>
</resultMap>
<select id="getTeacherById" resultMap="teacherMap">
select t.id tid, t.name tn, s.id sid, s.name sn from teacher t, student s where t.id = s.tid and t.id = #{tid}
</select>
</mapper>
最后是测试类
public class TeacherDaoTest {
@Test
public void testSelectTeacherByID() {
try (SqlSession session = MybatisUtils.getSqlSession()) {
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
}
}
}
输出结果为
Teacher(id=1, name=老师A,
studentList=[Student(id=1, name=小明, teacher=null),
Student(id=2, name=小红, teacher=null),
Student(id=3, name=小张, teacher=null),
Student(id=4, name=小李, teacher=null),
Student(id=5, name=小王, teacher=null)])
所以,实体类完全可以相互持有。
6.5.2、按查询(子查询)
只需要修改 TeacherMapper.xml 为:
<mapper namespace="com.dzy.dao.TeacherMapper">
<resultMap id="teacherMap" type="Teacher">
<id property="id" column="id" />
<result property="name" column="name"/>
<!-- javaType是集合的类型,ofType是集合里的元素的类型 -->
<!-- colunm是传入参数的列名,绝对不能错 -->
<collection property="studentList" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTid"/>
</resultMap>
<select id="getTeacherById" resultMap="teacherMap">
select * from teacher where id = #{tid}
</select>
<select id="getStudentByTid" parameterType="int" resultType="Student">
select * from student where tid = #{tid}
</select>
</mapper>
返回结果是一样的
一般选择**按结果(联表)**的方法,因为只有一个 sql 方便调试。
6.6、小结
- association & collection
- javaType & ofType
- property & column
- 多个 sql & 一个 sql
高频问题:
- MySQL 引擎
- InnoDB 底层原理
- 索引
- 索引优化