JavaLearn#(28)MyBatis高级:无级联查询级联查询(立即加载结果映射延迟加载)多表连接查询MyBatis注解MyBatis运行原理面试题
Posted LRcoding
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn#(28)MyBatis高级:无级联查询级联查询(立即加载结果映射延迟加载)多表连接查询MyBatis注解MyBatis运行原理面试题相关的知识,希望对你有一定的参考价值。
1. 多表查询
前面的操作(MyBatis进阶),不管是用 SqlSession还是 Mapper代理,都是对单个数据库表的操作
在实际开发中,经常会将来自多张表的数据在一个位置显示,比如查询并显示员工信息时,需要展示部门表的部门名称,岗位表的岗位名称等。这就要求 Employee中,要包含部门 Dept、岗位 Position的信息
MyBatis是如何实现对多表的查询并组装数据呢?(此时没有DML的事)
- 方式1:无级联查询——开发者手动完成多表数据的组装,需要执行多条 SQL语句
- 方式2:级联查询——使用MyBatis映射配置自动完成数据的组装,需要执行多条 SQL语句
- 方式3:
连接查询
——使用MyBatis映射配置自动完成数据的组装,只执行一条 SQL语句
以 部门Dept 和 员工Emp 的一对多关系为例:
-
功能1:查询所有员工的信息(多对一:多个员工对应一个部门)
包含字段:empno、ename、sal、comm、deptno、dname
其中 deptno是关联字段,dname来自部门表Dept
-
功能2:查询10号部门及其该部门员工信息(一对多:一个部门对应多个员工)
包含字段:deptno、dname、loc。 员工显示empno、ename、sal、comm
1.1 环境搭建
需要定义两个实体类、两个映射接口、两个映射XML文件(注意在mybatis-cfg.xml中告诉映射文件的位置,可以使用package)
需要在两个实体类之间建立关联关系,数据库之间通过外键建立关联,类之间通过属性
建立关联(使用组合)
-
一个员工只有一个部门,所以在 Employee中,定义一个 Dept类的成员变量
public class Employee private Dept dept; // 虽然 Dept里包含了 deptno,但是在 Employee中的也不能省略,供 insert和 update使用 // 其他属性省略,均有 getter/setter方法
-
一个部门有多个员工,所以在 Dept中,定义一个泛型为 Employee的集合的成员变量
public class Dept private List<Employee> employeeList; // 其他属性省略,均有 getter/setter方法
1.2 无级联查询(不使用,了解)
需要开发者手动的进行多次查询并完成多表数据的组装,需要执行多个 SQL语句,很少使用
实现功能1(多对一):
-
测试类中:
@Test public void testFindAll() SqlSession sqlSession = DBUtil.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List<Employee> employeeList = empMapper.findAll(); // 在代码中进行组装!!!!!!!!!!!! for (Employee employee : employeeList) // 获取员工的部门编号 int deptno = employee.getDeptno(); // 按照部门编号查找部门数据 Dept dept = deptMapper.findById(deptno); // 将数据赋给Employee的dept字段 employee.setDept(dept); DBUtil.closeSqlSession(sqlSession); employeeList.forEach(emp -> if (emp.getDept() == null) System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal() + "\\t 无部门"); else System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal() + "\\t" + emp.getDept().getDname() + "\\t" + emp.getDept().getLoc()); );
-
映射文件
<!-- EmpMapper.xml中 --> <mapper namespace="com.lwclick.mapper.EmpMapper"> <sql id="empCols"> empno, ename, sal, hireDate, deptno </sql> <select id="findAll" resultType="employee"> SELECT <include refid="empCols" /> FROM emp </select> </mapper> <!-- DeptMapper.xml中 --> <mapper namespace="com.lwclick.mapper.DeptMapper"> <select id="findById" resultType="dept"> SELECT * FROM dept WHERE deptno = #param1 </select> </mapper>
实现功能2(一对多):
-
测试类:
@Test public void testFindAll() SqlSession sqlSession = DBUtil.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); // 此处应查询所有部门数据,通过循环,获取每个部门的编码 int deptno = 10; Dept dept = deptMapper.findById(deptno); // 通过部门的编码,查询该部门下所有的员工 List<Employee> empList = empMapper.findByDeptno(dept.getDeptno()); // 赋给该部门的员工list dept.setEmployeeList(empList); DBUtil.closeSqlSession(sqlSession); System.out.println(dept.getDeptno() + "\\t" + dept.getDname() + "\\t" + dept.getLoc()); for (Employee employee : dept.getEmployeeList()) System.out.println("\\t" + employee);
-
映射文件:
<!-- EmpMapper.xml中 --> <select id="findByDeptno" resultType="employee"> SELECT <include refid="empCols" /> FROM emp WHERE deptno = #param1 </select>
1.3 级联查询
级联查询:利用数据库表间的外键关联关系,进行自动的级联查询操作
使用MyBatis实现级联查询,除了要在 实体类中增加关联属性外,还需要在映射文件中进行配置
1.3.1 立即加载
实现功能1(多对一):
-
测试类:
@Test public void testFindAll() SqlSession sqlSession = DBUtil.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); // 无需组装!!!!!! List<Employee> employeeList = empMapper.findAll(); DBUtil.closeSqlSession(sqlSession); employeeList.forEach(emp -> if (emp.getDept() == null) System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal() + "\\t 无部门"); else System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal() + "\\t" + emp.getDept().getDname() + "\\t" + emp.getDept().getLoc()); );
-
映射文件(多对一),使用 resultMap,结合
association
:<select id="findAll" resultMap="empResultType"> <!-- 返回类型改为 resultMap --> SELECT <include refid="empCols" /> FROM emp </select> <!-- 配置 resultMap --> <resultMap id="empResultType" type="employee"> <!-- ================ 如果属性名称,除大小写外都一致,无需手动映射 ============= // 映射主键 <id property="empno" column="empno" /> // 映射非主键列 property:实体类的属性名 column:数据库表的列名 <result property="ename" column="ename" /> <result property="job" column="job" /> <result property="hireDate" column="hireDate" /> <result property="sal" column="sal" /> --> <!-- 下面也用到了 deptno,所以此处要指定一下 --> <result property="deptno" column="deptno" /> <!-- 配置【多对一、一对一】的关联映射 --> <!-- column:是当前表emp的deptno --> <association property="dept" column="deptno" select="com.lwclick.mapper.DeptMapper.findById" /> <!-- select:执行哪个文件下的什么SQL --> </resultMap>
实现功能2(一对多):
-
测试类:
@Test public void testFindAll() SqlSession sqlSession = DBUtil.getSqlSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); // 不再手动组装 int deptno = 10; Dept dept = deptMapper.findById(deptno); DBUtil.closeSqlSession(sqlSession); System.out.println(dept.getDeptno() + "\\t" + dept.getDname() + "\\t" + dept.getLoc()); for (Employee employee : dept.getEmployeeList()) System.out.println("\\t" + employee);
-
映射文件(一对多),使用 resultMap,结合
collection
:<select id="findById" resultMap="deptResultMap"> <!-- 返回类型改为 resultMap --> SELECT * FROM dept WHERE deptno = #param1 </select> <resultMap id="deptResultMap" type="dept"> <!-- 由于两个表中都有 deptno,所以此处要指定一下 --> <id property="deptno" column="deptno" /> <!-- 配置【一对多】的关联映射 --> <!-- column是当前表 dept的deptno --> <!-- ofType:集合中元素的数据类型 --> <collection property="employeeList" column="deptno" ofType="employee" select="com.lwclick.mapper.EmpMapper.findByDeptno" /> </resultMap>
1.3.2 理解 resultMap
在单表情况下,数据库字段名和实体类属性如果一致(不考虑大小写),还可以自动映射Auto-Mapping
在复杂的情况下,就需要对查询结果进行手动映射
<resultMap id="" type="">
<!-- 【主键】映射 -->
<id property="" javaType="" column="" jdbcType="" typeHandler=""></id>
<!-- 【非主键】映射 -->
<result property="" column=""></result>
<!-- 【多对一、一对一】映射 -->
<association property="" column="" fetchType="" select=""></association>
<!-- 【一对多】映射 -->
<collection property="" column="" fetchType="" select="" ofType=""></collection>
</resultMap>
resultMap 元素的属性:
- id:resultMap 的唯一标识
- type:结果最终形成哪个类的对象,和 resultType一样
resultMap 子元素:
- id:配置主键的数据库列和对象属性的关系
- property:需要映射到 JavaBean 的属性名称
- column:数据表的列名或者列别名
- result:配置非主键列和属性
- property、column
association
:配置多对一、一对一关联字段列和属性,对应一个对象属性- property、column
- fetchType:自动延迟加载
select
:使用哪个查询获取属性的值,要求指定namespace+id的全名称
collection
:配置一对多、多对多关联字段和属性,对应一个集合属性- property、column、fetchType、
select
- ofType:指明集合中元素的类型
- property、column、fetchType、
注意
:在resultMap手动映射中,一个关联列可能对应多个property,都要进行手动映射(比如 deptno字段)。其他列名和属性名一致的,都可以省略
1.3.3 延迟加载
多表查询中,如果第一个查询结果有 N条记录,随后根据这 N条记录的值,再去查询数据库 N次,共计N+1次查询,对服务器压力很大
- 解决方法1:延迟加载。关联表的数据,只有等到真正使用的时候才查询,不用不查询
- 解决方法2:连接查询。一条SQL语句,查询到所有的数据
延迟加载:设置为延迟加载的内容,等到真正使用时才进行查询(比如查询所有员工信息时,只需要显示员工信息,而不显示部门信息,那么部门信息就可以设置默认不查询,等真正要用的时候,再进行查询,提高了数据库的新能)。多用在关联对象和集合中
延迟加载的设置:
-
全局开关:在 mybatis-cfg.xml中打开延迟加载的开关(打开后,所有的 association和 collection都生效)
<settings> <!-- 配置延迟加载的开关 --> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
- lazyLoadingEnabled(默认true):是否开启延迟加载。开启后所有关联对象都会延迟加载, fetchType属性会覆盖该项的开关
- aggressiveLazyLoading(默认true):开启时,任何方法的调用都会加载懒加载对象的所有属性。否则,每个属性按需加载
-
分开关:指定的某个 association和 collection元素中,配置
fetchType
,取值 eager | lazy ,会覆盖全局延迟设置<resultMap id="deptResultMap" type="dept"> <id property="deptno" column="deptno"/> <!-- fetchType,配置是否开启延迟加载 --> <collection property="employeeList" column="deptno" fetchType="lazy" select="com.lwclick.mapper.EmpMapper.findByDeptno" /> </resultMap>
-
eager(
association 默认
):立即加载 -
lazy(
collection 默认
):延迟加载
功能1(fetchType=“eager”):
功能1(fetchType=“lazy”):
-
测试类:
@Test public void testFindAll() SqlSession sqlSession = DBUtil.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Employee> employeeList = empMapper.findAll(); DBUtil.closeSqlSession(sqlSession); employeeList.forEach(emp -> System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal()); // 不输出部门信息(模拟不使用部门的数据) );
-
映射文件:
<resultMap id="empResultType" type="employee"> <result property="deptno" column="deptno" /> <!-- 使用 fetchType="lazy" 设置懒加载 --> <association property="dept" column="deptno" select="com.lwclick.mapper.DeptMapper.findById" fetchType="lazy"/> </resultMap> <select id="findAll" resultMap="empResultType"> SELECT 以上是关于JavaLearn#(28)MyBatis高级:无级联查询级联查询(立即加载结果映射延迟加载)多表连接查询MyBatis注解MyBatis运行原理面试题的主要内容,如果未能解决你的问题,请参考以下文章
JavaLearn#(26)MyBatis基础:认识框架MyBatis环境搭建基本CRUD配置文件日志管理别名属性文件ThreadLocal保存sqlSession本地DTD模板
JavaLearn#(20)注解元注解模拟MyBatis注解JDK新特性数据库建模UML建模
JavaLearn#(20)注解元注解模拟MyBatis注解JDK新特性数据库建模UML建模
JavaLearn#(27)MyBatis进阶:Mapper代理(接口绑定)多参数传递模糊查询分页自增主键回填动态SQL一级缓存二级缓存
JavaLearn#(27)MyBatis进阶:Mapper代理(接口绑定)多参数传递模糊查询分页自增主键回填动态SQL一级缓存二级缓存