MyBatis级联第四篇——N+1问题和全局延迟加载【推荐,MyBatis级联重要的一篇原创,建议认真读】

前面的三篇是我们介绍入门的章节,然而级联并非我们想象的那样简单。下面我们先描述一下N+1问题,再讨论全局设定的延迟加载。

1、N+1问题

在做完前面三篇入门的例子后,让我们运行一下以下:

package com.ykzhen2014.csdn.main;

import org.apache.ibatis.session.SqlSession;

import com.ykzhen2014.csdn.util.SqlSessionFactoryUtil;
import com.ykzhen2015.csdn.mapper.EmployeeMapper;
import com.ykzhen2015.csdn.pojo.Employee;

public class CsdnMain {
	public static void main(String []args) {
		SqlSession sqlSession = null;
		try {
			sqlSession = SqlSessionFactoryUtil.openSqlSession();
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			Employee emp = employeeMapper.getEmployee(1);
		} finally {
			if (sqlSession != null) {
			    sqlSession.close();
			}
		}
	}
}

让我们看看日志的情况:

DEBUG 2016-04-27 11:28:34,409 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2016-04-27 11:28:34,629 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-04-27 11:28:34,629 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-04-27 11:28:34,629 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-04-27 11:28:34,629 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-04-27 11:28:34,629 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-04-27 11:28:34,869 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-04-27 11:28:35,170 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 1335298403.
DEBUG 2016-04-27 11:28:35,200 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, emp_name as empName, sex from t_employee where id =?
DEBUG 2016-04-27 11:28:35,220 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-04-27 11:28:35,290 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====>  Preparing: select prostate from t_healthy_male where emp_id = ?
DEBUG 2016-04-27 11:28:35,290 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Parameters: 1(Integer)
DEBUG 2016-04-27 11:28:35,320 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <====      Total: 1
DEBUG 2016-04-27 11:28:35,320 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====>  Preparing: SELECT id, emp_id as empId, card_no as cardNo FROM t_employee_card WHERE emp_id = ?
DEBUG 2016-04-27 11:28:35,320 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Parameters: 1(Integer)
DEBUG 2016-04-27 11:28:35,330 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <====      Total: 1
DEBUG 2016-04-27 11:28:35,330 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====>  Preparing: SELECT a.id, a.proj_name as projName FROM t_project a, t_employee_project b where a.id = b.proj_id and b.emp_id = ?
DEBUG 2016-04-27 11:28:35,330 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Parameters: 1(Integer)
DEBUG 2016-04-27 11:28:35,370 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <====      Total: 2
DEBUG 2016-04-27 11:28:35,370 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-04-27 11:28:35,370 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [[email protected]]
DEBUG 2016-04-27 11:28:35,370 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [[email protected]]
DEBUG 2016-04-27 11:28:35,370 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 1335298403 to pool.

我们发现级联是成功了,首先执行查询t_employee表,然后根据sex的结果,找到t_healthy_male的信息,跟着查询员工卡信息,最后查找工程信息。级联完全成功。但是......

假如我只想了解的信息只限于t_employee表,那么级联后,MyBatis会将所有相关数据查询,白白执行了好几个SQL,这显然不利于我们系统的性能。其次每当用t_employyee去级联别的查询,就会多执行一条SQL,这样就不断的消耗性能,这便是MyBatis的N+1问题,显然这不是一个结果,那么我们有什么好的办法呢?在MyBatis克服N+1问题的方法往往是延迟加载、

延迟加载分为全局性和非全局性的,我们这篇先研究全局性的延迟加载问题。

2 全局定义的延迟加载

延迟加载是MyBatis处理N+1问题的途径,我们有必要去研究一下延迟加载。在MyBatis中可以配置全局参数,控制延迟加载:

设置参数 描述 有效值 默认值
cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 true | false true

这里截取的是官方的说明。但是我觉得还不够详细,我这里再加入一下论述,使其更加完善。

  • cacheEnabled 是一个开关,代表你要不要使用缓存?
  • lazyLoadingEnabled 也是一个开关,它来控制是否使用延迟加载的功能,但是延迟加载将采取默认规则,按树的层级加载,详细情况下篇我们在谈。
  • aggressiveLazyLoading  如果设置它为false,则它不会按树的层级加载,具体的情况我们暂时不介绍,后面章节再谈。

好让我们现在修改这两个参数,看看运行的结果如何,现在我们修改MyBatis的配置文件:

  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
     <setting name="cacheEnabled" value="true"/>
     <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
	<environments default="development">
.......
</configuration>

我们运行一下,上面的代码,得到日志:

DEBUG 2016-05-10 16:13:56,443 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2016-05-10 16:13:56,582 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:13:56,584 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:13:56,584 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:13:56,584 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:13:56,584 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:13:56,921 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-05-10 16:13:57,183 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 1037324811.
DEBUG 2016-05-10 16:13:57,199 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, emp_name as empName, sex from t_employee where id =?
DEBUG 2016-05-10 16:13:57,220 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-05-10 16:13:57,332 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-05-10 16:13:57,333 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [[email protected]]
DEBUG 2016-05-10 16:13:57,333 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [[email protected]]
DEBUG 2016-05-10 16:13:57,333 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 1037324811 to pool.

我们发现全部只是执行了一条SQL,仅仅查询出了t_employee表的信息,其他的信息并未加载,延迟加载的效果成功了。这时我们再把配置,修改为:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
     <setting name="cacheEnabled" value="true"/>
     <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="aggressiveLazyLoading" value="true"/>
    </settings>
	<environments default="development">
......
</configuration>

我们再运行一次代码,得到日志如下:

DEBUG 2016-05-10 16:18:56,910 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2016-05-10 16:18:57,020 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:18:57,020 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:18:57,020 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:18:57,020 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:18:57,020 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:18:57,130 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-05-10 16:18:57,360 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 1037324811.
DEBUG 2016-05-10 16:18:57,360 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, emp_name as empName, sex from t_employee where id =?
DEBUG 2016-05-10 16:18:57,400 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-05-10 16:18:57,482 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====>  Preparing: select prostate from t_healthy_male where emp_id = ?
DEBUG 2016-05-10 16:18:57,482 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Parameters: 1(Integer)
DEBUG 2016-05-10 16:18:57,532 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <====      Total: 1
DEBUG 2016-05-10 16:18:57,532 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-05-10 16:18:57,532 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [[email protected]]
DEBUG 2016-05-10 16:18:57,532 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [[email protected]]
DEBUG 2016-05-10 16:18:57,532 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 1037324811 to pool.

显然健康情况的东西出来了,也就是说鉴别器的东西出来了。

好,我们再修改一下测试的代码:

public class CsdnMain {
	public static void main(String []args) {
		SqlSession sqlSession = null;
		try {
			sqlSession = SqlSessionFactoryUtil.openSqlSession();
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			Employee emp = employeeMapper.getEmployee(1);
			EmployeeCard card = emp.getEmployeeCard();//获取学生卡,效果如何呢??
		} finally {
			if (sqlSession != null) {
			    sqlSession.close();
			}
		}
	}
}

于是我们再次运行代码,得到如下日志:

DEBUG 2016-05-10 16:22:31,158 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2016-05-10 16:22:31,248 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:22:31,248 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:22:31,248 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:22:31,248 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:22:31,248 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-10 16:22:31,358 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-05-10 16:22:31,588 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 1037324811.
DEBUG 2016-05-10 16:22:31,588 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, emp_name as empName, sex from t_employee where id =?
DEBUG 2016-05-10 16:22:31,628 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-05-10 16:22:31,700 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====>  Preparing: select prostate from t_healthy_male where emp_id = ?
DEBUG 2016-05-10 16:22:31,700 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Parameters: 1(Integer)
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <====      Total: 1
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: SELECT a.id, a.proj_name as projName FROM t_project a, t_employee_project b where a.id = b.proj_id and b.emp_id = ?
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 2
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: SELECT id, emp_id as empId, card_no as cardNo FROM t_employee_card WHERE emp_id = ?
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [[email protected]]
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [[email protected]]
DEBUG 2016-05-10 16:22:31,710 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 1037324811 to pool.

于是一个结果出来了,除了员工卡信息外,我们发现连同员工项目表的信息也被加载了。

这便是笔者提到的树形层级加载,这是全局参数的局限所在,为了克服这个问题我们下章还会讨论精确定义的加载策略。

时间: 2024-10-12 16:04:02

MyBatis级联第四篇——N+1问题和全局延迟加载【推荐,MyBatis级联重要的一篇原创,建议认真读】的相关文章

深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)[转]

上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, 本篇继续讲剩下的配置节点之一:typeAliases. typeAliases节点主要用来设置别名,其实这是挺好用的一个功能, 通过配置别名,我们不用再指定完整的包名,并且还能取别名. 例如: 我们在使用 com.demo.entity. UserEntity 的时候,我们可以直接配置一个别名user, 这样

Mybatis系列(四)注解

Mybatis系列(四)注解 1.pom.xlm: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mave

MyBatis学习(四)XML配置文件之SQL映射的XML文件

SQL映射文件常用的元素: 1.select 查询语句是MyBatis最常用的语句之一. 执行简单查询的select元素是非常简单的: <select id="selectUser" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select> 这个语句被称作selectUser,接受一个int类型的参数,

NHibernate 延迟加载与立即加载 (第七篇)

NHibernate 延迟加载与立即加载 (第七篇) 一.延迟加载 延迟加载可以理解为:当需要用的时候才加载. 假设我们数据库有一个Person对象,一个Country对象,其中Person属于Country,一对多关系.当我们Get()一个 Person对象的时候.并不立即把Country对象也加入来,而是当我们的代码执行到要通过Person调用Country对象的时 候,NHibernate才到数据库去查询对应的Country对象,这就叫延迟加载.相反,如果我们Get()一个Person对象

MyBatis学习总结(四)——解决字段名与实体类属性名不相同的冲突

在平时的开发中,我们表中的字段名和表对应实体类的属性名称不一定都是完全相同的,下面来演示一下这种情况下的如何解决字段名与实体类属性名不相同的冲突. 一.准备演示需要使用的表和数据 CREATE TABLE orders( order_id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(20), order_price FLOAT ); INSERT INTO orders(order_no, order_price) VALUES('aaaa'

MyBatis学习 之 四、MyBatis配置文件

四.MyBatis主配置文件 在定义sqlSessionFactory时需要指定MyBatis主配置文件: Xml代码   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml&qu

MyBatis学习(四)、MyBatis配置文件

四.MyBatis主配置文件 在定义sqlSessionFactory时需要指定MyBatis主配置文件: Xml代码   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml&qu

MyBatis 之 使用四(二) (mapper.xml)

执行查询操作的时候,通常返回的结果集会出现一下几种情况: 1. 一对一查询:返回的结果集的唯一主键是非重复的.(下面会标出唯一主键,和 oracle 的主键有区别) 查询订单以及订单的用户信息,两种接收结果集的方式: 1)使用 pojo 对象去接收结果集(唯一主键:) public class OrderCustom extends User { private int orderId; //订单id private int user_id; //用户id private String orde

总结篇——从零搭建maven多模块springboot+mybatis项目

一.创建maven父工程: 1.修改pom.xml,最终结果如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="htt