mybatis_级联查询(积极加载/懒加载)


  • 0x01_什么是级联查询

  • 0x02_立即加载(积极加载/eager)

    • 优化

    • 简单总结

  • 0x03_延迟加载(懒加载/lazy)

  • 0x04_多表查询的总结与扩展


Mybatis实现多表查询之级联查询

0x01_什么是级联查询

其实就是完成多表查询的任务,将多表查询任务变成n个单表查询任务。

比如现在有两张表:emp(员工表)和dept(部门表):

mybatis_级联查询(积极加载/懒加载)mybatis_级联查询(积极加载/懒加载)

要根据部门编号查询该部门的信息,并且查询部门所在的员工的信息。

可以通过两个sql语句进行查询:

select * from dept where deptno = 20;
select * from emp where deptno = 20;

查询结果分别是:

mybatis_级联查询(积极加载/懒加载)
image-20221017125515324
mybatis_级联查询(积极加载/懒加载)
image-20221017125525674

以上只是一个从数据库角度来说简单的说法,下面的案例(结合代码)之后再来总结级联查询的含义

0x02_立即加载(积极加载/eager)

【1】准备以上数据库相关的项目

新建一个maven项目,搭建项目结构如下:

mybatis_级联查询(积极加载/懒加载)

其中pom.xml引入依赖和配置如下:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>compile</scope>
</dependency>
</dependencies>

【2】如果按照数据库中查询的来写,那么非常简单,也就是两个查询语句呗:

(1)实体类:

Emp:

package com.bones.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
* @author : bones
* @version : 1.0
*/

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
}

Dept

package com.bones.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
* @author : bones
* @version : 1.0
*/

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept implements Serializable {
private Integer deptno;
private String dname;
private String loc;
}

(2)Mapper接口:

DeptMapper

package com.bones.mapper;

import com.bones.pojo.Dept;

/**
* @author : bones
* @version : 1.0
*/

public interface DeptMapper {
Dept findDeptByDeptno(int deptno);
}

EmpMapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bones.mapper.EmpMapper">
<sql id="empColumn">
empno,ename,job,mgr,hiredate,sal,comm,deptno
</sql>

<!--List<Emp> findEmpByDeptno(int deptno); -->
<select id="findEmpByDeptno" resultType="emp">
select <include refid="empColumn"/> from emp where deptno = #{deptno}
</select>
</mapper>

(3)mapper映射文件:

EmpMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.bones.mapper.EmpMapper">
<sql id="empColumn">
empno,ename,job,mgr,hiredate,sal,comm,deptno
</sql>

<!--List<Emp> findEmpByDeptno(int deptno); -->
<select id="findEmpByDeptno" resultType="emp">
select <include refid="empColumn"/> from emp where deptno = #{deptno}
</select>
</mapper>

DeptMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bones.mapper.DeptMapper">

<sql id="deptColumns">
deptno,dname,loc
</sql>

<resultMap id="DeptJoin" type="dept">
<id column="deptno" property="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>

</resultMap>

<!-- Dept findDeptByDeptno(int deptno);-->
<select id="findDeptByDeptno" resultMap="DeptJoin">
select <include refid="deptColumns"/> from dept where deptno = #{deptno}
</select>

</mapper>

(4)测试代码:

package com.bones.test01;

import com.bones.mapper.DeptMapper;
import com.bones.mapper.EmpMapper;
import com.bones.pojo.Dept;
import com.bones.pojo.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
* @author : bones
* @version : 1.0
*/

public class Test01 {
SqlSession sqlSession;
@Before
public void init(){
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = null;
try {
resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory factory = ssfb.build(resourceAsStream);
sqlSession = factory.openSession();

}

@Test
public void testFindDeptJoinEmpByDeptno(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.findDeptByDeptno(20);
System.out.println(dept);
EmpMapper mapper1 = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper1.findEmpByDeptno(20);
emps.forEach(System.out::println);
}

@After
public void close(){
sqlSession.close();
}


}

执行结果:

mybatis_级联查询(积极加载/懒加载)
image-20221017203921801

可看到执行了2个SQL语句。

优化

在mybatis中实际上级联查询可以进行下面的优化:

首先在dept实体类中引入属性:

实体类:Dept.java

package com.bones.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
* @author : bones
* @version : 1.0
*/

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept implements Serializable {
private Integer deptno;
private String dname;
private String loc;
//组合emp对象的List集合作为属性
private List<Emp> emps;
}

Mapper映射文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.bones.mapper.DeptMapper">

<sql id="deptColumns">
deptno,dname,loc
</sql>

<resultMap id="DeptJoin" type="dept">
<id column="deptno" property="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="emps"
select="com.bones.mapper.EmpMapper.findEmpByDeptno"
javaType="list"
column="deptno"
jdbcType="INTEGER"
fetchType="eager"
>

</collection>
</resultMap>

<!-- Dept findDeptByDeptno(int deptno);-->
<select id="findDeptByDeptno" resultMap="DeptJoin">
select <include refid="deptColumns"/> from dept where deptno = #{deptno}
</select>

</mapper>

测试方法:

package com.bones.test01;

import com.bones.mapper.DeptMapper;
import com.bones.mapper.EmpMapper;
import com.bones.pojo.Dept;
import com.bones.pojo.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
* @author : bones
* @version : 1.0
*/

public class Test01 {
SqlSession sqlSession;
@Before
public void init(){
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = null;
try {
resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory factory = ssfb.build(resourceAsStream);
sqlSession = factory.openSession();

}

@Test
public void testFindDeptJoinEmpByDeptno(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.findDeptByDeptno(20);
System.out.println(dept.getDeptno());
System.out.println(dept.getDname());
System.out.println(dept.getLoc());
List<Emp> emps = dept.getEmps();
emps.forEach(System.out::println);

}

@After
public void close(){
sqlSession.close();
}


}

测试结果:

mybatis_级联查询(积极加载/懒加载)
image-20221017205603921

测试结果的日志打印可以看出,执行了两句SQL语句。

简单总结

(1)优化的意义:经过对比,发现经过在映射文件中配置,测试类的代码大大简化了,无序手动进行关联查询和组装数据了。

(2)关于积极加载:

设置的位置是:

mybatis_级联查询(积极加载/懒加载)
image-20221017210440131

还需要特别注意上面的几个属性的写法:

<collection property="emps"
select="com.bones.mapper.EmpMapper.findEmpByDeptno"
javaType="list"
column="deptno"
jdbcType="INTEGER"
fetchType="eager"
>
</collection>
  • property:就是实体类的属性名:

mybatis_级联查询(积极加载/懒加载)

  • select:要执行的SQL语句的标签的id的全限定路径名,比如:com.bones.mapper.EmpMapper.findEmpByDeptno
  • javaType:查询结果,封装成什么java数据类型
  • column:上面的参数中,哪一项作为当前语句的传入的参数
mybatis_级联查询(积极加载/懒加载)
image-20221017212028110
  • jdbcType:column这个属性的类型
  • fetchType:指定加载是积极加载还是懒加载(eager还是lazy)

积极加载(eager)是指如果你访问一个对象的属性,Mybatis就会帮你把需要进步一查询的该属性或者其他属性在数据库中查询出来.

懒加载(lazy)是指,有这种必要的时候,采取进行必要的数据库检索

关于懒加载的内容,下面的0x03_延迟加载(懒加载/lazy)详细记录。

0x03_延迟加载(懒加载/lazy)

接着按照上面的案例,说说懒加载。

要实现懒加载,需要修改几个内容:

【1】全局开关:在sqlMapConfig.xml中打开延迟加载的开关。配置完成后所有的association和collection元素都生效

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

这两个标签的意义:

  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。默认值是false,所以需要懒加载的全局开关时,需要显式指明是true
  • aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods:指定对象的哪些方法触发一次延迟加载)。 默认值(在 3.4.1 及之前的版本中默认为 true)  是false。

但是要注意加的位置,这个可以参考mybatis的官方中文文档:https://mybatis.org/mybatis-3/zh/configuration.html

从下图可以看出,加在properties之后,在typeAliases之前

mybatis_级联查询(积极加载/懒加载)
image-20221017220007086

在我现有的项目里,已经有了settings标签了:(是记录日志的标签,用的框架是log4j)

mybatis_级联查询(积极加载/懒加载)

添加完懒加载的全局开关之后:

mybatis_级联查询(积极加载/懒加载)

【2】分开关:指定的associationcollection元素中配置fetchType属性。eager:表示立刻加载;lazy:表示延迟加载。将覆盖全局延迟设置。

在Mapper映射文件中:

mybatis_级联查询(积极加载/懒加载)

测试发现:

mybatis_级联查询(积极加载/懒加载)
image-20221017220637263

发现两个SQL语句还是执行了。

这是因为测试方法中:

@Test
public void testFindDeptJoinEmpByDeptno(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.findDeptByDeptno(20);
System.out.println(dept.getDeptno());
System.out.println(dept.getDname());
System.out.println(dept.getLoc());
List<Emp> emps = dept.getEmps();
emps.forEach(System.out::println);

}

现在将测试方法改为:

@Test
public void testFindDeptJoinEmpByDeptno(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.findDeptByDeptno(20);
System.out.println(dept.getDeptno());
System.out.println(dept.getDname());
System.out.println(dept.getLoc());
// List<Emp> emps = dept.getEmps();
// emps.forEach(System.out::println);

}

再次测试,发现还是执行了2个SQL语句:

mybatis_级联查询(积极加载/懒加载)
image-20221017221538587

如果只测试下面的这句:

Dept dept = mapper.findDeptByDeptno(20);

这时候就懒加载了,执行了一句SQL语句:

mybatis_级联查询(积极加载/懒加载)
image-20221017221706131

emm,感觉懒加载的说法有点暧昧。。。

查了一些资料吧:

延迟加载是开发过程中灵活获取对象的一种求值策略,该策略在定义目标对象时并不会立即计算实际对象值,而是在该对象后续被实际调用时才去求值。

也就是说这一句Dept dept = mapper.findDeptByDeptno(20);其实只是定义对象,所以懒加载了。

而如果像下面这样:

    System.out.println(dept.getDeptno());
System.out.println(dept.getDname());
System.out.println(dept.getLoc());

严格来说,已经去计算dept对象的值了,所以就算没有查emps这个属性的值,也会执行第二句SQL 语句。

所以说:对于积极加载(立即加载),对象在定义时便已获取,若后续逻辑并未使用该对象,则会造成资源浪费,降低运行效率。而懒加载(延迟加载)会在数据真正被调用时去获取,若后续未调用则不获取,避免计算开销,若后续调用多次,也可通过存储计算结果的方式来实现结果复用,避免多次计算。延迟加载流程图如下所示。

mybatis_级联查询(积极加载/懒加载)
image-20221017222211251

对于业务场景中计算开销较大的数据对象,若其在后续逻辑中可能会根据不同的业务判断条件在不同作用域中被使用多次,也可能一次都不会被使用到,那就可以使用延迟加载来获取该对象。

在以前的版本的计时器中,其实对于这个懒加载有争议,可能mybatis团队也做了调整,这里感兴趣可以阅读以下stackOverFlow上面的一个问题和相关的解答,在mybatis源码中,可能以前的版本确实有这样的问题。

https://stackoverflow.com/questions/24013855/lazy-loading-using-mybatis-3-with-java

0x04_多表查询的总结与扩展

  • 1.首先关于级联查询,做一个理解的小总结:

级联查询,顾名思义,就是利用数据库表间的外键关联关系进行自动的级联查询操作。使用MyBatis实现级联查询,除了实体类增加关联属性外,还需要在映射文件中进行配置。例如:

mybatis_级联查询(积极加载/懒加载)
image-20221017222341040
  • 2.延迟查询:延迟加载,又称按需加载。延迟加载的内容等到真正使用时才去进行加载(查询)。多用在关联对象或集合中。

延迟加载的好处:先从单表查询、需要时再从关联表去关联查询,大大降低数据库在单位时间内的查询工作量,将工作在时间上的分配更加均匀,而且单表要比关联查询多张表速度要快。

  • 3.resultMap中的常见属性
属性 描述
property 需要映射到JavaBean 的属性名称。
javaType property的类型,一个完整的类名,或者是一个类型别名。如果你匹配的是一个JavaBean,那MyBatis 通常会自行检测到。
column 数据表的列名或者列别名。
jdbcType column在数据库表中的类型。这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。
typeHandler 使用这个属性可以覆写类型处理器,实现javaType、jdbcType之间的相互转换。一般可以省略,会探测到使用的什么类型的typeHandler进行处理
fetchType 自动延迟加载
select association、collection的属性,使用哪个查询查询属性的值,要求指定namespace+id的全名称
ofType collection的属性,指明集合中元素的类型(即泛型类型)
  • 4.级联查询和联合查询的比较及其选择

级联查询 联合查询
SQL语句数量 多条 一条
性能 性能低 性能高
延迟加载 立即加载、延迟加载 只有立即加载
灵活性 更灵活 不灵活
SQL难易度 简单 复杂
选择依据 简单、灵活 高性能
  • 5.ResultType和ResultMap使用场景
  1. 如果你做的是单表的查询并且封装的实体和数据库的字段一一对应  resultType

  2. 如果实体封装的属性和数据库的字段不一致  resultMap

  3. 使用N+1级联查询的时候  resultMap

  4. 使用的是多表的连接查询  resultMap

  • 6.一对一关联映射的实现
  1. 实例:学生和学生证、雇员和工牌

  2. 数据库层次:主键关联或者外键关联(参看之前内容)

  3. MyBatis层次:在映射文件的设置双方均使用association即可,用法相同

  • 7.多对多映射的实现
  1. 实例:学生和课程、用户和角色

  2. 数据库层次:引入一个中间表将一个多对多转为两个一对多

  3. MyBatis层次

方法1:在映射文件的设置双方均使用collection即可,不用引入中间类

方法2:引入中间类和中间类的映射文件,按照两个一对多处理

  • 8.自关联映射

1)实例:Emp表中的员工和上级。一般是一对多关联 2)数据库层次:外键参考当前表的主键(比如mgr参考empno) 3)MyBatis层次:按照一对多处理,但是增加的属性都写到一个实体类中,增加的映射也都写到一个映射文件中


原文始发于微信公众号(小东方不败):mybatis_级联查询(积极加载/懒加载)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/47393.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!