您当前的位置: 首页 >  mybatis

white camel

暂无认证

  • 2浏览

    0关注

    442博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

white camel 发布时间:2020-03-07 08:13:59 ,浏览量:2

目录
  • 多对一关系
    • 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类 在这里插入图片描述 Department类 在这里插入图片描述

1、保存员工、部门信息

跳转到目录

Mapper

EmployeeMapper

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



    
    INSERT INTO employee1(name, dept_id) VALUES (#{name}, #{dept.id})

DepartmentMapper

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



	INSERT INTO department1(name) VALUES (#{name})

测试类
/**
 * 测试保存操作
 */
@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();
}

在这里插入图片描述

2、根据id查询员工信息(包括员工的部门)

跳转到目录

通过额外SQL

Mapper

EmployeeMapper

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



    
    
    




    
    SELECT id, name, dept_id FROM employee1 WHERE id = #{id}

DepartmentMapper

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



    SELECT id, name FROM department1 WHERE id = #{id}

测试类

因为获取的结果集中的列名和对象的属性名不匹配,设置resultMap解决不匹配问题, 但是结果集中的dept_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());
}

在这里插入图片描述 在测试方法中不想自己手动发额外的SQL怎么办呢?MyBatis可以帮我们完成,这就需要在resultMap中配置一下

额外SQL的配置方式

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


   
     
     
     
                  
     

  • 这样以来,就不用了在测试方法中不用自己去写了,实际操作是一样的,只是把额外发送的SQL封装配置起来了.
  • DepartmentMapper中就不需要写Department getDeptById(Long deptId)接口方法了

在这里插入图片描述

3、额外SQL的N+1问题

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

  • 1 : SELECT * FROM employee
  • N: SELECT * FROM department WHERE id = ?

当查询所有员工时,如下图: (只要是不同部门的员工, 都会额外会根据dept_id去查一下部门信息) 在这里插入图片描述 解决方案: 使用多表查询(JSON查询), 一条SQL语句就搞定

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

跳转到目录 在这里插入图片描述

Mapper

EmployeeMapper

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



    
    
    
    
    
    
    
    
    
    
    	
        
        
    




    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

这样一来, 通过关联查询, id, name, d_id, d_name就和上面设置的对应上了

DepartmentMapper 不需要写 在这里插入图片描述

这样一来, 当查所有的用户信息时, 就会把每个用户的部门信息也查出来,并赋值给用户的dept属性

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

    sqlSession.close();
}

在这里插入图片描述

5、总结 (多对一配置)

跳转到目录

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

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

总结:

多对一问题:
1、主要是在employee中添加了Department dept属性, 
所以就会导致employee表中的字段和employee类中的属性, 出现不一致, 因为表中的字段为`dept_id`, 而类中为dept属性(是一个部门对象),
因为我们可以拿到`dept_id`, 所以, 我们先通过dept_id额外去部门mapper中
查询到部门对象, 然后将部门对象赋值给employee.dept; 

2、发送额外SQL, 可以去手动编写(看上面代码), 也可以通过标签中的
它就可以用来发额外SQL; 

3、这样虽然可以了, 但是又发生一个问题, 如果同时查询多个员工的部门, 假如每个员工的部门都不同, 
此时`查询所有员工, 都需要额外的根据每个员工表中的dept_id去查询部门对象(发SQL)`, 这样SQL就太多; 
也就是 N+1 条SQL问题

4、通过内联查询来解决N+1, 也就是多表直接的关联查询, 
SELECT e.id, e.name, d.id, d.name FROM employee e JOIN department d ON e.dept_id = d.id



    
    

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

跳转到目录

  • 案例: 多个员工对象属于同一个部门对象
  • 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类 在这里插入图片描述 Department类

在这里插入图片描述

1、保存员工、部门信息

跳转到目录

Mapper

EmployeeMapper

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



    
    INSERT INTO employee2(name, dept_id) VALUES (#{name}, #{deptId})

DepartmentMapper

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



    INSERT INTO department2(name) VALUES (#{name})

测试类
/**
 * 测试保存操作
 */
@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();
}

在这里插入图片描述

2、根据deptId查询部门员工信息

跳转到目录

通过额外SQL

Mapper

EmployeeMapper

// 根据部门id来查询部门的员工信息
List getUsersByDeptId(Long deptId);



    SELECT id, name, dept_id AS deptId FROM employee2 WHERE dept_id = #{deptId}

DepartmentMapper


    
    
    
    
    




    SELECT id, name FROM department2 WHERE id = #{id}

注意: 这里关联属性是集合类型的,所以要使用collection元素在这里插入图片描述 在这里插入图片描述

测试类
/**
 * 查询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();
}

在这里插入图片描述

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

跳转到目录 在这里插入图片描述

mapper

DepartmentMapper

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


    
    

    
    
        
        
    




    
    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}

EmployeeMapper 不需要写

在这里插入图片描述

测试类

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

延迟加载

跳转到目录

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

  • 为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才会发出sql语询进行查询该数据
  • MyBatis运行时期的属性配置,放在主配置文件中的元素中。
  • MyBatis中直接查询出来的many方对象其实就已经是一个代理对象,当使用many方对象的任意一个属性时,立刻很积极的把关联对象也查询出来,此时性能不会太好。
  • 配置细节: ①MyBatis 缺省情况下,禁用了延迟加载。 ②MyBatis 会很积极的去查询关联对象。 ③MyBatis 中缺省情况下调用equals. clone. hashCode、 toString 都会触发延迟加载 一般的我们保留clone就可以了,也就是说调用many方对象的toString. hashCode. equals 方法依然不会去发送查询one方的SQL.

在MaBatis的主配置文件中


    
    

    
    
    
    
    
    

这样以来就不会发送额外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类 在这里插入图片描述 Teacher类 在这里插入图片描述

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

跳转到目录

Mapper

StudentMapper

// 保存学生信息
void save(Student stu);

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


    INSERT INTO student (name) VALUES (#{name})



    INSERT INTO student_teacher (student_id, teacher_id) VALUES (#{studentId}, #{teacherId})

TeacherMapper

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


    INSERT INTO teacher(name) VALUES (#{name})

测试类
/**
 * 将学生、老师信息存储到各自的表中
 *  并将学生、老师的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();
}

在这里插入图片描述

2、删除指定id的学生

跳转到目录

  • 当删除指定id的学生前, 要删除中间表中 关于该id的学生, 以及其教师的关联关系
Mapper

StudentMapper

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

// 当删除学生的id时,中间表中学生的id也应该删除
void deleteRelationTeacherById(long studentId);


    DELETE FROM student_teacher WHERE student_id = #{studentId}



    DELETE FROM student WHERE id = #{id}

测试类

/**
  * 根据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();
 }

在这里插入图片描述

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

跳转到目录 使用额外SQL

因为 额外SQL(分布查询), 一般,需要进入另一个页面显示更详细信息的时候.–>集合属性

Mapper

StudentMapper

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


    
    

    
    




    SELECT id,name FROM student WHERE id = #{id}

TeacherMapper


	
    SELECT t.id, t.name
    FROM teacher t JOIN student_teacher st on t.id = st.teacher_id
    WHERE st.student_id = #{studentId}

在这里插入图片描述

测试类

/**
 * 根据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());
}

在这里插入图片描述

2021.4.21补充

	
	
		
		
		
	
	
	
		select id,dept_name from tbl_dept where id=#{id}
	
	
	
关注
打赏
1661428283
查看更多评论
立即登录/注册

微信扫码登录

0.0866s