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

最近一直在学习MyBatis,虽然在实际的开发中,使用MyBatis也有一段时间了,但是我知道自己对于MyBatis的掌握还停留在表面,于是决定潜心学习MyBatis。

本篇博客的主题是如何实现MyBatis的延迟加载(也叫懒加载),因为延迟加载通常与分步查询一起使用,所以也说一下关于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>

映射文件编写(使用)


这里使用高级映射resultMap,在 resultMap内使用association标签自定义嵌套部门信息,其中:

property---JavaBean内成员变量名,这里就是员工实体类里定义的部门类变量名

select---指定查询方法,命名方式为对应XML文件的命名空间+查询ID(Mapper接口的包路径+方法名)

column---参数列名,指下一步查询的参数对应的列名。这里查询部门需要用到的参数为员工主键,因此此处的column应为员工主键的数据库列名

<!-- 分步查询映射定义 -->
        <resultMap type="com.xuyong.entity.Employee" id="MyEmpByStep">
        	<id column="id" property="id"/>
        	<result column="last_name" property="lastName"/>
        	<result column="gender" property="gender"/>
        	<result column="email" property="email"/>
        	<association property="department" select="com.xuyong.dao.DepartmentDao.getDepartmentById" column="id">
        	</association>
        </resultMap>
定义部门Mapper接口,与上文中的select值相对应
/**
 * 部门类Mapper接口
 * 
 * @author xuyong
 *
 */
public interface DepartmentDao {

	/**
	 * 根据部门主键获取部门信息
	 * 
	 * @param id
	 * @return
	 */
	Department getDepartmentById(Integer id);
	
}
相应的xml编写
这里直接使用resultType进行映射,开发中建议使用resultMap

<!-- 根据主键获取部门信息 -->
        <select id="getDepartmentById" resultType="com.xuyong.entity.Department">
        	select id,department_name from department 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>

三、结语

到这里这篇博客就结束了(what?就这么结束了?延迟加载就没了?),我认为技术本身的实现不是最重要的,重要的是如何把技术完美的应用到实际开发中,提高我们的代码质量,提高产品的性能。以此共勉。

猜你喜欢

转载自blog.csdn.net/postersxu/article/details/79053916