博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis 高级映射与懒加载
阅读量:4230 次
发布时间:2019-05-26

本文共 12739 字,大约阅读时间需要 42 分钟。

MyBatis的高级映射是该框架非常重要的一个功能,在开发中有广泛应用,在面试中也是常考的内容,下面我们就学习下高级映射。

       引言:MyBatis的的高级映射分为关联和集合两种,关联是一对一关系的映射,集合是一对多关系的映射,都分为嵌套查询和嵌套结果两种实现方式;嵌套查询在数据量不大和关联不多的时候使用比较合适,当数据量大的时候,会出现N + 1问题,导致执行的SQL很多,加载很慢,另外也可以指定是否使用懒加载模式(请看下文中的说明);嵌套结果使用联合查询,在大量数据的情况下能有效解决N + 1问题。

        注意:本学习使用的MyBatis的版本为3.4.6

 业务场景设计:

    实体部门,一个部门只有一个项目经理和一个产品经理,但是有多名员工,实体类如下:

public class Department {	//id	private int id;	//部门名 eg.software	private String name;	//项目经理	private Manager projectManage;	//产品经理	private Manager productManager;	//多名员工	private List
empList; public List
getEmpList() { return empList; } public void setEmpList(List
empList) { this.empList = empList; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Manager getProjectManage() { return projectManage; } public void setProjectManage(Manager projectManage) { this.projectManage = projectManage; } public Manager getProductManager() { return productManager; } public void setProductManager(Manager productManager) { this.productManager = productManager; } @Override public String toString() { return "Department [id=" + id + ", name=" + name + "]"; } }
public class Manager {	//经理id	private int id;	private String name;	private String position;	private int age;	//经理所在的部门id	private int departmentId;		public int getDepartmentId() {		return departmentId;	}	public void setDepartmentId(int departmentId) {		this.departmentId = departmentId;	}	public int getId() {		return id;	}	public void setId(int id) {		this.id = id;	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}	public String getPosition() {		return position;	}	public void setPosition(String position) {		this.position = position;	}	public int getAge() {		return age;	}	public void setAge(int age) {		this.age = age;	}	@Override	public String toString() {		return "Manager [id=" + id + ", name=" + name + ", position=" + position + ", age=" + age + ", departmentId="				+ departmentId + "]";	}	}

 

public class Emp{	//员工id	private int id;	private String name;	private String gender;	private int age;	//员工所在的部门id	private int departmentId;		public int getDepartmentId() {		return departmentId;	}	public void setDepartmentId(int departmentId) {		this.departmentId = departmentId;	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}	public String getGender() {		return gender;	}	public void setGender(String gender) {		this.gender = gender;	}	public int getAge() {		return age;	}	public void setAge(int age) {		this.age = age;	}	public int getId() {		return id;	}	public void setId(int id) {		this.id = id;	}	public  Emp clone() throws CloneNotSupportedException{		return (Emp) super.clone();	}	@Override	public String toString() {		return "Emp{" +				"id=" + id +				", name='" + name + '\'' +				", gender='" + gender + '\'' +				", age=" + age +				'}';	}}

SQL:

create table department (	id int auto_increment,	name varchar(128) not null,	project_manage_id int not null,	product_manage_id int not null,	primary key (id));
create table manager (	id int auto_increment,	name varchar(128) not null,	position varchar(64) not null,	age int not null,	department_id int not null,	primary key (id));
insert into 	manager		 (name,position,age,department_id) 	values 		('張三','projm',32,1);	insert into 	manager		 (name,position,age,department_id) 	values 		('李四','prodm',34,1);insert into 	department		 (name,project_manage_id,product_manage_id) 	values 		('software',1,2);

 

关联查询 - 嵌套查询

   嵌套查询 - 示例

    格式:<association property =“author”column =“blog_author_id”javaType =“Author”select =“selectAuthor”/>

    需求:查询一个部门的时候,同时查询出该部门的项目经理

    实现:在departmentMapper.xml中编写SQL及相关代码

使用弹簧整合的MyBatis的方式在控制器层调用服务 - >映射器的方式调用departmentMapper.xml中的selectById方法:

春天整合MyBatis的配置:

true

MyBatis的-config.xml中

道服务控制器

public interface DepartmentMapper {	Department selectById(int id);}@Servicepublic class DepartmentService implements IDepartmentService{    @Autowired    DepartmentMapper departmentMapper;    @Override    public Department selectById(int id) {        return departmentMapper.selectById(id);    }}@RestController@RequestMapping("/department")public class DepartmentController {	@Autowired	IDepartmentService departmentService;	@GetMapping("/select")	public String getDepartment(@RequestParam("id") int id) {		Department department = departmentService.selectById(id);		department.getProjectManage().getAge();		return "ok";	}	/**	 * consumes-用来限制请求实体头域中的content-Type,只要请求中的实体头域content-Type头域值在consumes指定的类型中	 * 则可以正常处理,否则会报406错误,406 Not Acceptable	 * produces-用来限制请求头域中的Accept,只要该参数指定的值都不在请求头域中的Accept值中, 则报415错误,Unsupported	 * Media Type;设置响应体中实体头域content-Type的作用 以上两处校验是在HandlerMapping中校验的	 * 	 * @param id	 * @return	 * @throws Exception	 */	@GetMapping(value = "/select/projM", consumes = "application/json", produces = { "text/html;charset=utf-8" })	public String getDepartmentProjM(@RequestParam("id") int id) throws Exception {		Department department = departmentService.selectById(id);//		System.out.println(department.toString());//		department.getId();		return "查询成功";	}}

执行请求本地主机:8080 /部门/选择/ projM ID = 1,日志SQL显示如下: 

变量监控显示:

关联查询成功!

懒加载怎么处理呢?

    比如我现在有的service层方法不需要使用productManager属性,有的service层方法需要使用productManager属性,那么我们可以指定策略来完成这个需求。先看看懒加载相关的配置:

    1.lazyLoadingEnabled

    属于全局配置, 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。有效值 true | false,默认值 false。也就说默认情况下是不会延迟加载,所以我们的查询直接执行了嵌套查询的sql。

    2.aggressiveLazyLoading

    属于全局配置,当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。有效值 true | false,默认值 false(true in ≤3.4.1)。

    3.fetchType

     属于局部配置,使用在mapper.xml中的resultMap标签的association标签中(当使用嵌套查询方式时),该配置有效值为eager|lazy,无默认值,该值会覆盖全局配置中lazyLoadingEnabled。

   4.lazyLoadTriggerMethods

    属于全局配置,指定哪个对象的方法触发一次延迟加载。用逗号分隔的方法列表。默认值equals,clone,hashCode,toString。

     我们主要针前三个属性进行学习和测试

    开启全局延迟加载 -- 添加全局配置lazyLoadingEnabled并设置值为true

 

重新执行请求, sql执行日志如下:

设置嵌套查询的 fetchType属性为eager,代表本关联查询不使用懒加载,修改如下:

执行请求sql日志如下:

修改fetchType属性为lazy,在controller层方法中添加代码department.getId()后执行请求,sql日志显示如下:

可以看到没有执行嵌套查询,因为执行department.getId()方法时,不会触发嵌套查询。当我们修改aggressiveLazyLoading为true时,任何方法的调用都会加载该对象的所有属性,那么执行department.getId()方法时就会触发嵌套查询。现在修改aggressiveLazyLoading为true,执行请求,sql日志显示如下:

关联查询-嵌套结果

    格式:<association property="projectManage" javaType="com.pactera.spring.model.Manager"                      column="department_project_manage_id" resultMap="managerResultMap" />

    需求:查询一个部门的时候,同时查询出该部门的项目经理

    实现:在departmentMapper.xml中编写sql及相关代码

    异同:嵌套查询会产生两次sql查询,嵌套结果是一个sql查询,不过这个查询时是联合查询;

               嵌套查询可以使用懒加载模式,嵌套结果不能。

  修改后查询语句如下:

执行请求后sql日志显示如下:

[DEBUG] 2018-12-17 16:31:53,787(18231) --> [catalina-exec-4] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159): ==>  Preparing: SELECT d.id as department_id, d.name as department_name, d.project_manage_id as department_project_manage_id, m.id as manager_id, m.name as manager_name, m.position as manager_position, m.age as manager_age, m.department_id as manager_department_id FROM department d left outer join manager m on d.project_manage_id = m.id WHERE d.id=?   [DEBUG] 2018-12-17 16:31:53,819(18263) --> [catalina-exec-4] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159): ==> Parameters: 1(Integer)  [DEBUG] 2018-12-17 16:31:53,836(18280) --> [catalina-exec-4] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159): <==      Total: 1

属性 columnPrefix--重用嵌套结果的映射结果集

业务场景:查询部门的时候同时查询出部门的项目经理和产品经理,

a.联合查询语句修改如下:

再次外联查询一次manager表,并将产品经理的结果字段命名为带product_前缀的,前缀之后的名字都与managerResultMap结果映射中的字段名保持一致。

b.DepartmentResultMap修改如下:新增了产品经理的关联结果配置,columnPrefix的值一定是包含了下划线的,也就说我们要保证除了前缀外剩余的字段名都要与managerResultMap中的保持一致。

执行请求,请求结果修改为department的toString()方法结果,结果显示为:

Department{id=1, name='finance', projectManager=Manager [id=1, name=張三, position=projm, age=32, departmentId=1], productManager=Manager [id=2, name=李四, position=prodm, age=34, departmentId=1], empList=null}

集合查询

    现在根据查询一个部门的项目经理和产品经理已经完成了,但是现在我还要查询一个部门的所有员工,这个时候我们就可以使用集合查询,基本的格式为:

或者

前者为集合查询的嵌套查询后者为集合查询的嵌套结果

    嵌套查询

    1.编写嵌套查询的sql语句

 2.修改DepartmentResultMap,新增嵌套查询语句

 说明:collection标签中property属性为该集合在实体中的属性名,column为与集合元素关联的字段,比如本例子中,员工属于那个部门,所以该字段为部门id,最后在查询语句中使用部门id查询其所有的员工信息。select为嵌套查询的查询语句id,ofType为集合元素的类型。也可以添加javaType属性指定该集合的类型,但是MyBatis一般会推断出该类型,故一般不写此属性。

3.执行请求查看结果如下:

Department{id=1, name='finance', projectManager=Manager [id=1, name=張三, position=projm, age=32, departmentId=1], productManager=Manager [id=2, name=李四, position=prodm, age=34, departmentId=1], empList=[Emp{id=516926, name='jack', gender='f', age=20}, Emp{id=516927, name='rose', gender='m', age=19}, Emp{id=516928, name='lily', gender='m', age=45}, Emp{id=516929, name='joy', gender='f', age=17}, Emp{id=516930, name='jobs', gender='f', age=32}]}

嵌套结果

1.编写嵌套结果的映射结果集

2.修改DepartmentResultMap,新增嵌套结果语句

3.修改selectById的查询语句,新增emp列映射和外联emp表

4.执行请求,结果如下:

Department{id=1, name='finance', projectManager=Manager [id=1, name=張三, position=projm, age=32, departmentId=1], productManager=Manager [id=2, name=李四, position=prodm, age=34, departmentId=1], empList=[]}

查询成功!!!

将empResultMap单独作为一个映射结果集可以提高复用率,如果不需要复用,则可以简化DepartmentResultMap书写如下:

如上,则empResultMap结果映射就不需要了。

如果出现如关联相似的相同元素集合,可以使用属性columnPrefix做区分。

总结

    MyBatis的的高级映射功能,在数据量不大的情况下,嵌套查询和嵌套结果都是不错的选择,当然你可以根据自己的业务需求以及自己的编程爱好进行选择。

转载地址:http://lasqi.baihongyu.com/

你可能感兴趣的文章
Python笔记:初识Matplotlib和Seaborn
查看>>
Typescript 中的接口的实现
查看>>
Typescript中的泛型的使用
查看>>
Fix pymysql.err.OperationalError 2003 on Mac
查看>>
Java内部类以及类的加载顺序(一)
查看>>
JavaScript中使用offset时遇到的bug
查看>>
java基础入门(一)
查看>>
Java基础入门(二)
查看>>
Java基础入门(三)
查看>>
Java基础入门(四)
查看>>
Java基础入门(十)
查看>>
Java基础入门(完结篇)
查看>>
Java进阶之面向对象(一)——继承
查看>>
Java进阶之自定义ArrayList&斗地主发牌案例
查看>>
JavaWeb之filter&listener&文件上传
查看>>
JavaWeb之Ajax&json
查看>>
BUFG,IBUFG,BUFGP,IBUFGDS等含义以及使用
查看>>
转载:在 Windows 10 下遇到移动硬盘不自动分配盘符的问题
查看>>
阻抗匹配基础
查看>>
DDR2 SSTL_18标准
查看>>