栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

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

ResultMap(结果映射)


简单理解如下:


  
  
  
    
    
  
  
  
    
      
  
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

 
 



	
	






 



 








 

	
	


  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


	
		
		
		
					
					
	
	
	
		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}
	

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


	
		SELECT id t_id,name t_name,tid FROM student WHERe id = #{id}
	

测试:

@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对其进行映射


		
		
		
		
			
		
	

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


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

总结:
对于分步查询

第一步:先执行

然后查出来相关内容到resultmap上进行映射

然后执行association里面的select语句,将查询结果映射到其resultmap中(类名与字段名不一致的情况下)

然后将封装好的Teacher对象赋值给student的属性teacher。

第一个resultMap的association属性中无需再用子属性进行显示的封装,因为在执行完association属性中的select对应的sql语句之后,已经进行了封装,封装好后直接传递给association(javaType和property等属性已经可以确保正确接收),此时也不存在字段名和属性名不一致的映射问题,字段名和属性名不一致的映射问题在执行association属性中的select对应的sql语句之后进行相关值的封装时应该进行解决。
对照本例:
此时在封装teacher对象时进行字段名和属性名不一致问题的解决(如果不一致时)

然后将封装好的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` 

结果:

嵌套结果

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

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

TeacherMapper.java

public Teacher selectTeacherAndStudent(int id);

TeacherMapper.xml


		
		
		
		
			
			
			
			
				
				
			
		
			
	
	
	
		select * from student where tid = #{tid}
	

TeacherMapper.java

public Teacher selectTeacherById(int id);

TeacherMapper.xml


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

测试代码

@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 list = mapper.selectStudentsALL();
		
		System.out.println(list);
		sqlSession.close();
	}

结果

方法二嵌套查询

TeacherMapper.java

public Teacher selectTeacherById2(int id);

TeacherMapper.xml


		
		
	
	
		select * from student
	

测试代码

@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 list = mapper.selectStudentsALL2();
		
		System.out.println(list);
		sqlSession.close();
	}

结果:


根据日志的理解:
先查询出了所有学生(此时只有学生的一些字段),然后开始查询第一个学生的tid(2),将其作为参数传递给select中的sql语句,然后查出来id为2的老师,然后将id为2的老师的相关信息映射到所有tid为2的学生信息中。此时tid为1的学生的老师相关信息还没有,然后将tid(1)将其作为参数传递给select中的sql语句,然后查出来id为1的老师,然后将id为1的老师的相关信息映射到所有tid为1的学生信息中。

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

总结
  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的相关信息)全部查询完毕。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/444742.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号