MyBatis 高级映射与懒加载

10 篇文章 0 订阅
3 篇文章 0 订阅

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<Emp> empList;
	
	public List<Emp> getEmpList() {
		return empList;
	}
	public void setEmpList(List<Emp> 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及相关代码

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.pactera.spring.mapper.DepartmentMapper">
<resultMap      id="DepartmentResultMap2" 
				type="com.pactera.spring.model.Department">
			 <id column="id" property="id"/>
			 <result column="name" property="name"/>
		     <association 
                     property="projectManage"
					 column="project_manage_id"
					 javaType="com.pactera.spring.model.Manager"
					 select="selectManagerById"
		/>
			 </resultMap>
    <!-- 根据id查找部门-->
    <select id="selectById" parameterType="int"
		resultMap="DepartmentResultMap2">
		SELECT 
			id ,
		 	name ,
		  	project_manage_id
		 FROM 
		 	department 
		 WHERE id=#{id}
	</select>
   <!-- 根据id查找经理-->
   <select id="selectManagerById" parameterType="int" 
		resultType="com.pactera.spring.model.Manager">
		select id,name,position,age,department_id as departmentId
		from manager where id=#{id}
	</select>
</mapper>

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

春天整合MyBatis的配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test_db?useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="maxActive" value="1000"/>
        <property name="initialSize" value="1000"/>
    </bean>
    <bean id="factoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:config/mapper/*.xml"/>
        <property name="configLocation" value="classpath:config/mybatis-config.xml"/>
        <!-- <property name="plugins" ref="list"/> -->
        <!--<property name="configurationProperties" ref="configurationProperties"/>-->
    </bean>
    <util:properties id="configurationProperties">
        <prop key="lazyLoadingEnabled">true</prop>
    </util:properties>
    <util:list id="list" value-type="org.apache.ibatis.plugin.Interceptor" list-class="java.util.ArrayList">
        <ref bean="interceptor"/>
    </util:list>
    <bean id="interceptor" class="com.pactera.spring.interceptor.MyBatisInterceptor"/>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.pactera.spring.mapper"/>
    </bean>

MyBatis的-config.xml中

<?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>
    <!-- <properties resource="config/config.properties">
        <property name="username" value="dev_user" />
        <property name="password" value="F2Fa3!33TYyg" />
    </properties> -->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <!--<setting name="lazyLoadingEnabled" value="true"/>-->
        <!-- 所有的方法都会加载该对象所有的属性-->
       <!-- <setting name="aggressiveLazyLoading" value="false"/>-->
        <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->

    </settings>
    <typeAliases>
        <package name="com.pactera.spring.model"/>
    </typeAliases>

</configuration>

道服务控制器

public interface DepartmentMapper {
	Department selectById(int id);
}

@Service
public 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查询,不过这个查询时是联合查询;

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

  修改后查询语句如下:

<resultMap id="DepartmentResultMap"
               type="com.pactera.spring.model.Department">
        <id column="department_id" property="id"/>
        <result column="department_name" property="name"/>
        <association property="projectManage"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_project_manage_id" resultMap="managerResultMap"
        />
    </resultMap>
    <!-- 嵌套查询的映射集合-->
    <resultMap id="managerResultMap" type="com.pactera.spring.model.Manager" >
        <id column="manager_id" property="id"/>
        <result column="manager_name" property="name"/>
        <result column="manager_position" property="position"/>
        <result column="manager_age" property="age"/>
        <result column="manager_department_id" property="departmentId"/>
    </resultMap>
    <select id="selectById" parameterType="int"
            resultMap="DepartmentResultMap">
		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=#{id}
	</select>

执行请求后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结果映射中的字段名保持一致。

<select id="selectById" parameterType="int"
            resultMap="DepartmentResultMap">
		SELECT 
			d.id as department_id,
		 	d.name as department_name,
		  	d.project_manage_id as department_project_manage_id,
		    d.product_manage_id as department_product_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,

			mp.id as product_manager_id,
            mp.name as product_manager_name,
			mp.position as product_manager_position,
			mp.age as product_manager_age,
			mp.department_id as product_manager_department_id
		 FROM 
		 	department d
		 left outer 
		 	join manager m 
		 on d.project_manage_id = m.id
		 left outer
		 	join manager mp
		 on d.product_manage_id = mp.id
		 WHERE d.id=#{id}
	</select>

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

<resultMap id="DepartmentResultMap"
               type="com.pactera.spring.model.Department">
        <id column="department_id" property="id"/>
        <result column="department_name" property="name"/>
        <association property="projectManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_project_manage_id" resultMap="managerResultMap"
        />
        <association property="productManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_product_manage_id" resultMap="managerResultMap"
					 columnPrefix="product_"
        />

执行请求,请求结果修改为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}

集合查询

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

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>或者
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" resultMap="***Result"/>

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

    嵌套查询

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

<select id="selectEmpByDepartmentId" parameterType="int" resultType="com.pactera.spring.model.Emp">
        SELECT id,name,gender,age,department_id as departmentId FROM emp WHERE department_id =#{departmentId}
    </select>

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

<resultMap id="DepartmentResultMap"
               type="com.pactera.spring.model.Department">
        <id column="department_id" property="id"/>
        <result column="department_name" property="name"/>
        <association property="projectManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_project_manage_id" resultMap="managerResultMap"
        />
        <association property="productManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_product_manage_id" resultMap="managerResultMap"
					 columnPrefix="product_"
        />
        <!-- 新增-->
        <collection property="empList" column="department_id" select="selectEmpByDepartmentId"
                    ofType="com.pactera.spring.model.Emp"/>
    </resultMap>

 说明: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.编写嵌套结果的映射结果集

<resultMap id="empResultMap" type="com.pactera.spring.model.Emp">
        <id column="emp_id" property="id"/>
        <result column="emp_name" property="name"/>
        <result column="gender" property="gender"/>
        <result column="emp_age" property="age"/>
        <result column="emp_department_id" property="departmentId"/>
    </resultMap>

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

<resultMap id="DepartmentResultMap"
               type="com.pactera.spring.model.Department">
        <id column="department_id" property="id"/>
        <result column="department_name" property="name"/>
        <association property="projectManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_project_manage_id" resultMap="managerResultMap"
        />
        <association property="productManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_product_manage_id" resultMap="managerResultMap"
					 columnPrefix="product_"
        />
        <!-- 新增-->
        <!--<collection property="empList" column="department_id" select="selectEmpByDepartmentId"
                    ofType="com.pactera.spring.model.Emp"/>-->
        <!-- 新增集合嵌套结果-->
        <collection property="empList" column="department_id" resultMap="empResultMap" ofType="com.pactera.spring.model.Emp"/>
    </resultMap>

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

<select id="selectById" parameterType="int"
            resultMap="DepartmentResultMap">
		SELECT 
			d.id as department_id,
		 	d.name as department_name,
		  	d.project_manage_id as department_project_manage_id,
		    d.product_manage_id as department_product_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,

			mp.id as product_manager_id,
            mp.name as product_manager_name,
			mp.position as product_manager_position,
			mp.age as product_manager_age,
			mp.department_id as product_manager_department_id,

            e.id as emp_id,
            e.name as emp_name,
            e.gender as emp_gender,
            e.age as emp_age,
            e.department_id as emp_department_id

		 FROM 
		 	department d
		 left outer 
		 	join manager m 
		 on d.project_manage_id = m.id
		 left outer
		 	join manager mp
		 on d.product_manage_id = mp.id
		 left outer
		 	join emp e
		 on d.id = e.id
		 WHERE d.id=#{id}
	</select>

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书写如下:

<resultMap id="DepartmentResultMap"
               type="com.pactera.spring.model.Department">
        <id column="department_id" property="id"/>
        <result column="department_name" property="name"/>
        <association property="projectManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_project_manage_id" resultMap="managerResultMap"
        />
        <association property="productManager"
                     javaType="com.pactera.spring.model.Manager"
                     column="department_product_manage_id" resultMap="managerResultMap"
					 columnPrefix="product_"
        />
        <!-- 新增-->
        <!--<collection property="empList" column="department_id" select="selectEmpByDepartmentId"
                    ofType="com.pactera.spring.model.Emp"/>-->
        <!-- 新增集合嵌套结果-->
        <!--<collection property="empList" column="department_id" resultMap="empResultMap" ofType="com.pactera.spring.model.Emp"/>-->
        <collection property="empList" column="department_id" ofType="com.pactera.spring.model.Emp">
            <id column="emp_id" property="id"/>
            <result column="emp_name" property="name"/>
            <result column="gender" property="gender"/>
            <result column="emp_age" property="age"/>
            <result column="emp_department_id" property="departmentId"/>
        </collection>
    </resultMap>

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

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

总结

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

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值