resultMap 结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素,之前所写的 sql 语句,返回值都是简单的基本数据类型或者某一个实体类,比如下面这段 sql 返回的就是最简单的 User 类型。
<select id="getUserById" resultType="user" parameterType="int">
select * from user where id=#{id};
</select>
现在思考一下下面这种情况,如果实体类中定义的某一个字段和数据库中的字段不一致怎么办?
public class User {
private int id;
private String lastname;
//.....
}
比如我定义了一个 User 类,包含 id 和 lastname 两个属性,而数据库中这两个字段的名字为 id 和 name。此时再执行查询时结果如下:lastname 这个字段直接为 null。
这时候我们就可以使用 resultMap 来解决这个问题,resultMap 可以将数据库中的字段映射到实体类上。column 代表数据库中的字段名,properties 代表实体类中的字段名,通过映射之后 Mybatis 就可以找到对应的字段。
<resultMap id="UserMap" type="User">
<!--column代表数据库中的字段名,properties代表实体类中的字段名-->
<result column="id" property="id"/>
<result column="name" property="lastname"/>
</resultMap>
<select id="getUserById" resultMap="UserMap" parameterType="int">
select * from user where id=#{id};
</select>
准备数据
接下来结合学生与教室的案例模拟复杂场景:
//创建教室表
create table classroom
(
id int not null AUTO_INCREMENT,
classname VARCHAR(40) not null,
PRIMARY KEY (id)
);
//创建学生表
create table student
(
id int not null AUTO_INCREMENT,
name VARCHAR(40) not null,
classid int not null,
PRIMARY KEY (id),
FOREIGN key (classid) REFERENCES classroom(id)
);
//创建一些数据
insert into classroom VALUES (1,'101班');
insert into classroom VALUES (2,'102班');
insert into student VALUES(1,'Amy',1);
insert into student VALUES(2,'Bob',1);
insert into student VALUES(3,'javayz',1);
多对一查询(association)
现在要实现一个多对一的查询需求,查询所有的学生,并将每个学生所在的教室包含在内。由于现在的情况是多学生和教室的关系是多对一,因此在构建实体类时在 Student 类上要加上 ClassRoom 变量。
在 Java 的实体类代码中分别建立 Student 和 ClassRoom 的类:
package com.cn.pojo;
public class ClassRoom {
private int id;
private String classname;
public ClassRoom(){}
public int getId() {
return id;
}
public String getClassname() {
return classname;
}
public void setId(int id) {
this.id = id;
}
public void setClassname(String classname) {
this.classname = classname;
}
@Override
public String toString() {
return "ClassRoom{" +
"id=" + id +
", classname='" + classname + '\'' +
'}';
}
}
package com.cn.pojo;
public class Student {
private int id;
private String name;
private ClassRoom classRoom;
public Student(){}
public int getId() {
return id;
}
public String getName() {
return name;
}
public ClassRoom getClassRoom() {
return classRoom;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setClassRoom(ClassRoom classRoom) {
this.classRoom = classRoom;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", classRoom=" + classRoom +
'}';
}
}
定义一个 StudentMapper 接口:
package com.cn.mapper;
import java.util.List;
import com.cn.pojo.Student;
public interface StudentMapper {
List<Student> selectAllStudent();
}
编写 StudentMapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cn.mapper.StudentMapper">
<select id="selectAllStudent" resultMap="StudentAndClassRoom">
select s.id sid,s.name sname,c.id cid,c.classname cname
from student s,classroom c
where s.classid=c.id
</select>
<resultMap id="StudentAndClassRoom" type="com.cn.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="classRoom" javaType="com.cn.pojo.ClassRoom">
<result property="id" column="cid"/>
<result property="classname" column="cname"/>
</association>
</resultMap>
</mapper>
上面的这种 sql 编写模式称为结果嵌套查询,首先通过一段 sql 查询语句将需要的信息查询出来,接着通过 resultMap 对结果进行拼接。这里使用 association 将 classRoom 的信息拼接到了 classRoom 类中,实现多对一查询。
别忘了在配置类里把 mapper 映射加上,编写测试类:
public class StudentMapperTest {
public static void main(String[] args) {
// 获取SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectAllStudent();
System.out.println(students);
}
}
一对多查询(collection)
一个教室里有多个学生,如果想要查询每个教室中的所有学生,就会用到一对多查询。
修改两个实体类,命名为 Student2 和 ClassRoom2,因为一个教室中有多个学生,因此在教室类中通过 List<Student2> 的方式引入 Student2 类
public class Student2 {
private int id;
private String name;
private int classId;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", classId=" + classId +
'}';
}
}
import java.util.List;
public class ClassRoom2 {
private int id;
private String classname;
private List<Student2> students;
@Override
public String toString() {
return "ClassRoom{" +
"id=" + id +
", classname='" + classname + '\'' +
", students=" + students +
'}';
}
}
接着编写 Mapper 接口和对应的 Mapper.xml
import java.util.List;
import com.cn.pojo.ClassRoom2;
public interface ClassRoomMapper {
List<ClassRoom2> getClassRoomByid( int id);
}
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cn.mapper.ClassRoomMapper">
<select id="getClassRoomByid" resultMap="ClassRoomAndStudent" parameterType="int">
select c.id cid,c.classname cname,s.id sid,s.name sname,s.classid classid
from student s,classroom c
where s.classid=c.id and c.id=#{id}
</select>
<resultMap id="ClassRoomAndStudent" type="com.cn.pojo.ClassRoom2">
<result property="id" column="cid"/>
<result property="classname" column="cname"/>
<!--对于集合属性,需要使用collection来实现-->
<collection property="students" ofType="com.cn.pojo.Student2">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="classId" column="classid"/>
</collection>
</resultMap>
</mapper>
依旧是通过结果嵌套查询的方式,通过 sql 语句查询出结果,再通过 resultMap 进行组装,一对多查询用的是 collection。
在配置文件中增加 mapper 映射器之后,编写一个测试类:
public class TeacherMapperTest {
public static void main(String[] args) {
// 获取SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
ClassRoomMapper mapper = sqlSession.getMapper(ClassRoomMapper.class);
List<ClassRoom2> classRoom = mapper.getClassRoomByid(1);
System.out.println(classRoom);
}
}
总结成一点:对象的关联(多对一)使用 association,集合的关联(一对多)使用 collection。
懒加载
在上面的两个例子中,一次 sql 查询就将两个表的数据一次性查询了出来,这种方式就是即时加载。但是在某些业务场景下,可能只需要学生的信息或者教室的信息,而不需要两者的联表数据,这种时候就可以使用懒加载。
以上边的 association 案例解释懒加载的实现。
上边的例子中,通过联表查询一次性就查询出了学生信息和教室信息:
select s.id sid,s.name sname,c.id cid,c.classname cname
from student s,classroom c
where s.classid=c.id
如果想要通过懒加载实现,就需要把 sql 语句转换为:
select * from student;
select * from classroom where id = #{classid}
按照这个思路,建立 StudentLazyMapper 类:
package com.cn.mapper;
import java.util.List;
import com.cn.pojo.Student;
public interface StudentLazyMapper {
List<Student> selectAllStudent();
}
创建对应的 StudentLazyMapper.xml 文件,将原先的一条 sql 转换为两条 sql:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cn.mapper.StudentLazyMapper">
<select id="selectAllStudent" resultMap="studentAndClassRoom">
select * from student
</select>
<resultMap id="studentAndClassRoom" type="com.cn.pojo.Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="classRoom" javaType="com.cn.pojo.ClassRoom" column="classid" select="selectClassRoomById">
<result property="id" column="id"/>
<result property="classname" column="classname"/>
</association>
</resultMap>
<select id="selectClassRoomById" resultType="com.cn.pojo.ClassRoom">
select * from classroom where id = #{classid}
</select>
</mapper>
在 mybatis-config.xml 中增加 mapper 映射,为了更好地看到懒加载效果,开启控制台日志输出,完整 xml 如下:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<!--每个mapper.xml都需要在mybatis配置文件中进行配置-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/StudentMapper.xml"/>
<mapper resource="mapper/StudentLazyMapper.xml"/>
</mappers>
</configuration>
新建一个测试类 StudentMapperLazyTest:
public class StudentMapperLazyTest {
public static void main(String[] args) {
// 获取SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentLazyMapper mapper = sqlSession.getMapper(StudentLazyMapper.class);
List<Student> students = mapper.selectAllStudent();
System.out.println(students.get(0).getId());
}
}
这个时候是还没开启懒加载的,从运行结果可以看出,虽然代码中只需要得到 student 的 id,但是却查询了两张表:
在配置文件的 setting 节点下开启懒加载的配置:
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
再次运行测试代码:
可以看到,只有 student 一张表被查询,实现了懒加载