MyBatis学习——分步查询与延迟加载

时间:2022-11-30 18:43:26

声明:面试是遇到延迟加载问题,在网页搜索到此篇文章,感觉很有帮助,留此学习之用!

一、分步查询

分步查询通常应用于关联表查询,如:电商平台,查询订单信息时需要查询部分的用户信息;OA系统查询个人信息时需要查询部门信息,反之亦是。相对于关联查询来说,分步查询将查询sql拆分,这里引申出一个问题是:分步查询与关联表查询的不同。

从代码层面来说:关联表查询能够有效的简化代码编写逻辑,减小代码编写难度,同时避免B-U-G(代码多了,bug   就多了);

而分步查询则能够增强代码的可用性(这点我也不是非常理解,在实际开发中也未遇到过)

从功能上说:关联表查询毕竟只需要查询一次数据库,对于业务量较小的系统来说,效率更高,数据库压   力相对较小;

分步查询虽然需要多次查询数据,但是这也意味着能够更好地使用数据缓存服务,且缓存的   数据耦合度低,利用率高,而且单次查询效率很高,数据库压力较小(对于业务量较大的系    统来说)。还有一点则是数据库锁的问题,毕竟关联查询是多表同时使用,分步查询每次只    操作一个表。

分步查询的实现(以简单的OA系统为例)

业务场景一:嵌套标签association的应用---查询员工信息时,获取员工所在的部门信息。

1.建立员工与部门实体类

/**
* 员工实体类
*
* @author xuyong
*
*/
public class Employee { private Integer id; private String lastName; private String gender; private String email; private Department department; private Integer dId; public Integer getdId() {
return dId;
} public void setdId(Integer dId) {
this.dId = dId;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getLastName() {
return lastName;
} public void setLastName(String lastName) {
this.lastName = lastName;
} public String getGender() {
return gender;
} public void setGender(String gender) {
this.gender = gender;
} public String getEmail() {
return email;
} public void setEmail(String email) {
this.email = email;
} public Department getDepartment() {
return department;
} public void setDepartment(Department department) {
this.department = department;
} @Override
public String toString() {
return "Employee [dId=" + dId + ", department=" + department
+ ", email=" + email + ", gender=" + gender + ", id=" + id
+ ", lastName=" + lastName + "]";
}
}

  

/**
* 部门实体类
*
* @author xuyong
*
*/
public class Department { private Integer id; private String departmentName; private List<Employee> employee; public List<Employee> getEmployee() {
return employee;
} public void setEmployee(List<Employee> employee) {
this.employee = employee;
} /**
*
*/
private Department() {
super();
// TODO Auto-generated constructor stub
} /**
* @param id
* @param departmentName
*/
private Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getDepartmentName() {
return departmentName;
} public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
} @Override
public String toString() {
return "Department [departmentName=" + departmentName + ", employee="
+ employee + ", id=" + id + "]";
} }

2.JUNIT单元测试编写

  获取sqlSession实例(未使用框架,自己手动获取)

/**
* 获取sqlSession实例
* @return
* @throws IOException
*/
public SqlSessionFactory getSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}

  业务方法

/**
* 分布查询实现:
* 获取员工信息及部门信息
* @throws IOException
*/
@Test
public void test08() throws IOException {
SqlSessionFactory sessionFactory = getSessionFactory();
SqlSession sqlSession = sessionFactory.openSession();
try {
EmployeeDaoPlus dao = sqlSession.getMapper(EmployeeDaoPlus.class);
Employee employee = dao.getEmpByIdStep(1);
System.out.println(employee);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}

 在Mapper中定义接口

/**
* 员工类Mapper接口
*
* @author xuyong
*
*/
public interface EmployeeDaoPlus { /**
* 分步查询测试
* 获取员工信息同时获取部门信息
* @param id
* @return
*/
Employee getEmpByIdStep(Integer id); }

 编写对应的XML(sql资源文件)文件

<!-- 使用association分布查询 -->
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from employee where id=#{id}
</select>

 以下为查询结果,可以看到打印了两次sql

[com.xuyong.dao.EmployeeDaoPlus.getEmpByIdStep]-==>  Preparing: select * from employee where id=?
[com.xuyong.dao.EmployeeDaoPlus.getEmpByIdStep]-==> Parameters: 1(Integer)
[com.xuyong.dao.EmployeeDaoPlus.getEmpByIdStep]-<== Columns: id, last_name, gender, email, d_id
[com.xuyong.dao.EmployeeDaoPlus.getEmpByIdStep]-<== Row: 1, 徐永, 男, t1heluosh1@163.com, 1
[com.xuyong.dao.DepartmentDao.getDepartmentById]-====> Preparing: select id,department_name from department where id=?
[com.xuyong.dao.DepartmentDao.getDepartmentById]-====> Parameters: 1(Integer)
[com.xuyong.dao.DepartmentDao.getDepartmentById]-<==== Columns: id, department_name
[com.xuyong.dao.DepartmentDao.getDepartmentById]-<==== Row: 1, 文案部
[com.xuyong.dao.DepartmentDao.getDepartmentById]-<==== Total: 1
[com.xuyong.dao.EmployeeDaoPlus.getEmpByIdStep]-<== Total: 1
Employee [dId=1, department=Department [departmentName=文案部, employee=null, id=1], email=t1heluosh1@163.com, gender=男, id=1, lastName=徐永]

业务场景二:嵌套标签collection的应用---查询部门信息时,获取该部门下所有的员工。
  collection主要用于映射集合数据,工作中常见的应用场景有:

  订单查询、地址查询、产品分类查询

  Junit单元测试

/**
* 分步查询
* 查询部门时获取部门里所有的员工信息
*
* @throws IOException
*/
@Test
public void test10() throws IOException {
SqlSessionFactory sessionFactory = getSessionFactory();
SqlSession sqlSession = sessionFactory.openSession();
try {
DepartmentDao dao = sqlSession.getMapper(DepartmentDao.class);
Department department = dao.getDeptAndAEmpsByStep(1);
/*EmployeeDaoPlus dao2 = sqlSession.getMapper(EmployeeDaoPlus.class);
Employee employee = dao2.getEmpById(department.getEmployee().get(0).getId());*/
System.out.println(department);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}

接口定义此处忽略

XML文件编写

<!-- collection分布查询 -->
<resultMap type="com.xuyong.entity.Department" id="DepartAndEmpByStep">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
<collection property="employee" select="com.xuyong.dao.EmployeeDaoPlus.getEmpByDid" column="id"></collection>
</resultMap> <!-- collection分布查询部门下的所有员工信息 -->
<select id="getDeptAndAEmpsByStep" resultMap="DepartAndEmpByStep">
select id,department_name from department
where id = #{id}
</select>

  查询结果

[com.xuyong.dao.DepartmentDao.getDeptAndAEmpsByStep]-==>  Preparing: select id,department_name from department where id = ?
[com.xuyong.dao.DepartmentDao.getDeptAndAEmpsByStep]-==> Parameters: 1(Integer)
[com.xuyong.dao.DepartmentDao.getDeptAndAEmpsByStep]-<== Columns: id, department_name
[com.xuyong.dao.DepartmentDao.getDeptAndAEmpsByStep]-<== Row: 1, 文案部
[com.xuyong.dao.EmployeeDaoPlus.getEmpByDid]-====> Preparing: select * from employee where d_id = ?
[com.xuyong.dao.EmployeeDaoPlus.getEmpByDid]-====> Parameters: 1(Integer)
[com.xuyong.dao.EmployeeDaoPlus.getEmpByDid]-<==== Columns: id, last_name, gender, email, d_id
[com.xuyong.dao.EmployeeDaoPlus.getEmpByDid]-<==== Row: 1, 徐永, 男, t1heluosh1@163.com, 1
[com.xuyong.dao.EmployeeDaoPlus.getEmpByDid]-<==== Row: 4, 张三, 女, 123131313, 1
[com.xuyong.dao.EmployeeDaoPlus.getEmpByDid]-<==== Total: 2
[com.xuyong.dao.DepartmentDao.getDeptAndAEmpsByStep]-<== Total: 1
Department [departmentName=文案部, employee=[Employee [dId=1, department=null, email=t1heluosh1@163.com, gender=男, id=1, lastName=徐永], Employee [dId=1, department=null, email=123131313, gender=女, id=4, lastName=张三]], id=1]

  

二、延迟加载

  什么是延迟加载?

  当我们在某项业务里需要同时获取A、B两份数据,但是B这份数据又不需要立即使用(或者存在压根就不会使用的情况),当程序需要加载B时,再去请求数据库来获取B数据,而不是一次性将数据全部取出来或者重新发送一份请求,这就是延迟加载。

MyBatis默认关闭延迟加载技术,需要我们在配置文件里手动配置,配置如下:

<!-- 设置 -->
<settings>
<!-- 驼峰命名映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="jdbcTypeForNull" value="NULL"/> <!-- 懒加载设置 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入懒加载,设置为false则按需加载,否则会全部加载 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 标准日志输出 -->
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>--> <!-- log4j日志输出 -->
<setting name="logImpl" value="LOG4J"/> </settings>

  

注意:
  1. lazyLoadingEnabled与aggressiveLazyLoading必须全部设置,且lazyLoadingEnabled为true,aggressiveLazyLoading为false才能让延迟加载真正生效

  2.  toString与重载方法过滤:

通常我们在测试时会在实体类加入toString,或者存在了一些重载方法,这些MyBatis会对其进行过滤,但是过滤会调     用cglib与asm指定包,因此要将两个包添加到buildpath。以下为两个包的maven依赖:

         <dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency> <dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>

  

3.如果想单个开启或禁用延迟加载,可以使用fetchType属性来实现

<!-- collection分布查询 -->
        <resultMap type="com.xuyong.entity.Department" id="DepartAndEmpByStep">
        <id column="id" property="id"/>
        <result column="department_name" property="departmentName"/>
        <!-- 多个值传递可封装成map如:column="{key1=column1,...}"
             fetchType="lazy" 表示使用懒加载   fetchType="eager"表示禁用懒加载
        -->
        <collection property="employee" select="com.xuyong.dao.EmployeeDaoPlus.getEmpByDid" column="{id=id}" fetchType="lazy"></collection>
        </resultMap>