1.关联映射(多表查询)
数据库中多表之间存在着三种关系,如图所示。
多对多: 程序员<------>项目 用户--------->角色
一对多: 班级------>学生 学校------>班级 帅哥----->多个女朋友 土豪---多辆豪车
一对一: 学生----->学位证 人------>DNA 公民----身份证 房子--产权证
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。
现实生活中实体和实体之间的关系: 一对多 多对多 一对一
关系:是双向的!!
2.关联映射作用
在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存在着一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表。那么Mybatis中是如何实现这种多表关系的映射呢?
查询结果集ResultMap
resultMap元素是 MyBatis中最重要最强大的元素。它就是让你远离90%的需要从结果 集中取出数据的JDBC代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事 情。
有朋友会问,之前的示例中我们没有用到结果集,不是也可以正确地将数据表中的数据映射到Java对象的属性中吗?是的。这正是resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
resultMap元素中,允许有以下直接子元素:
id - 作用与result相同,同时可以标识出用这个字段值可以区分其他对象实例。可以理解为数据表中的主键,可以定位数据表中唯一一笔记录
result - 将数据表中的字段注入到Java对象属性中
association - 关联,简单的讲,就是“有一个”关系,如“用户”有一个“帐号” has a
collection - 集合,顾名思议,就是“有很多”关系,如“客户”有很多“订单” has many
每个元素的用法及属性我会在下面结合使用进行讲解。
3. 一对一关联(了解)
2.1. 需求
根据班级id查询班级信息(带老师的信息)
2.2.创建数据表
创建一张教师表和班级表,这里我们假设一个老师只负责教一个班,那么老师和班级之间的关系就是一种一对一的关系。
CREATE TABLE teacher(
t_id INT PRIMARY KEY AUTO_INCREMENT,
t_name VARCHAR(20)
);
CREATE TABLE class(
c_id INT PRIMARY KEY AUTO_INCREMENT,
c_name VARCHAR(20),
teacher_id INT
);
ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES teacher(t_id);
INSERT INTO teacher(t_name) VALUES('teacher1');
INSERT INTO teacher(t_name) VALUES('teacher2');
INSERT INTO class(c_name, teacher_id) VALUES('class_a', 1);
INSERT INTO class(c_name, teacher_id) VALUES('class_b', 2);
2.3.定义实体类
1、Teacher类,Teacher类是teacher表对应的实体类。
public class Teacher {
// 定义实体类的属性,与teacher表中的字段对应
private int id; // id===>t_id
private String name; // name===>t_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 + "]";
}
}
2、Classes类,Classes类是class表对应的实体类
public class Classes {
// 定义实体类的属性,与class表中的字段对应
private int id; // id===>c_id
private String name; // name===>c_name
/**
* class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性,
* 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的
*/
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 "Classes [id=" + id + ", name=" + name + ", teacher=" + teacher + "]";
}
}
2.4.定义sql映射文件classMapper.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.mybatis.mapper.ClassMapper">
<!--
select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1
-->
<select id="getClass1" parameterType="int" resultMap="ClassResultMap1">
select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
</select>
<!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->
<resultMap type="Classes" id="ClassResultMap1">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" javaType="Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
</mapper>
2.5 编写单元测试类
@Test
public void test1(){
Classes c1 = mapper.getClass1(1);
System.out.println(c1);
}
@Test
public void test2(){
Classes c1 = mapper.getClass2(1);
System.out.println(c1);
}
2.6 MyBatis一对一关联查询总结
MyBatis中使用association(有一个)标签来解决一对一的关联查询,association标签可用的属性如下:
property:对象属性的名称
javaType:对象属性的类型
column:所对应的外键字段名称
select:使用另一个查询封装的结果
4. 一对多关联(重点)
2.1.需求
根据classId查询对应的班级信息,包括学生
2.2.定义实体类
1、Student类
/**
* 学生实体
*/
public class Student {
private long sId;
private String sName;
private long sAge;
private String sEmail;
private long classId;
//额外准备一个班级对象
private Classes classes; //体现一个学生在一个班级中
public Classes getClasses() {
return classes;
}
public void setClasses(Classes classes) {
this.classes = classes;
}
public long getSId() {
return sId;
}
public void setSId(long sId) {
this.sId = sId;
}
public String getSName() {
return sName;
}
public void setSName(String sName) {
this.sName = sName;
}
public long getSAge() {
return sAge;
}
public void setSAge(long sAge) {
this.sAge = sAge;
}
public String getSEmail() {
return sEmail;
}
public void setSEmail(String sEmail) {
this.sEmail = sEmail;
}
public long getClassId() {
return classId;
}
public void setClassId(long classId) {
this.classId = classId;
}
@Override
public String toString() {
return "Student{" +
"sId=" + sId +
", sName='" + sName + '\'' +
", sAge=" + sAge +
", sEmail='" + sEmail + '\'' +
", classId=" + classId +
", classes=" + classes +
'}';
}
}
2、修改Classes类,添加一个List students属性,使用一个List集合属性表示班级拥有的学生,如下:
package com.bruceliu.bean;
import java.util.List;
/**
* 班级实体类 1的一方
*/
public class Classes {
private long cId;
private String cName;
//表示含义 1个班级下有多个学生
private List<Student> students; //学生集合 多的一方
public long getCId() {
return cId;
}
public void setCId(long cId) {
this.cId = cId;
}
public String getCName() {
return cName;
}
public void setCName(String cName) {
this.cName = cName;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Classes{" +
"cId=" + cId +
", cName='" + cName + '\'' +
", students=" + students +
'}';
}
}
2.3.修改sql映射文件classMapper.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.bruceliu.mapper.ClassesMapper">
<!--配置1对多 结果集映射-->
<resultMap id="classMap" type="Classes">
<!--主键-->
<id property="cId" column="C_ID"/>
<result property="cName" column="c_name"/>
<!--配置一个包含关系 “有很多”关系 -->
<collection property="students" ofType="Student">
<id property="sId" column="s_id"/>
<result property="sName" column="s_name"/>
<result property="sAge" column="s_age"/>
<result property="sEmail" column="s_email"/>
<result property="classId" column="class_id"/>
</collection>
</resultMap>
<select id="getById" resultMap="classMap">
SELECT C.*,S.* FROM classes C INNER JOIN student S on C.c_id=S.class_id where C.c_id=#{classId}
</select>
</mapper>
2.4. 编写单元测试类
package com.bruceliu.test;
import com.bruceliu.bean.Classes;
import com.bruceliu.mapper.ClassesMapper;
import com.bruceliu.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author bruceliu
* @create 2019-07-09 10:06
* @description 测试1对多
*/
public class TestOne2Many {
SqlSession session=null;
ClassesMapper classesMapper=null;
@Before
public void init(){
session = MyBatisUtils.getSession();
classesMapper = session.getMapper(ClassesMapper.class);
}
@Test
public void test1(){
Classes c = classesMapper.getById(1L);
System.out.println(c);
}
@After
public void destory(){
session.close();
}
}
2.5. MyBatis一对多关联查询总结
MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。
5. 多对多关联(重点)
3.1 需求
3.2. 准备SQL语句
3.3 User实体类
package com.bruceliu.bean;
import java.util.List;
public class User {
private long uId;
private String uName;
private String uSex;
private long uAge;
private List<Role> roles; //一个用户下面多个角色
public long getUId() {
return uId;
}
public void setUId(long uId) {
this.uId = uId;
}
public String getUName() {
return uName;
}
public void setUName(String uName) {
this.uName = uName;
}
public String getUSex() {
return uSex;
}
public void setUSex(String uSex) {
this.uSex = uSex;
}
public long getUAge() {
return uAge;
}
public void setUAge(long uAge) {
this.uAge = uAge;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"uId=" + uId +
", uName='" + uName + '\'' +
", uSex='" + uSex + '\'' +
", uAge=" + uAge +
", roles=" + roles +
'}';
}
}
3.4 Role实体类
package com.bruceliu.bean;
public class Role {
private long rId;
private String rName;
public long getRId() {
return rId;
}
public void setRId(long rId) {
this.rId = rId;
}
public String getRName() {
return rName;
}
public void setRName(String rName) {
this.rName = rName;
}
@Override
public String toString() {
return "Role{" +
"rId=" + rId +
", rName='" + rName + '\'' +
'}';
}
}
3.5 UserMapper
package com.bruceliu.mapper;
import com.bruceliu.bean.User;
/**
* @author bruceliu
* @create 2019-07-09 11:10
* @description
*/
public interface UserMapper {
//1.根据ID查询user(同时关联查询一下role集合信息)
public User getUserByid(long uid);
}
3.6 UserMapper.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.bruceliu.mapper.UserMapper">
<resultMap id="userMap" type="User">
<id property="uId" column="u_id"/>
<result property="uAge" column="u_age"/>
<result property="uName" column="u_name"/>
<result property="uSex" column="u_sex"/>
<!--一个用户多个角色-->
<collection property="roles" ofType="Role">
<id property="rId" column="r_id"/>
<result property="rName" column="r_name"/>
</collection>
</resultMap>
<select id="getUserByid" resultMap="userMap">
SELECT * FROM `user` U INNER JOIN role_user RU ON U.u_id=RU.uu__id INNER JOIN role R ON RU.rr_id=R.r_id
where U.u_id=#{uid}
</select>
</mapper>
3.7 测试
package com.bruceliu.test;
import com.bruceliu.bean.Student;
import com.bruceliu.bean.User;
import com.bruceliu.mapper.StudentMapper;
import com.bruceliu.mapper.UserMapper;
import com.bruceliu.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author bruceliu
* @create 2019-07-09 11:20
* @description
*/
public class TesMany2Many {
SqlSession session=null;
UserMapper userMapper=null;
@Before
public void init(){
session = MyBatisUtils.getSession();
userMapper = session.getMapper(UserMapper.class);
}
/**
* 测试根据人查询所属的角色集合
*/
@Test
public void test1(){
User user = userMapper.getUserByid(1);
System.out.println(user);
}
@After
public void destory(){
session.close();
}
}