MyBatis关联查询

226 阅读7分钟

一对一查询

MyBatis 实现 “一对一查询” 的两种方法:扩展类及resultMap,扩展类就是写一个扩展类来映射查询结果,不方便直接舍弃。

先准备sql语句。
CREATE DATABASE IF NOT EXISTS `MyBatisDemo`;

USE `MyBatisDemo`;
DROP TABLE IF EXISTS `studentCard`;

CREATE TABLE `studentCard`
(
    `cardId`   int(10) NOT NULL,
    `cardInfo` varchar(50) NOT NULL,
    PRIMARY KEY (`cardId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `studentCard` values (1000, '张三是清华大学计算机学院的');
insert into `studentCard` values (1001, '李四是北京大学管理学院的');
insert into `studentCard` values (1002, '颜群是复旦大学经管学院的');
insert into `studentCard` values (1003, '王五是西南交通大学数学学院的');
insert into `studentCard` values (1004, '赵六是电子科技大学英语学院的');
insert into `studentCard` values (1005, '孙琪是中国科学技术大学计算机学院的');

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student`
(
    `stuNo`   int(10) NOT NULL,
    `stuName` varchar(50) NOT NULL,
    `stuAge`  tinyint,
    `graName` varchar(50) NOT NULL,
        `cardId`  int(10) NOT NULL,
    PRIMARY KEY (`stuNo`),
    KEY `student_cardId` (`cardId`),
    CONSTRAINT `student_cardId` FOREIGN KEY (cardId) REFERENCES studentCard(cardId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `student` values (31, '张三', 23, '就业班', 1000);
insert into `student` values (32, '李四', 24, '初级', 1001);
insert into `student` values (33, '颜群', 28, '就业班', 1002);
insert into `student` values (34, '王五', 25, '初级', 1003);
insert into `student` values (36, '赵六', 26, '中级', 1004);
insert into `student` values (37, '孙琪', 27, '中级', 1005);

使用 resultMap 实现一对一查询 要实现学生和学生证之间的一对一查询,将学生和学生证设计成两个类, 然后在学生类中添加一个学生证类型的成员变量(当然,也可以反过来)。

实体类
public class StudentCard{
    //学生证号
    private int cardId;
    //学生证的相关信息
    private String cardInfo;
    //getter、setter
    public int getCardId() {
        return this.cardId;
    }

    public void setCardId(int cardId) {
        this.cardId = cardId;
    }

    public String getCardInfo() {
        return this.cardInfo;
    }

    public void setCardInfo(String cardInfo) {
        this.cardInfo = cardInfo;
    }
}

public class Student{
    private int stuNo;
    private String stuName;
    private int stuAge;
    //学生证
    private StudentCard card ;
    ...
    //setter、getter
    public StudentCard getCard() {
        return this.card;
    }

    public void setCard(StudentCard card) {
        this.card = card;
    }
}
SQL 映射文件StudentMapper.xml

select标签中 resultMap属性是自己写的结果集映射,属性值对应resultMap标签中的id属性值,必须一致。 resultMap的一个作用,就是用来解决实体类属性名和表字段名不一致的问题。resultMap 的另一个作用,就是用来处理关联查询。 association标签中 property="card"是学生类中的一个属性名,javaType="StudentCard"是实体类

<!-- 用resultMap,实现学生表和学生证表的一对一查询 -->
    <select id="queryStudentAndCardByStuNoWithResultMap"
        parameterType="int" resultMap="student_card_map">
        select s.*,c.* from student s
            inner join studentCard c
            on  s.cardId = c.cardId
            where s.stuNo = #{stuNo}
    </select>

    <resultMap type="Student" id="student_card_map">
        <!-- property:实体类的属性名称  column:字段名称  -->
        <id property="stuNo" column="stuNo"/>
        <result property="stuName" column="stuName"/>
        <result property="stuAge" column="stuAge"/>
        <result property="graName" column="graName"/>
        <!-- association: 就是用来一对一查询,关联级联属性-->
        <association property="card" javaType="StudentCard">
            <id property="cardId" column="cardId"/>
            <result property="cardInfo" column="cardInfo"/>
        </association>
    </resultMap>
代理接口StudentMapper.java
public interface StudentMapper{
        //方法名和xml中的id属性值必须一致
       Student queryStudentAndCardByStuNoWithResultMap(int stuNo);
}
测试
@Test
public void queryStudentAndCardByStuNoWithResultMap()
throws IOException{
    …
    SqlSession session = sessionFactory.openSession();
    StudentMapper studentMapper = session.getMapper(StudentMapper.class);
    Student student = studentMapper.queryStudentAndCardByStuNoWithResultMap(32);
    …
}

以上,就是使用resultMap中的association子标签实现一对一关联查询的具体步骤。

一对多查询

sql
CREATE DATABASE IF NOT EXISTS `MyBatisDemo`;

USE `MyBatisDemo`;

DROP TABLE IF EXISTS `studentClass`;

CREATE TABLE `studentClass`
(
    `classId`   int(10) NOT NULL,
    `className` varchar(50) NOT NULL,
    PRIMARY KEY (`classId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `studentClass` values (1, 'Java 班');
insert into `studentClass` values (2, 'IOS 班');
insert into `studentClass` values (3, 'HTML5 班');

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student`
(
    `stuNo`   int(10) NOT NULL,
    `stuName` varchar(50) NOT NULL,
    `stuAge`  tinyint,
    `graName` varchar(50) NOT NULL,
    `classId`  int(10) NOT NULL,
    PRIMARY KEY (`stuNo`),
    KEY `student_classId` (`classId`),
    CONSTRAINT `student_classId` FOREIGN KEY (classId) REFERENCES studentClass(classId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `student` values (31, '张三', 23, '就业班', 1);
insert into `student` values (32, '李四', 24, '初级', 2);
insert into `student` values (33, '颜群', 28, '就业班', 3);
insert into `student` values (34, '王五', 25, '初级', 1);
insert into `student` values (36, '赵六', 26, '中级', 2);
insert into `student` values (37, '孙琪', 27, '中级', 3);
实体类
public class StudentClass{
     // 班级id
    private int classId ;
     // 班级名称
    private String className;
    //setter、getter
    public int getClassId() {
        return this.classId;
    }

    public void setClassId(int classId) {
        this.classId = classId;
    }

    public String getClassName() {
        return this.className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

public class StudentClass{
    private int classId ;
    private String className;
    // 班级中的学生信息
    private List<Student> students ;
    //setter、getter
    public int getClassId() {
        return this.classId;
    }

    public void setClassId(int classId) {
        this.classId = classId;
    }

    public String getClassName() {
        return this.className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public List<Student> getStudents() {
        return this.students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }
}
SQL 映射文件 StudentMapper.xml

通过select执行一对多的查询 SQL,并将查询结果通过resultMap映射到 StudentClass 类中的各个属性中:普通类型通过id、result映射,List 类型的属性 students 通过collection映射,并通过 ofType 指定 List 中元素的类型。

    <select id="queryClassAndStudentsByClassId" parameterType="int" resultMap="classAndStudentMap">
        select s.*,sc.* from student s
                inner join studentClass sc
                on s.classid=sc.classid
                where sc.classid = #{classId}
    </select>

    <resultMap type="studentClass" id="classAndStudentMap">
        <id property="classId" column="classId" />
        <result property="className" column="className" />

        <collection property="students" ofType="student">
             <id property="stuNo" column="stuNo" />
             <result property="stuName" column="stuName" />
             <result property="stuAge" column="stuAge" />
             <result property="graName" column="graName" />
        </collection>
    </resultMap>
动态代理接口 StudentMapper.java
    <select id="queryClassAndStudentsByClassId" parameterType="int" resultMap="classAndStudentMap">
        select s.*,sc.* from student s
        inner join studentClass sc
        on s.classid=sc.classid
        where sc.classid = #{classId}
    </select>

    <resultMap type="studentClass" id="classAndStudentMap">
        <id property="classId" column="classId" />
        <result property="className" column="className" />

        <collection property="students" ofType="student">
            <id property="stuNo" column="stuNo" />
            <result property="stuName" column="stuName" />
            <result property="stuAge" column="stuAge" />
            <result property="graName" column="graName" />
        </collection>
    </resultMap>
测试
    @Test
    public void queryClassAndStudentsByClassId() throws IOException{
        …
        IStudentMapper studentMapper = session.getMapper(IStudentMapper.class);
        StudentClass stuclass = studentMapper.queryClassAndStudentsByClassId(1);
        …
    }

多对多查询

多对多查询,它的本质就是两个一对多的组合,同样它所使用的标签元素也是 collection

sql
CREATE DATABASE IF NOT EXISTS `MyBatisDemo`;

USE `MyBatisDemo`;

DROP TABLE IF EXISTS `course`;

CREATE TABLE `course`
(
    `courseNo`   int(10) NOT NULL,
    `courseName` varchar(50) NOT NULL,
    PRIMARY KEY (`courseNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `course` values (1001, 'Java基础');
insert into `course` values (1002, 'python');
insert into `course` values (1003, 'Web前端');
insert into `course` values (1004, 'C++');
insert into `course` values (1005, 'C语言');
insert into `course` values (1006, 'Oracle');


DROP TABLE IF EXISTS `student`;

CREATE TABLE `student`
(
    `stuNo`   int(10) NOT NULL,
    `stuName` varchar(50) NOT NULL,
    `stuAge`  tinyint,
    `graName` varchar(50) NOT NULL,
    PRIMARY KEY (`stuNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `student` values (31, '张三', 23, '就业班');
insert into `student` values (32, '李四', 24, '初级');
insert into `student` values (33, '颜群', 28, '就业班');
insert into `student` values (34, '王五', 25, '初级');
insert into `student` values (35, '赵六', 26, '中级');
insert into `student` values (36, '孙琪', 27, '中级');

DROP TABLE IF EXISTS `studentCourse`;

CREATE TABLE `studentCourse`
(
    `stuNo`   int(10) NOT NULL,
    `courseNo` int(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `studentCourse` values (31,1001);
insert into `studentCourse` values (31,1002);
insert into `studentCourse` values (32,1001);
insert into `studentCourse` values (32,1003);
insert into `studentCourse` values (33,1004);
insert into `studentCourse` values (33,1003);
insert into `studentCourse` values (34,1005);
insert into `studentCourse` values (34,1004);
insert into `studentCourse` values (35,1006);
insert into `studentCourse` values (35,1002);
insert into `studentCourse` values (36,1006);
insert into `studentCourse` values (36,1005);
实体类
public class Student{
    ...
    private int stuNo;
    private String stuName;
    //在学生类Student中保留课程信息
    private List<Course> courses;

    public List<Course> getCourses() {
        return this.courses;
    }

    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }
    ...
    public class Course{
        private int courseNo;
        private String courseName;
        //在课程类Course类中保留学生信息
        private List<Student> students;

        public int getCourseNo() {
            return this.courseNo;
        }

        public void setCourseNo(int courseNo) {
            this.courseNo = courseNo;
        }

        public String getCourseName() {
            return this.courseName;
        }

        public void setCourseName(String courseName) {
            this.courseName = courseName;
        }

        public List<Student> getStudents() {
            return this.students;
        }

        public void setStudents(List<Student> students) {
            this.students = students;
        }

        @Override
        public String toString()    {
            return   "课程编号:"+this.courseNo+"\t课程名称:"+this.courseName;
        }
    ...
    }
}
SQL 映射文件 StudentMapper.xml
    <!-- 多对多的查询 -->
    <select id="queryStudentsAndCourses" resultMap="studentsAndCoursesMap">
    select s.*,sc.*,c.* from student s left join studentCourse sc on s.stuNo=sc.stuNo
         left join course c on c.courseNo=sc.courseNo
    </select>

    <resultMap type="student" id="studentsAndCoursesMap">
            <id property="stuNo" column="stuNo" />
            <result property="stuName" column="stuName" />
            <result property="stuAge" column="stuAge" />
            <result property="graName" column="graName" />
            <collection property="courses" ofType="Course">
                   <id property="courseNo" column="courseNo" />
                   <result property="courseName" column="courseName" />
            </collection>
    </resultMap>
动态代理接口 IStudentMapper.java
public interface IStudentMapper{
      …
    List<Student> queryStudentsAndCourses();
}
测试
    @Test
    public void testQueryStudentByStuNoWithConverter() throws IOException{
        String resource = "mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sessionFactory.openSession();
        IStudentMapper studentMapper = session.getMapper(IStudentMapper.class);

        List<Student> students = studentMapper.queryStudentsAndCourses();

        for (Student student : students) {
            System.out.println(student+"\n"+student.getCourses());
        }
        session.close();
    }

延迟加载

现在需要通过延迟加载来实现:员工表多对一关联查询部门表,要求默认只查询员工表, 只有当需要时再查询部门表,即延迟加载查询部门表。 首先要开启延迟加载的相关配置,在mybatis-config.xml文件中的settings标签中添加下面的配置。

    <!-- 将延迟加载设置为true(可省略,因为默认值就是true)-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将立即加载设置为false -->
        <setting name="aggressiveLazyLoading" value="false"/>

然后再编写mapper.xml文件

    //DepartmentMapper.xml 中的查询标签
    <select id="queryDepartment" parameterType="int" resultType="department">
        select * from department where dNo=#{dNo}
    </select>

    //EmployeeMapper.xml 中的查询标签以及
        <!-- 实现多对一下的延迟加载 -->
    <resultMap id="map" type="Employee" autoMapping="true">
        <id column="eNo" property="eNo"/>
        <!--
          select:通过 namespace+id 指定延迟加载执行的SQL语句
          column:给方法传递的字段值
        -->
        <association property="department" javaType="Department" column="dNo"
             select="com.test.dao.DepartmentMapper.queryDepartment"/>
    </resultMap>

    <select id="queryEmployeeByEno" resultMap="map">
        select * from employee where eNo=#{eNo}
    </select>

测试

当不执行查询部门时就只会查询员工信息如下:

==>  Preparing: select * from employee where eNo=? 
==> Parameters: 20100101(String)
<==    Columns: eNo, dNo, jNo, eName, cardId, sex, phone, email
<==        Row: 20100101, 0001, 1001, 令狐冲, 321456987989674563, 男, 16678890788, 0101@126.com
<==      Total: 1

当需要查询部门信息时System.out.println(employee)就会出现如下效果:

==>  Preparing: select * from employee where eNo=?
==> Parameters: 20100101(String)
<==    Columns: eNo, dNo, jNo, eName, cardId, sex, phone, email
<==        Row: 20100101, 0001, 1001, 令狐冲, 321456987989674563, 男, 16678890788, 0101@126.com
<==      Total: 1
==>  Preparing: select * from department where dNo=?
==> Parameters: 1(Integer)
<==    Columns: dNo, dName, mgrNo
<==        Row: 0001, 技术部, 20100101
<==      Total: 1
Employee{eNo='20100101', dNo='null', jNo='1001', eName='令狐冲', cardId='321456987989674563', sex='男', phone='16678890788', email='0101@126.com', department='Department{dNo='0001', dName='技术部', mgrNo='20100101', jobs=null}'}
    @Test
    public void queryEmployeeByEno() throws Exception{
        // 获取接口对象
        EmployeeMapper eMapper = MybatisUtil.getSqlSession().getMapper(EmployeeMapper.class);
        // 调用方法
        Employee employee = eMapper.queryEmployeeByEno("20100101");
//         System.out.println(employee);
    }