系列博客目录:MyBatis从零开始博客目录
5. MyBatis高级查询
在关系型数据库中,我们经常要处理一对一、一对多的关系。例如:一个学生只能在一个班级,一个班级可以存在很多学生。
数据准备
-- ----------------------------
-- Table structure for `t_class_info`
-- ----------------------------
DROP TABLE IF EXISTS `t_class_info`;
CREATE TABLE `t_class_info` (
`class_id` int(11) NOT NULL AUTO_INCREMENT,
`class_name` varchar(255) DEFAULT NULL COMMENT '班级名称',
PRIMARY KEY (`class_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_class_info
-- ----------------------------
INSERT INTO `t_class_info` VALUES ('1', '一年级');
INSERT INTO `t_class_info` VALUES ('2', '二年级');
INSERT INTO `t_class_info` VALUES ('3', '三年级');
INSERT INTO `t_class_info` VALUES ('4', '四年级');
-- ----------------------------
-- Table structure for `t_student_info`
-- ----------------------------
DROP TABLE IF EXISTS `t_student_info`;
CREATE TABLE `t_student_info` (
`student_id` int(11) NOT NULL AUTO_INCREMENT,
`student_name` varchar(255) DEFAULT NULL,
`class_id` int(11) DEFAULT NULL,
PRIMARY KEY (`student_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_student_info
-- ----------------------------
INSERT INTO `t_student_info` VALUES ('1', '张三', '1');
INSERT INTO `t_student_info` VALUES ('2', '李四', '2');
INSERT INTO `t_student_info` VALUES ('3', '路人甲', '3');
INSERT INTO `t_student_info` VALUES ('4', '路人乙', '1');
5.1 一对一映射
场景:一个学生只能对应一个班级
5.1.1 使用自动映射处理一对一关系
学生信息类:StudentInfo.java
public class StudentInfo {
private Integer studentId;
private String studentName;
/**
* 所在班级
*/
private ClassInfo classInfo;
// 此处省略其它get、set方法
// 此处省略无参的构造方法
// 此处toString()方法
public ClassInfo getClassInfo() {
return classInfo;
}
public void setClassInfo(ClassInfo classInfo) {
this.classInfo = classInfo;
}
}
班级信息类:ClassInfo.java
public class ClassInfo {
private Integer classId;
private String className;
// 此处省略其它get、set方法
// 此处省略无参的构造方法
// 此处toString()方法
}
接口类:StudentInfoMapper.java
import org.apache.ibatis.annotations.Param;
import com.xiangty.bean.StudentInfo;
public interface StudentInfoMapper {
/**
* 根据学生id查询学生信息和班级信息
* @param studentId 学生id
* @return
*/
StudentInfo selectStudentAndClassById(@Param("studentId") Integer studentId);
}
StudentInfoMapper.xml
<select id="selectStudentAndClassById" resultType="com.xiangty.bean.StudentInfo">
select s.student_id,
s.student_name,
s.class_id,
c.class_name
from t_student_info s
left join t_class_info c
on c.class_id = s.class_id
where s.student_id = #{studentId}
</select>
单元测试类StudentInfoTest.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.xiangty.bean.StudentInfo;
import com.xiangty.mapper.StudentInfoMapper;
public class StudentInfoTest {
@Test
public void testSelectStudentAndClassById(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
try {
StudentInfoMapper studentInfoMapper = sqlSession.getMapper(StudentInfoMapper.class);
Integer studentId = 1;
StudentInfo studentInfo = studentInfoMapper.selectStudentAndClassById(studentId);
System.out.println(studentInfo);
} finally {
sqlSession.close();
}
}
}
输出结果:
DEBUG [main] - ==> Preparing: select s.student_id, s.student_name, s.class_id, c.class_name from t_student_info s left join t_class_info c on c.class_id = s.class_id where s.student_id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: student_id, student_name, class_id, class_name
TRACE [main] - <== Row: 1, 张三, 1, 一年级
DEBUG [main] - <== Total: 1
StudentInfo [studentId=1, studentName=张三, classInfo=null]
注意:上面的查询的信息中classInfo对象为空。需要在Mapper.xml文件中做如下改造
StudentInfoMapper.xml
<!--
c.class_id列后面的"classInfo.classId",这种是复杂的属性映射,可以多层嵌套。
MyBatis会先查找classInfo属性,如果存在这个属性就创建classInfo对象,然后继续查找classId属性,将c.class_id的值绑定到这个属性上面。
-->
<select id="selectStudentAndClassById" resultType="com.xiangty.bean.StudentInfo">
select s.student_id,
s.student_name,
c.class_id "classInfo.classId",
c.class_name "classInfo.className"
from t_student_info s
left join t_class_info c
on c.class_id = s.class_id
where s.student_id = #{studentId}
</select>
单元测试类StudentInfoTest.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.xiangty.bean.StudentInfo;
import com.xiangty.mapper.StudentInfoMapper;
public class StudentInfoTest {
@Test
public void testSelectStudentAndClassById(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
try {
StudentInfoMapper studentInfoMapper = sqlSession.getMapper(StudentInfoMapper.class);
Integer studentId = 1;
StudentInfo studentInfo = studentInfoMapper.selectStudentAndClassById(studentId);
System.out.println(studentInfo);
} finally {
sqlSession.close();
}
}
}
输出结果:
DEBUG [main] - ==> Preparing: select s.student_id, s.student_name, c.class_id "classInfo.classId", c.class_name "classInfo.className" from t_student_info s left join t_class_info c on c.class_id = s.class_id where s.student_id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: student_id, student_name, classInfo.classId, classInfo.className
TRACE [main] - <== Row: 1, 张三, 1, 一年级
DEBUG [main] - <== Total: 1
StudentInfo [studentId=1, studentName=张三, classInfo=ClassInfo [classId=1, className=一年级]]
通过控制台答应的日志看到查询出一条数据,MyBatis将这条数据映射到了班级和学生类中。这种通过一次查询将结果映射到不同对象的方式,称之为关联的嵌套结果映射。
关联的嵌套结果映射需要关联多个表,将所有需要的值一次性查询出来。这种方式的好处是减少数据库查询次数,减轻数据库的压力,缺点是要写复杂SQL。
5.1.2 使用resultMap配置一对一映射
除了使用MyBatis的自动映射来处理一对一嵌套外,还可以在XML映射文件中配置结果映射。使用resultMap进行配置也可以达到5.1.1中的效果,代码如下:
StudentInfoMapper.xml
<!--
association标签用于和一个负载的类型进行关联,即用于一对一的关联配置。
学生和班级是一对一的,所以在学生的resultMap里面加上association标签配置的班级信息即可。
property属性表示的是实体类中的属性名称;
column属性表示的是对应的数据库中字段的名称。
-->
<resultMap type="com.xiangty.bean.StudentInfo" id="studentMap">
<id property="studentId" column="student_id"/>
<result property="studentName" column="student_name"/>
<association property="classInfo" javaType="com.xiangty.bean.ClassInfo">
<result property="classId" column="class_id"/>
<result property="className" column="class_name"/>
</association>
</resultMap>
<!-- 注意这个方法使用resultMap配置映射,所以放回值不能用resultType来设置,而是需要使用resultMap属性将其配置为上面的studentMap -->
<select id="selectStudentAndClassById2" resultMap="studentMap">
select s.student_id,
s.student_name,
s.class_id,
c.class_name
from t_student_info s
left join t_class_info c
on c.class_id = s.class_id
where s.student_id = #{studentId}
</select>
单元测试类StudentInfoTest.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.xiangty.bean.StudentInfo;
import com.xiangty.mapper.StudentInfoMapper;
public class StudentInfoTest {
@Test
public void testSelectStudentAndClassById2(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
try {
StudentInfoMapper studentInfoMapper = sqlSession.getMapper(StudentInfoMapper.class);
Integer studentId = 1;
StudentInfo studentInfo = studentInfoMapper.selectStudentAndClassById2(studentId);
System.out.println(studentInfo);
} finally {
sqlSession.close();
}
}
}
输出结果:
DEBUG [main] - ==> Preparing: select s.student_id, s.student_name, s.class_id, c.class_name from t_student_info s left join t_class_info c on c.class_id = s.class_id where s.student_id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: student_id, student_name, class_id, class_name
TRACE [main] - <== Row: 1, 张三, 1, 一年级
DEBUG [main] - <== Total: 1
StudentInfo [studentId=1, studentName=张三, classInfo=ClassInfo [classId=1, className=一年级]]
5.2 一对多映射
一对多关系映射的标签collection和association类似,结合的嵌套结果映射就是指通过一次SQL查询将所有的结果查询出来,然后通过配置的结果映射,将数据映射到不同的对象中去。
一对多的场景,一个班级包含多个学生:
ClassInfo.java 新增学生集合studentInfoList
package com.xiangty.bean;
import java.util.List;
/**
* 班级信息
* @author xiangty
*/
public class ClassInfo {
private Integer classId;
private String className;
// 学生集合
List<StudentInfo> studentInfoList;
// 此处省略其它get、set方法
// 此处省略无参的构造方法
// 此处toString()方法
}
ClassInfoMapper.java
package com.xiangty.mapper;
import org.apache.ibatis.annotations.Param;
import com.xiangty.bean.ClassInfo;
public interface ClassInfoMapper {
/**
* 根据班级id查询班级和学生信息
* @param classId 班级id
* @return
*/
ClassInfo selectClassInfoAndStudentById(@Param("classId") Integer classId);
}
ClassInfoMapper.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.xiangty.mapper.ClassInfoMapper">
<!--
collection标签用于和一个负载的类型进行关联,即用于一对多的关联配置。
班级和学生是一对多的,所以在班级的resultMap里面加上collection标签配置的班级信息即可。
property属性表示的是实体类中的属性名称;
column属性表示的是对应的数据库中字段的名称。
-->
<resultMap type="com.xiangty.bean.ClassInfo" id="classMap">
<id property="classId" column="class_id"/>
<result property="className" column="class_name"/>
<collection property="studentInfoList" ofType="com.xiangty.bean.StudentInfo">
<result property="studentId" column="student_id"/>
<result property="studentName" column="student_name"/>
</collection>
</resultMap>
<!-- 注意这个方法使用resultMap配置映射,所以放回值不能用resultType来设置,而是需要使用resultMap属性将其配置为上面的studentMap -->
<select id="selectClassInfoAndStudentById" resultMap="classMap">
select s.student_id,
s.student_name,
s.class_id,
c.class_name
from t_class_info c
left join t_student_info s
on s.class_id = c.class_id
where c.class_id = #{classId}
</select>
</mapper>
ClassInfoTest.java
package com.xiangty.test;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.xiangty.bean.ClassInfo;
import com.xiangty.mapper.ClassInfoMapper;
public class ClassInfoTest {
@Test
public void selectClassInfoAndStudentById(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
try {
ClassInfoMapper classInfoMapper = sqlSession.getMapper(ClassInfoMapper.class);
Integer classId = 1;
ClassInfo classInfo = classInfoMapper.selectClassInfoAndStudentById(classId);
System.out.println(classInfo);
} finally {
sqlSession.close();
}
}
}
运行效果:
DEBUG [main] - ==> Preparing: select s.student_id, s.student_name, s.class_id, c.class_name from t_class_info c left join t_student_info s on s.class_id = c.class_id where c.class_id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: student_id, student_name, class_id, class_name
TRACE [main] - <== Row: 1, 张三, 1, 一年级
TRACE [main] - <== Row: 4, 路人乙, 1, 一年级
DEBUG [main] - <== Total: 2
ClassInfo [classId=1, className=一年级, studentInfoList=[StudentInfo [studentId=1, studentName=张三, classInfo=null], StudentInfo [studentId=4, studentName=路人乙, classInfo=null]]]
如果文档中有任何问题,可以直接联系我,便于我改正和进步。希望文档对您有所帮助。文档中代码GitHub地址:https://gitee.com/xiangty1/learn-MyBatis/