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

Posted wang-java-begining

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis学习——分步查询与延迟加载相关的知识,希望对你有一定的参考价值。

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

一、分步查询

分步查询通常应用于关联表查询,如:电商平台,查询订单信息时需要查询部分的用户信息;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, 徐永, 男, [email protected], 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 protected], 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, 徐永, 男, [email protected], 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 protected], 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>











以上是关于MyBatis学习——分步查询与延迟加载的主要内容,如果未能解决你的问题,请参考以下文章

mybatis级联查询,分步查询和延迟加载

MyBatis测试resultMap,分步查询以及延迟加载

[mybatis]映射文件_select_resultMap_关联查询_association分步查询&延迟加载

resultMap2_关联查询collection分步查询&延迟加载

MyBatis学习--延迟加载

MyBatis学习11MyBatis中的延迟加载