MyBatis——对象关系映射、延迟加载、关联对象的配置选择

目录

  • 多对一关系
    • 1、保存员工、部门信息
    • 2、根据id查询员工信息
    • 3、额外SQL的N+1问题
    • 4、内联映射(处理结果集和对象属性的不匹配)
    • 5、总结 (多对一配置)
  • 一对多关系
    • 1、保存员工、部门信息
    • 2、根据id查询员工信息
    • 3、内联映射(处理结果集和对象属性的不匹配)
  • 延迟加载
  • 关联对象的配置选择
  • 多对多关系
    • 1、保存学生、老师、中间表的信息
    • 2、删除指定id的学生
    • 3、查询指定id的学生(包括该id学生的老师)

在这里插入图片描述

MyBatis源码及资料: https://github.com/coderZYGui/MyBatis-Study

MyBatis系列

  1. MyBatis — ORM思想、MyBatis概述、日志框架、OGNL
  2. MyBaits — MyBatis的CRUD操作、别名配置、属性配置、查询结果映射、Mapper组件、参数处理、注解开发
  3. MyBatis — 动态SQL、if、where、set、foreach、sql片段
  4. MyBatis — 对象关系映射、延迟加载、关联对象的配置选择
  5. MyBatis — 缓存机制、EhCache第三方缓存
  6. MyBatis — MyBatis Generator插件使用(配置详解)

方便了解 关联关系的各种关系,请先参考: UML一一 类图关系 (泛化、实现、依赖、关联、聚合、组合)

多对一(关系在多的一方)

跳转到目录

  • 案例: 多个员工对象属于同一个部门对象
  • 1、保存员工部门信息
  • 2、根据id来查询员工信息
  • 3、查询所有用户
表设计
CREATE TABLE `employee1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `dept_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
CREATE TABLE `department1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8;
模型对象设计

Employee类
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第1张图片
Department类
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第2张图片

1、保存员工、部门信息

跳转到目录

mapper

EmployeeMapper

	// 保存员工信息
	void save(Employee emp);

	
    <insert id="save" useGeneratedKeys="true" keyProperty="id">
        
        INSERT INTO employee1(name, dept_id) VALUES (#{name}, #{dept.id})
    insert>

DepartmentMapper

	// 保存一个部门
	void save(Department dept);

	
	<insert id="save" useGeneratedKeys="true" keyProperty="id">
		INSERT INTO department1(name) VALUES (#{name})
	insert>
	
测试类
    /**
     * 测试保存操作
     */
    @Test
    public void test() {
        // 创建部门对象
        Department dept = new Department();
        dept.setName("开发部");
        // 创建员工对象
        Employee emp = new Employee();
        emp.setName("张三");
        emp.setDept(dept);  // 将张三分配到开发部中
        Employee emp2 = new Employee();
        emp2.setName("李四");
        emp2.setDept(dept); // 将李四分配到开发部中

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);

        // 保存操作:先保存部门(one方)因为,员工表中需要依赖于部门ID,先保存员工信息,部门信息可能为空
        departmentMapper.save(dept);
        employeeMapper.save(emp);
        employeeMapper.save(emp2);

        sqlSession.commit();
        sqlSession.close();
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第3张图片

2、根据id查询员工信息

跳转到目录
通过额外SQL

mapper

EmployeeMapper

	// 根据id来查询员工信息
	Employee getUserById(long empId);

	
    <resultMap id="BaseResultMap" type="Employee">
        <result column="id" property="id" />
        <result column="name" property="name" />
        <result column="dept_id" property="dept.id"/>
    resultMap>
	
    <select id="getUserById" resultMap="BaseResultMap">
        
        SELECT id, name, dept_id FROM employee1 WHERE id = #{id}
    select>

DepartmentMapper

    // 根据部门的id查询出部门对象
    Department getDeptById(Long deptId);

	
    <select id="getDeptById" resultType="Department">
        SELECT id, name FROM department1 WHERE id = #{id}
    select>
测试类

因为获取的结果集中的列名和对象的属性名不匹配,设置resultMap解决不匹配问题,
但是结果集中的id列,不能和对象中的dept对象匹配,只能和对象dept的属性id匹配
所以,能获取到部门的id,但是期望得到的是Department对象

解决方案: 根据部门的id,再 额外 发送一条SQL语句,查询出 部门对象 ,把部门对象设置给员工即可!

	/**
     * 根据id来查询员工信息
     */
    @Test
    public void test1() {

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
        Employee employee = employeeMapper.getUserById(2L);

        // -------------------------------------
        /*
            因为获取的结果集中的列名和对象的属性名不匹配,设置resultMap解决不匹配问题,
               但是结果集中的id列,不能和对象中的dept对象匹配,只能和对象dept的属性id匹配
               所以,能获取到部门的id,但是期望得到的是Department对象
            解决方案: 根据部门的id,再 额外 发送一条SQL语句,查询出 部门对象 ,把部门对象设置给员工即可!
         */

        // 下面三行代码让,MyBatis完成,需要在resultMap中配置
        Long deptId = employee.getDept().getId();
        // 根据部门id来查询出,部门对象
        Department department = departmentMapper.getDeptById(deptId);
        employee.setDept(department);

        System.out.println(employee);
        System.out.println(employee.getDept());
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第4张图片
在测试方法中不想自己手动发额外的SQL怎么办呢?MyBatis可以帮我们完成,这就需要在resultMap中配置一下

额外SQL的配置方式

  • association元素: 配置单一元素(非数组、集合)的关联对象
    • property属性: 关联对象的属性名; 这里跟的就是Employee中的属性名称
    • javaType属性: 关联对象属性类型
    • select属性: 发送额外的SQL
    • column属性: 指定列的值,传递给SQL语句

    <resultMap id="BaseResultMap" type="Employee">
        <result column="id" property="id" />
        <result column="name" property="name" />
        <result column="dept_id" property="dept.id"/>
        
        <association property="dept" javaType="Department"
                     select="com.sunny.mapper.StudentMapper.getDeptById"
                     column="dept_id"
        />
    resultMap>

这样以来,就不用了在测试方法中不用自己去写了,实际操作是一样的,只是把额外发送的SQL封装配置起来了.
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第5张图片

3、额外SQL的N+1问题

跳转到目录
假如每一个员工的部门ID都是不同的, 查询所有员工信息, 此时就会发生N+1问题, 其实就是发送了N+1条SQL语句
1 : SELECT * FROM employee
N: SELECT * FROM department WHERE id = ?

当查询所有员工时,如下图:
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第6张图片
解决方案: 使用多表查询(JSON), 一条SQL语句就搞定

4、内联映射(处理结果集和对象属性的不匹配)

跳转到目录
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第7张图片

mapper

EmployeeMapper

	//查询所有用户信息(包括部门)
	List<Employee> getAllUser();

    
    <resultMap id="BaseResultMap" type="Employee">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        
        
        
        
        
        
        <association property="dept" javaType="Department">
            <result column="d_id" property="id"/>
            <result column="d_name" property="name"/>
        association>

    resultMap>

    
    <select id="getAllUser" resultMap="BaseResultMap">
        SELECT e.id, e.name, d.id AS d_id, d.name AS d_name FROM employee1 e JOIN
            department1 d ON e.dept_id = d.id
    select>

DepartmentMapper 不需要写

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第8张图片

测试类
    /**
     * 查询所有用户
     */
    @Test
    public void test2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List<Employee> emps =  mapper.getAllUser();
        for (Employee emp : emps) {
            System.out.println(emp);
        }

        sqlSession.close();
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第9张图片

5、总结 (多对一配置)

跳转到目录

  • 使用association元素,配置单一对象属性
    • 方式一: 额外SQL(分布查询), 一般,需要进入另一个页面显示更详细信息的时候.–>集合属性
    • 方式二: 内联映射(多表查询)

如果需要在查询出来的信息包含关联对象(这就是上面的在Employee类中,关联的属性–> Department dept)的数据, 就需要使用 内联映射, 否则就会出现N+1问题;

一对多(关系在一的一方)

跳转到目录

  • 案例: 多个员工对象属于同一个部门对象
  • 1、保存员工部门信息
  • 2、查询10号ID的部门信息
表设计
CREATE TABLE `employee2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `dept_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
CREATE TABLE `department2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
模型对象设计

Employee类
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第10张图片
Department类

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第11张图片

1、保存员工、部门信息

跳转到目录

mapper

EmployeeMapper

	// 保存员工信息
	void save(Employee emp);

	
    <insert id="save" useGeneratedKeys="true" keyProperty="id">
        
        INSERT INTO employee2(name, dept_id) VALUES (#{name}, #{deptId})
    insert>

DepartmentMapper

	// 保存一个部门
	void save(Department dept);

	
	<insert id="save" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO department2(name) VALUES (#{name})
    insert>
	
测试类
    /**
     * 测试保存操作
     */
    @Test
    public void test() {
        // 创建部门对象
        Department dept = new Department();
        dept.setName("开发部");
        // 创建员工对象
        Employee emp = new Employee();
        emp.setName("张三");

        Employee emp2 = new Employee();
        emp2.setName("李四");

        // 维护对象关系
        dept.getEmps().add(emp);
        dept.getEmps().add(emp2);

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);

        departmentMapper.save(dept);
        // 当保存部门信息后,再获取部门ID
        emp.setDeptId(dept.getId());
        emp2.setDeptId(dept.getId());

        employeeMapper.save(emp);
        employeeMapper.save(emp2);

        sqlSession.commit();
        sqlSession.close();
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第12张图片

2、根据id查询员工信息

跳转到目录
通过额外SQL

mapper

EmployeeMapper

	// 根据部门id来查询部门的员工信息
	List<Employee> getUsersByDeptId(Long deptId);
	
	 
    <select id="getUsersByDeptId" resultType="Employee">
        SELECT id, name, dept_id AS deptId FROM employee2 WHERE dept_id = #{deptId}
    select>

DepartmentMapper

      	<resultMap id="BaseResultMap" type="Department">
            <result column="id" property="id"/>
            <result column="name" property="name"/>
            
            
            <collection property="emps"
                select="com.sunny.mapper.DepartmentMapperMapper.getUsersByDeptId"
                column="id"
            />
        resultMap>

        
        <select id="getDeptById" resultMap="BaseResultMap">
            SELECT id, name FROM department2 WHERE id = #{id}
        select>

注意: 这里关联属性是集合类型的,所以要使用collection元素在这里插入图片描述
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第13张图片

测试类
   /**
     * 查询10号ID的部门信息(包括部门的员工)
     */
    @Test
    public void test2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
        Department dept = departmentMapper.getDeptById(10L);

        System.out.println(dept);
        System.out.println(dept.getEmps());

        /*
        * 手动完成 额外SQL查询
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        Long deptId = dept.getId();
        List emps = employeeMapper.getUsersByDeptId(deptId);
        dept.setEmps(emps);
        List emps1 = dept.getEmps();
        System.out.println(emps1);
        */

        sqlSession.close();
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第14张图片

3、内联映射(处理结果集和对象属性的不匹配)

跳转到目录
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第15张图片

mapper

DepartmentMapper

	// 根据id查询部门信息
	Department getDeptById(long l);

    <resultMap id="BaseResultMap" type="Department">
            <result column="id" property="id"/>
            <result column="name" property="name"/>

            
            <collection property="emps" ofType="Employee">
                <result column="e_id" property="id"/>
                <result column="e_name" property="name"/>
            collection>
        resultMap>

        
        <select id="getDeptById" resultMap="BaseResultMap">
            
            SELECT d.id, d.name,
                   e.id AS e_id, e.name AS e_name
            FROM department2 d JOIN employee2 e ON  d.id = e.dept_id
            WHERE d.id = #{id}
        select>

EmployeeMapper 不需要写

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第16张图片

测试类

和上面相同
在这里插入图片描述

延迟加载

跳转到目录

  • 延迟加载(lazy load) :也称为懒加载。

为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才会发出sql语询进行查询该数据。

MyBatis运行时期的属性配置,放在主配置文件中的元素中。

MyBatis中直接查询出来的many方对象其实就已经是一个代理对象,当使用many方对象的任意一个属性时,立刻很积极的把关联对象也查询出来,此时性能不会太好。

  • 配置细节:

①MyBatis 缺省情况下,禁用了延迟加载。
②MyBatis 会很积极的去查询关联对象。
③MyBatis 中缺省情况下调用equals. clone. hashCode、 toString 都会触发延迟加载
一般的我们保留clone就可以了,也就是说调用many方对象的toString. hashCode. equals 方法依然不会去发送查询one方的SQL.

在MaBatis的主配置文件中

    <settings>
        
        <setting name="logImpl" value="LOG4J"/>

        
        <setting name="lazyLoadingEnabled" value="true"/>
        
        <setting name="aggressiveLazyLoading" value="false"/>
        
        <setting name="lazyLoadTriggerMethods" value="clone"/>
    settings>

这样以来就不会发送额外SQL

关联对象的配置选择

跳转到目录

  • 针对单属性对象, 使用association元素,通常直接使用多表查询操作,也就是使用内联查询
  • 针对集合属性对象,使用collection元素,通常使用延迟加载, 也就是额外SQL处理

多对多(单向-关系在学生方)

跳转到目录

  • 1、保存学生、老师、中间表的信息
  • 2、删除指定id的学生
  • 3、查询指定id的学生(包括该id学生的老师)
表设计
CREATE TABLE `student` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `teacher` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `student_teacher` (
  `student_id` bigint(20) NOT NULL,
  `teacher_id` bigint(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
模型对象设计

Student类
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第17张图片
Teacher类
MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第18张图片

1、保存学生、老师和中间表的信息

跳转到目录

mapper

StudentMapper

	// 保存学生信息
    void save(Student stu);
    
	// 因为关系在学生这边.(因为传递的是两个参数,所以要做参数处理,使用注解)
	void insertRelationWithTeacher(@Param("studentId") Long studentId, @Param("teacherId") Long teacherId);

    <insert id="save" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO student (name) VALUES (#{name})
    insert>
    
    <insert id="insertRelationWithTeacher">
        INSERT INTO student_teacher (student_id, teacher_id) VALUES (#{studentId}, #{teacherId})
    insert>

TeacherMapper

	// 保存老师信息
    void save(Teacher teacher);

  	<insert id="save" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO teacher(name) VALUES (#{name})
    insert>
	
测试类
    /**
     * 将学生、老师信息存储到各自的表中
     *  并将学生、老师的ID插入到中间表中
     */
    @Test
    public void testSave() {
        Teacher t1 = new Teacher();
        t1.setName("老师1");
        Teacher t2 = new Teacher();
        t2.setName("老师2");

        Student s1 = new Student();
        s1.setName("张三");
        Student s2 = new Student();
        s2.setName("李四");

        // 维护关系(学生的老师们)
        s1.getTeachers().add(t1);
        s1.getTeachers().add(t2);
        s2.getTeachers().add(t1);
        s2.getTeachers().add(t2);

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        studentMapper.save(s1);
        studentMapper.save(s2);
        teacherMapper.save(t1);
        teacherMapper.save(t2);

        // 当学生和老师信息都保存之后,再维护中间表的数据
        for (Teacher teacher1 : s1.getTeachers()) {
            studentMapper.insertRelationWithTeacher(s1.getId(), teacher1.getId());
        }
        for (Teacher teacher2 : s2.getTeachers()) {
            studentMapper.insertRelationWithTeacher(s2.getId(), teacher2.getId());
        }

        sqlSession.commit();
        sqlSession.close();
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第19张图片

2、删除指定id的学生

跳转到目录

mapper

StudentMapper

	// 根据id删除学生记录
    void deleteStuById(long l);

    // 当删除学生的id时,中间表中学生的id也应该删除
    void deleteRelationTeacherById(long l);@Param("teacherId") Long teacherId);

    <delete id="deleteRelationTeacherById">
        DELETE FROM student_teacher WHERE student_id = #{id}
    delete>

    <delete id="deleteStuById">
        DELETE FROM student WHERE id = #{id}
    delete>

测试类

   /**
     * 根据ID删除
     */
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        // 因为多对多关系, 中间表中列,是两表中的主键,当删除一个学生时,中间表也必然发生改变
        // 在删除学生之前,应该先删除中间表中的数据
        studentMapper.deleteRelationTeacherById(1L);

        studentMapper.deleteStuById(1L);

        sqlSession.commit();
        sqlSession.close();
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第20张图片

3、根据id查询学生信息(包括学生的老师们)

跳转到目录
使用额外SQL

mapper

StudentMapper

	// 根据id来查询学生信息
    Student get(Long l);

    <resultMap id="BaseResultMap" type="Student">
        <result column="id" property="id"/>
        <result column="name" property="name"/>

        
        <collection property="teachers" ofType="Teacher"
            select="com.sunny.mapper.TeacherMapper.selectTeachersByStuId"
            column="id"
        />
    resultMap>

    
    <select id="get" resultMap="BaseResultMap">
        SELECT id,name FROM student WHERE id = #{id}
    select>

TeacherMapper

    <select id="selectTeachersByStuId" resultType="Teacher">
        SELECT t.id, t.name
        FROM teacher t JOIN student_teacher st on t.id = st.teacher_id
        WHERE st.student_id = #{studentId}
    select>

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第21张图片

测试类

    /**
     * 根据Id查询学生信息(包括该学生的老师们)
     */
    @Test
    public void testQueryByStuId(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        Student student = studentMapper.get(1L);
        System.out.println(student);
        System.out.println(student.getTeachers());
    }

MyBatis——对象关系映射、延迟加载、关联对象的配置选择_第22张图片

你可能感兴趣的:(MyBatis,对象关系映射,延迟加载,多对一,一对多)