玩转Mybatis的高级关系映射(实战加详解,保姆级教程)

目录

  • 前置知识点
  • 实战所需数据库、配置文件等相关代码
  • 实战一对一(精讲)
  • 实战一对多
  • 实战多对一
  • 实战多对多
  • 总结

关联关系

一对一:在任意一方引入对方主键作为外键。
一对多:在“多”的一方,添加“一”的一方的主键作为外键。
多对多:产生中间关系表,引入两张表的主键作为外键,两个主键成为联合主键或使用新的字段作为主键。

ResultMap(结果映射)

玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第1张图片
简单理解如下:

<resultMap id="唯一的标识" type="映射的pojo对象">
  <id column="表的主键字段,或者可以为查询语句中的别名字段" jdbcType="字段类型" property="映射pojo对象的主键属性" />
  <result column="表的一个字段(可以为任意表的一个字段)" jdbcType="字段类型" property="映射到pojo对象的一个属性(须为type定义的pojo对象中的一个属性)"/>
  <association property="pojo的一个对象属性" javaType="pojo关联的pojo对象">
    <id column="关联pojo对象对应表的主键字段" jdbcType="字段类型" property="关联pojo对象的主席属性"/>
    <result  column="任意表的字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
  </association>
  <!-- 集合中的property须为oftype定义的pojo对象的属性-->
  <collection property="pojo的集合属性" ofType="集合中的pojo对象">
    <id column="集合中pojo对象对应的表的主键字段" jdbcType="字段类型" property="集合中pojo对象的主键属性" />
    <result column="可以为任意表的字段" jdbcType="字段类型" property="集合中的pojo对象的属性" />  
  </collection>

Mybatis中javaType和jdbcType对应关系:

JDBC Type    Java Type
  
CHAR                String  
VARCHAR             String  
LONGVARCHAR         String  
NUMERIC             java.math.BigDecimal  
DECIMAL             java.math.BigDecimal  
BIT             	boolean  
BOOLEAN             boolean  
TINYINT             byte  
SMALLINT            short  
INTEGER             int  
BIGINT              long  
REAL                float  
FLOAT               double  
DOUBLE              double  

实战演练

Mybatis在映射文件中加载关联关系对象主要通过两种方式:嵌套查询和嵌套结果。下面的代码中我们将会对这两种方式进行详细的解释加运用。

相关代码

数据库相关代码

CREATE DATABASE IF NOT EXISTS resultmap DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

USE resultmap

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8


INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

SELECT * FROM teacher
SELECT * FROM student

Java中的pojo老师类

package com.tz.pojo;

public class Teacher {
	private int id;
	private String name;
	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;
	}
	@Override
	public String toString() {
		return "Teacher [id=" + id + ", name=" + name + "]";
	}
	
	
}

Java中的pojo学生类

package com.tz.pojo;

public class Student {
	private int id;
	private String name;
	
	//关联一个老师
	private Teacher teacher;

	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 Teacher getTeacher() {
		return teacher;
	}

	public void setTeacher(Teacher teacher) {
		this.teacher = teacher;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", teacher=" + teacher + "]";
	}
	
	
}

相关配置文件

  1. db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/resultmap?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
  1. mybatis-config.xml

DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>
 
<properties resource="db.properties">properties>

<typeAliases>
	
	<package name="com.tz.pojo"/>
typeAliases>





<environments default="development"> 
<environment id="development">

<transactionManager type="JDBC" />
 
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
dataSource>
environment>
environments>
 
<mappers>
	
	<package name="com.tz.mapper"/>
mappers>
configuration>
  1. log4j.properties
log4j.rootCategory=INFO, stdout   
log4j.logger.com.tz=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender   
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout   
log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n   

实战一:一对一

每个学生都对应一个老师,当我们查询某个学生的信息时,也能把这名学生对应老师的信息查出来
举例:(学生的tid对应老师的id,t_id为老师的id,这里为我们起的别名)
在这里插入图片描述

数据库代码如下:

SELECT student.id,student.name,student.tid,teacher.id t_id,teacher.name FROM student,teacher 
WHERE student.tid = teacher.id AND student.id = 1

方式一:嵌套结果(级联属性封装结果集)

StudentMapper.java

public Student selectStudentById(int id);

StudentMapper.xml

<mapper namespace="com.tz.mapper.StudentMapper">
	<resultMap type="com.tz.pojo.Student" id="map1">
		<id column="id" property="id" />
		<result column="name" property="name" />
		<result column="tid" property="tid"  />
		<result column="t_id" property="teacher.id" />			
		<result column="t_name" property="teacher.name"  />			
	resultMap>
	
	<select id="selectStudentById" resultMap="map1">
		SELECT student.id,student.name,student.tid,teacher.id t_id,teacher.name t_name FROM student,teacher 
		WHERE student.tid = teacher.id AND student.id = #{id}
	select>

//或者
<resultMap type="com.tz.pojo.Student" id="map1">
		<id column="id" property="id" />
		<result column="name" property="name" />
		<result column="tid" property="tid"  />
		<association property="teacher" javaType="com.tz.pojo.Teacher">
			<id column="t_id" property="id" />
			<result column="t_name" property="name" />
		association>
					
	resultMap>
	
	<select id="selectStudentById" resultMap="map1">
		SELECT student.id,student.name,student.tid,teacher.id t_id,teacher.name t_name FROM student,teacher 
		WHERE student.tid = teacher.id AND student.id = #{id}
	select>

test.java

@Test
	public void test1() throws IOException{
		
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		Student student = mapper.selectStudentById(1);
		System.out.println(student);
		
		sqlSession.close();
	}

结果:

Student [id=1, name=小明, tid=1, teacher=Teacher [id=1, name=秦老师]]

结果说明:
这种方法是执行一次select查询,一次查询两个表,然后再对这个联合结果表进行映射,只有最后才是对联合结果表进行一次映射。这种方式的resultMap会自行关闭自动映射,所以需要把所有需要映射的字段都显式写出来。原因在于结果表只有一张,如果两张表中有名称相同的字段,会产生歧义。结果表可以有两个列都叫id,但是对于映射来说列名是唯一标签,所以会产生歧义。所以这种情况下,要对有歧义的列名起别名。在这种情况下association中的列名不是原列名,而是起的别名。这样得出一张结果表后,先整体映射为指定的对象类型,再把其中要映射为属性或者需要处理的列去映射成相应的属性。而把需要映射为association的列摘出来,根据提供的对象类型把它们映射成相应的对象,赋给上层的对象的属性。

方式二:嵌套查询

association分步查询

SELECT * FROM student WHERE id = 1
SELECT * FROM teacher WHERE id = 1

在这里插入图片描述
解释:
我们可以进行分步查询,即先查询出相关学生的信息,然后根据学生的tid属性(外键)查出相关老师的信息

association进行分步查询

  1. 先按照学生id查询学生信息
  2. 根据学生信息中的tid去教师表中查出学生对应的教师信息
  3. 将对应的教师相关信息设置到学生中

TeacherMapper接口:

public Teacher easySelectTeacherById(int id);

TeacherMapper.xml

<mapper namespace="com.tz.mapper.TeacherMapper">
	<select id="easySelectTeacherById" resultType="com.tz.pojo.Teacher">
		select teacher.id,teacher.name from teacher where id=#{id}
	select>
mapper>

StudentMapper接口:

public Student easySelectStudentById(int id);

StudentMapper.xml

<resultMap type="com.tz.pojo.Student" id="map">
		<id column="t_id" property="id"/>
		<result column="t_name" property="name"/>
		<result column="tid" property="tid"/>
		
		<association property="teacher"
		javaType="com.tz.pojo.Teacher" 
		select="com.tz.mapper.TeacherMapper.easySelectTeacherById" 
		column="tid">
			
		association>
	resultMap>
	
	<select id="easySelectStudentById" resultMap="map">
		SELECT id t_id,name t_name,tid FROM student WHERE id = #{id}
	select>

测试:

@Test
	public void test2() throws IOException{
		
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		Student student = mapper.easySelectStudentById(2);
		System.out.println(student);
		sqlSession.close();
	}

代码说明:
association中嵌套select语句,这样的执行顺序是:先执行上层select找出resultMap中除了复杂映射的其他部分,默认开启部分自动映射,因此其他部分会被自动映射,除了有的映射不到,需要使用类型处理器。接着执行association中的select语句,并且将column值(如果列起的别名需要将别名传入)作为参数传入easySelectTeacherById,tid就会被当做easySelectTeacherById中的 #{id} ,执行完之后将查询结果进行封装然后 自动映射到assiciation中,因为在TeacherMapper.xml中已经将teacher对象封装好并将相关值进行传递,所以在StudentMapper.xml文件中的resultmap中的association属性中不需要再添加子属性进行显示的映射

结果:
在这里插入图片描述
在这里插入图片描述
过程说明:

使用association定义关联的单个对象的封装规则
我们查询的主体是学生,然后需要把一些额外的信息加上(学生对应的老师的相关信息)

这样做相当于把查询分了两步:
1.在Student中先select查询id为给定值的所有元组(行),把能自动映射或者指定了类型处理器的属性先映射到Student对象上

2.再根据上述元组中的tid属性去select查询Teacher表中id值等于Student中tid的行,并把查询结果集映射到Teacher对象上,并且把这个Teacher对象赋给上面的Student对象的teacher属性

整体就是两步select,根据第一步查询出的结果的某个字段去进行第二步查询。
先构建出第一步查询的结果对象obj1,然后把第二步查询的结果对象obj2赋给obj1对应的属性。

(先得到结果表1,对其进行映射,再使用其中的某字段得到结果表2,再对结果表2进行映射,将映射结果2嵌入映射结果1的属性中。整个过程产生了两张结果表,因为有两个select,因为是先后查询两张表,所以表中有重复的字段也不会产生冲突)

实战一总结

1.如果使用了select属性,则本质上要进行两次查询,得到两张结果表,因为是从不同的表中查询(相同也无所谓)且每次只查询一个表,所以二者互不干涉,所以不存在字段歧义的问题,自动映射会开启。对于分步查询(涉及到association属性),则传给select需要赋值的列的属性不会自动映射,其余属性会自动映射(在满足自动映射的条件下),分步查询依次查询两张表,即使两张表中有相同的字段名也不会冲突
2.如果使用resultMap属性,或者association本身当做一个resultMap(嵌套结果),则本质是对多张表的一次联合查询,只产生一张结果表。然后对这张结果表一次进行映射。可能存在字段歧义问题,自动映射会关闭(如果没有指明映射关系的字段会得到空值)。此时要把产生歧义的字段起别名,并且把所有需要映射的字段都显式写出来。

分步查询扩展说明

对于多个表中均出现属性名与字段名不匹配的问题
以上面的例子为例:
student属性如下:

	private int id;
	private String name;
	private int tid;
	private Teacher teacher;

我们在查询时起个别名

SELECT id sid,name sname,tid stid FROM student WHERE id = #{id}
//上面之所以是where id 而不是 where sid 与数据库的执行顺序有关

这时需要
用resultmap对其进行映射

<resultMap type="com.tz.pojo.Student" id="map">
		<id column="sid" property="id"/>
		<result column="sname" property="name"/>
		<result column="stid" property="tid"/>
		<association property="teacher"
		javaType="com.tz.pojo.Teacher" 
		select="com.tz.mapper.TeacherMapper.easySelectTeacherById" 
		column="stid">
			
		</association>
	</resultMap>

teacher属性如下:

	private Integer id;
	private String name;

字段名如下:

t_id
t_name

方法一:起别名,在查询时将字段名的别名与属性名变为一致(这样使用resultType即可)

select t_id id,t_name name from teacher where t_id=#{tid}

方法二:resultMap

<resultMap type="com.tz.pojo.Teacher" id="teacherMap">
		<id column="t_id" property="id"/>
		<result column="t_name" property="name"/>
	</resultMap>
	<select id="easySelectTeacherById" resultMap="teacherMap">
		select t_id,t_name from teacher where t_id=#{tid}
	</select>

总结:
对于分步查询
玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第2张图片
第一步:先执行
在这里插入图片描述
然后查出来相关内容到resultmap上进行映射
在这里插入图片描述

然后执行association里面的select语句,将查询结果映射到其resultmap中(类名与字段名不一致的情况下)
玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第3张图片
然后将封装好的Teacher对象赋值给student的属性teacher。
在这里插入图片描述
第一个resultMap的association属性中无需再用子属性进行显示的封装,因为在执行完association属性中的select对应的sql语句之后,已经进行了封装,封装好后直接传递给association(javaType和property等属性已经可以确保正确接收),此时也不存在字段名和属性名不一致的映射问题,字段名和属性名不一致的映射问题在执行association属性中的select对应的sql语句之后进行相关值的封装时应该进行解决。
对照本例:
此时在封装teacher对象时进行字段名和属性名不一致问题的解决(如果不一致时)
玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第4张图片
然后将封装好的Teacher对象(此时已经解决了字段名和属性名不一致的问题了,所以association属性无需再设置相关子属性进行映射,此时已经开启自动映射)赋值给association属性即可

嵌套查询(分步查询)的延迟加载

在mybatis-config.xml文件的中进行配置
在这里插入图片描述

一对多

场景:
一个老师教很多个学生,我们在查询某个老师(主体)的信息时需要将相关学生的信息查询出来
例如:

SELECT teacher.`t_id`,teacher.`t_name`,student.`id`,student.`name`,student.`tid` FROM teacher,student
WHERE teacher.`t_id`=student.`tid` 

结果:
玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第5张图片

嵌套结果

此时需要在Teacher.java中添加下面代码

	private List<Student> students;
	public List<Student> getStudents() {
			return students;
		}
	public void setStudents(List<Student> students) {
			this.students = students;
		}

TeacherMapper.java

public Teacher selectTeacherAndStudent(int id);

TeacherMapper.xml

<resultMap type="com.tz.pojo.Teacher" id="teacherAndStudent">
		<id column="t_id" property="id"/>
		<result column="t_name" property="name"/>
		<!-- collection定义关联集合类型的属性的封装规则 
			ofType:指定集合里面元素的类型
		-->
		<collection property="students" ofType="com.tz.pojo.Student">
			<id column="id" property="id"/>
			<result column="name" property="name"/>
			<result column="tid" property="tid"/>
			<association property="teacher" javaType="com.tz.pojo.Teacher">
				<id column="t_id" property="id"/>
				<result column="t_name" property="name"/>
			</association>
		</collection>
			
	</resultMap>
	<!-- 一对多方式一嵌套结果 -->
	<select id="selectTeacherAndStudent" resultMap="teacherAndStudent">
		SELECT teacher.`t_id`,teacher.`t_name`,student.`id`,student.`name`,student.`tid` FROM teacher,student
			WHERE teacher.`t_id`=student.`tid` AND teacher.`t_id`=#{id}
	</select>

测试代码

@Test
	public void test4() throws IOException{
		
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
		Teacher t = mapper.selectTeacherAndStudent(1);
		
		System.out.println(t);
		sqlSession.close();
	}

结果:
在这里插入图片描述
在这里插入图片描述

方法二嵌套查询

思路与相关注意事项与一对一查询类似,这里就不再赘述

SELECT * FROM teacher WHERE t_id = 1
SELECT * FROM student WHERE tid = 1

为方便演示,在Student.java中暂时将teacher属性打上注释

StudentMapper.java

public List<Student> selectStudentsByTid(int tid);

StudentMapper.xml

<select id="selectStudentsByTid" resultType="com.tz.pojo.Student">
		select * from student where tid = #{tid}
	</select>

TeacherMapper.java

public Teacher selectTeacherById(int id);

TeacherMapper.xml

<resultMap type="com.tz.pojo.Teacher" id="teacherAndStudent2">
		<id column="t_id" property="id"/>
		<result column="t_name" property="name"/>
		<!-- collection定义关联集合类型的属性的封装规则 
			ofType:指定集合里面元素的类型
		-->
		<collection property="students" ofType="com.tz.pojo.Student"
		select="com.tz.mapper.StudentMapper.selectStudentsByTid" column="t_id">
			<id column="id" property="id"/>
			<result column="name" property="name"/>
			<result column="tid" property="tid"/>
			<association property="teacher" javaType="com.tz.pojo.Teacher">
				<id column="t_id" property="id"/>
				<result column="t_name" property="name"/>
			</association>
		</collection>
	</resultMap>
	<!-- 一对多方式二嵌套查询 -->
	<select id="selectTeacherById" resultMap="teacherAndStudent2">
		select * from teacher where t_id = #{id}
	</select>

测试代码

@Test
	public void test5() throws IOException{
		
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
		Teacher t = mapper.selectTeacherById(1);
		
		System.out.println(t);
		sqlSession.close();
	}

结果
在这里插入图片描述
在这里插入图片描述

多对一

多个学生对应一个老师,当我们一次性查多个学生时,可以将其对应的老师相关信息也查出来

例如:

SELECT student.`id`,student.`name`,student.`tid`,teacher.`t_id`,teacher.`t_name` FROM student,teacher
WHERE student.`tid` = teacher.`t_id`

玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第6张图片
为了更好地演示,我们将数据库表进行了一些修改如上图所示,然后对pojo表的更新如下:
Student.java

private int id;
	private String name;
	private int tid;
	private Teacher teacher;
	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 int getTid() {
		return tid;
	}
	public void setTid(int tid) {
		this.tid = tid;
	}
	public Teacher getTeacher() {
		return teacher;
	}
	public void setTeacher(Teacher teacher) {
		this.teacher = teacher;
	}
	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", tid=" + tid + ", teacher=" + teacher + "]";
	}

Teacher.java

private Integer id;
	private String name;
	
	public Integer getId() {
		return id;
	}


	public void setId(Integer id) {
		this.id = id;
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	@Override
	public String toString() {
		return "Teacher [id=" + id + ", name=" + name + "]";
	}

方法一:嵌套结果

StudentMapper.java

public List<Student> selectStudentsALL();

StudentMapper.xml

<resultMap type="com.tz.pojo.Student" id="allStudent">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="tid" property="tid"/>
		<association property="teacher" javaType="com.tz.pojo.Teacher">
			<id column="t_id" property="id" />
			<result column="t_name" property="name" />
		association>
	resultMap>
	
	<select id="selectStudentsALL" resultMap="allStudent">
	SELECT student.`id`,student.`name`,student.`tid`,teacher.`t_id`,teacher.`t_name` FROM student,teacher
	WHERE student.`tid` = teacher.`t_id`
	select>

测试代码

@Test
	public void test6() throws IOException{
		
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		List<Student> list = mapper.selectStudentsALL();
		
		System.out.println(list);
		sqlSession.close();
	}

结果
在这里插入图片描述
在这里插入图片描述

方法二嵌套查询

TeacherMapper.java

public Teacher selectTeacherById2(int id);

TeacherMapper.xml

<resultMap type="com.tz.pojo.Teacher" id="map6">
		<id column="t_id" property="id"/>
		<result column="t_name" property="name"/>
	</resultMap>
	<select id="selectTeacherById2" resultMap="map6">
		select * from teacher where t_id = #{id}
	</select>

StudentMapper.java

public List<Student> selectStudentsALL2();

StudentMapper.xml

<resultMap type="com.tz.pojo.Student" id="allStudent2">
		<id column="id" property="id" />
		<result column="name" property="name" />
		<result column="tid" property="tid"  />
		<association property="teacher" javaType="com.tz.pojo.Teacher"
		select="com.tz.mapper.TeacherMapper.easySelectTeacherById" column="tid">
			
		</association>	
	</resultMap>
	<!--多对一嵌套查询  -->
	<select id="selectStudentsALL2" resultMap="allStudent2">
		select * from student
	</select>

测试代码

@Test
	public void test7() throws IOException{
		
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		List<Student> list = mapper.selectStudentsALL2();
		
		System.out.println(list);
		sqlSession.close();
	}

结果:
玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第7张图片
玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第8张图片
根据日志的理解:
先查询出了所有学生(此时只有学生的一些字段),然后开始查询第一个学生的tid(2),将其作为参数传递给select中的sql语句,然后查出来id为2的老师,然后将id为2的老师的相关信息映射到所有tid为2的学生信息中。此时tid为1的学生的老师相关信息还没有,然后将tid(1)将其作为参数传递给select中的sql语句,然后查出来id为1的老师,然后将id为1的老师的相关信息映射到所有tid为1的学生信息中。

扩展:嵌套查询传递多列值及延迟加载

玩转Mybatis的高级关系映射(实战加详解,保姆级教程)_第9张图片

多对多

对于多对多的查询,以商品和订单为例,在数据库设计中,因为商品表和订单表之间有多对多的关系,所以在设计数据库时需要增加一个商品表和订单表的关联表,此表需要相关代码自动维护。为方便展示,这里手动进行增添数据。

场景:商品和订单,一个商品可以出现在多个订单中,一个订单中也可以出现多个商品,查询订单时可以查出相关的商品,查询商品时可以查出相关的订单

思路:进行多对多查询时需要中间表,思路与一对多相同

相关代码:

数据库:

CREATE TABLE `tb_product` (
  `id` INT(10) PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(30) DEFAULT NULL,
  `price` DOUBLE
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO tb_product (NAME,price) VALUES ("java基础入门",44.5)
INSERT INTO tb_product (NAME,price) VALUES ("javaweb基础入门",22.5)
INSERT INTO tb_product (NAME,price) VALUES ("SSM框架实战",50)

CREATE TABLE `tb_user` (
  `id` INT(10) PRIMARY KEY AUTO_INCREMENT,
  `username` VARCHAR(32),
  `address` VARCHAR(256)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO tb_user VALUES ("1","詹姆斯","克利夫兰")
INSERT INTO tb_user VALUES ("2","哈登","休斯顿")
INSERT INTO tb_user VALUES ("3","科比","洛杉矶")

CREATE TABLE `tb_orders` (
  `id` INT(10) PRIMARY KEY AUTO_INCREMENT,
  `number` VARCHAR(32),
  `user_id` INT(32) NOT NULL,
  FOREIGN KEY(`user_id`) REFERENCES tb_user(`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO tb_orders VALUES (1,"1000011","1")
INSERT INTO tb_orders VALUES (2,"1000012","1")
INSERT INTO tb_orders VALUES (3,"1000013","2")

--中间表
CREATE TABLE `tb_ordersitem` (
  `id` INT(32) PRIMARY KEY AUTO_INCREMENT,
  `orders_id` INT(32),
  `product_id` INT(32),
  FOREIGN KEY(`orders_id`) REFERENCES tb_orders(`id`),
  FOREIGN KEY(`product_id`) REFERENCES tb_product(`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO tb_ordersitem VALUES (1,"1","1")
INSERT INTO tb_ordersitem VALUES (2,"1","3")
INSERT INTO tb_ordersitem VALUES (3,"3","3")

db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

log4j.properties

log4j.rootCategory=INFO, stdout   
log4j.logger.com.haust=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender   
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout   
log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n   

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>
<!-- 是用 resource 属性加载外部配置文件 --> 
<properties resource="db.properties" />

<typeAliases>
	<!-- <typeAlias type="com.haust.pojo.User"/> -->
	<package name="com.haust.pojo"/>
</typeAliases>




<!-- 和 spring 整合后 environments 配置将废除 -->
<environments default="development"> 
<environment id="development">
<!-- 使用 jdbc 事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 --> 
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 --> 
<mappers>
	<!-- <mapper class="com.haust.mapper.UserMapper" /> -->
	<package name="com.haust.mapper"/>
</mappers>
</configuration>

Orders.java

package com.haust.pojo;

import java.util.List;

public class Orders {
	private Integer id;
	private String number;
//	private List productList;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getNumber() {
		return number;
	}
	public void setNumber(String number) {
		this.number = number;
	}
//	public List getProductList() {
//		return productList;
//	}
//	public void setProductList(List productList) {
//		this.productList = productList;
//	}
//	@Override
//	public String toString() {
//		return "Orders [id=" + id + ", number=" + number + ", productList=" + productList + "]";
//	}
	@Override
	public String toString() {
		return "Orders [id=" + id + ", number=" + number + "]";
	}
	
	
}

Product.java

package com.haust.pojo;

import java.util.List;

public class Product {
	private Integer id;
	private String name;
	private Double price;
	private List<Orders> ordersList;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Double getPrice() {
		return price;
	}
	public void setPrice(Double price) {
		this.price = price;
	}
	public List<Orders> getOrdersList() {
		return ordersList;
	}
	public void setOrdersList(List<Orders> ordersList) {
		this.ordersList = ordersList;
	}
	@Override
	public String toString() {
		return "Product [id=" + id + ", name=" + name + ", price=" + price + ", ordersList=" + ordersList + "]";
	}
	
	
}

嵌套查询

OrdersMapper.java

	public List<Orders> selectOrdersByThree(int id);

ProductMapper.java

	public List<Product> selectProAndOrd();

OrdersMapper.xml

<?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.haust.mapper.OrdersMapper">
	<select id="selectOrdersByThree" parameterType="int"
	resultType="Orders" >
		select id,number from tb_orders where id in 
		(select orders_id from tb_ordersitem where product_id = #{id})
	</select>
</mapper>

ProductMapper.xml

<?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.haust.mapper.ProductMapper">
	<resultMap type="com.haust.pojo.Product" id="map1">
		<id column="id" property="id" />
		<collection property="ordersList" ofType="com.haust.pojo.Orders"
		select="com.haust.mapper.OrdersMapper.selectOrdersByThree" column="id"/>
	</resultMap>
	<select id="selectProAndOrd" parameterType="int"
	resultMap="map1">
		SELECT * FROM tb_product 
	</select>
</mapper>

test.java

@Test
	public void test1() throws IOException{
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
		List<Product> list = mapper.selectProAndOrd();
		for (Product product : list) {
			System.out.println(product);
		}
		
		sqlSession.close();
	}

结果:

[QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ==>  Preparing: SELECT * FROM tb_product 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ==> Parameters: 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ====>  Preparing: select id,number from tb_orders where id in (select orders_id from tb_ordersitem where product_id = ?) 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ====> Parameters: 1(Integer)
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | <====      Total: 1
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ====>  Preparing: select id,number from tb_orders where id in (select orders_id from tb_ordersitem where product_id = ?) 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ====> Parameters: 2(Integer)
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | <====      Total: 0
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ====>  Preparing: select id,number from tb_orders where id in (select orders_id from tb_ordersitem where product_id = ?) 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ====> Parameters: 3(Integer)
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | <====      Total: 2
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | <==      Total: 3
   Product [id=1, name=java基础入门, price=44.5, ordersList=[Orders [id=1, number=1000011]]]
Product [id=2, name=javaweb基础入门, price=22.5, ordersList=[]]
Product [id=3, name=SSM框架实战, price=50.0, ordersList=[Orders [id=1, number=1000011], Orders [id=3, number=1000013]]]

结果分析

本次查询的形式是嵌套查询,首先执行ProductMapper.xml文件中的SELECT * FROM tb_product ,然后将查出来相应的记录,此时仅仅查出来了tb_product表中的相关字段,此时的主体pojo(Product)的resultmap所有属性并没有全部查询完毕,然后会进行子标签collection中的select属性中对应位置的sql语句,因为需要将tb_product表中的id字段作为参数传入select属性中对应位置的sql语句。因为不同记录(行)的id字段值不相同所以会多次查询select属性中对应位置的sql语句,直到所有主体pojo(Product)的属性全部封装完毕。

简而言之:1.先执行外层的select语句,然后查出相应的记录,将相关字段进行封装,然后执行嵌套的select语句,将相应的column字段属性作为参数传入,因为相应的column字段属性(假设为id)的值可能不同(假设按照先后顺序的值为1、2、3)此时先将1作为参数传入嵌套的select语句然后执行并将相关结果(可能有多个关联pojo对象)封装好之后映射到column列参数为1的主体pojo对象…以此类推直至不同的id都查询过并封装。
注意事项
对于一对多的情况
在数据库中以一个商品对应多个订单的情况
在数据库中是两条记录
在这里插入图片描述
在Java中是一个主体pojo的属性中有一个关联pojo的集合

Product [id=3, name=SSM框架实战, price=50.0, ordersList=[Orders [id=1, number=1000011], Orders [id=3, number=1000013]]]

嵌套结果

ProductMapper.java

public List<Product> SelectAllByOnce();

ProductMapper.xml

<resultMap type="com.haust.pojo.Product" id="map2">
		<id column="pid" property="id"/>
		<result column="name" property="name"/>
		<result column="price" property="price"/>
		<collection property="ordersList" ofType="orders">
			<id column="oid" property="id"/>
			<result column="number" property="number"/>
		</collection>
	</resultMap>
	<select id="SelectAllByOnce" resultMap="map2">
		SELECT tb_product.`id` pid,tb_product.`name`,tb_product.`price` price,tb_orders.`id` oid,tb_orders.`number`
		FROM tb_orders,tb_product,tb_ordersitem
		WHERE tb_ordersitem.`orders_id`=tb_orders.`id` AND tb_ordersitem.`product_id`=tb_product.`id`
	</select>

test.java

@Test
	public void test2() throws IOException{
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
		List<Product> list = mapper.SelectAllByOnce();
		for (Product product : list) {
			System.out.println(product);
		}
		
		sqlSession.close();
	}

结果:

[QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ==>  Preparing: SELECT tb_product.`id` pid,tb_product.`name`,tb_product.`price` price,tb_orders.`id` oid,tb_orders.`number` FROM tb_orders,tb_product,tb_ordersitem WHERE tb_ordersitem.`orders_id`=tb_orders.`id` AND tb_ordersitem.`product_id`=tb_product.`id` 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | ==> Parameters: 
   [QC] DEBUG [main] org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.debug(54) | <==      Total: 3
   Product [id=1, name=java基础入门, price=44.5, ordersList=[Orders [id=1, number=1000011]]]
Product [id=3, name=SSM框架实战, price=50.0, ordersList=[Orders [id=1, number=1000011], Orders [id=3, number=1000013]]]

多对多嵌套查询总结:
多表(联立两表、三表…)查询,先进行笛卡尔积,然后将符合限制条件的记录筛选出来

总结

  1. 在使用resultmap的情况下,不涉及多表查询,仅仅是单表查询,会自动映射,在resultmap的子元素中只需要处理字段名和属性名不一致的情况即可。
  2. 多表查询时,如果是嵌套结果则会关闭自动映射,因为仅仅select一次,产生一张表,如果开启自动映射时可能会产生字段名相同的冲突,所以需要显示的写出相关的映射代码。
  3. 多表查询时,如果是嵌套查询则会开启自动映射,若为两张表时,通过观察日志记录可知,会产生两张表,但是一张表可能会查询多次(在多对一的情况下)。
  4. 对于association中的select值为:名称空间.id。
  5. 在多表的嵌套查询时,resultMap的association属性不用再写子属性进行映射,因为association中的select的sql语句执行完之后将相关对象封装好赋值给association,此时已经没有字段名和属性名不一致的问题且已经开启了自动映射。
  6. resultMap的type属性是查询的pojo主体(以谁为主),且此pojo主体中有与之相关连(主体的附加信息,比如查询学生(主体),需要将相关老师(与主体相关联)的信息也查出来)的pojo属性。
  7. 从一对多或者多对一嵌套查询运行的日志记录来看,嵌套查询的第一个select语句在查询出主体pojo的多条记录(此时只有主体pojo的属性值,关联pojo的相关信息需要下一个select(子查询)获取),若tid为association属性中select的参数,此时需要根据第一行记录的tid(假设为1)作为参数传递给association属性中select的sql语句,查询完毕后封装对象,赋值给所有tid为1的主体,若此时还有tid为2的记录(行),则重复上述操作,直至不同tid值的记录的所需要查询的值(关联pojo的相关信息)全部查询完毕。

你可能感兴趣的:(mybatis,resultMap,association)