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, 徐永, 男, 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>