Mybatis 对象关系分类 一对多 多对一 多对多

一、关系概述

1、关系应用

生活中数据很多是存在关系的,就是把生活中有关系的数据通过 MyBatis 持久化到数据库,且存储的数据也能表示出来这种关系,再由数据库中把有关系的数据查询出来在页面展示。

  • 保存:页面的数据 ---> 使用 Java 对象封装 ---> 通过 MyBatis ---> 数据库表的数据

  • 查询:数据库表的数据 ---> 通过 MyBatis ---> 封装成 Java 对象 ---> 页面展示数据

那么这里需要解决问题:

  • 怎么使用数据库表设计来表示数据之间关系;

  • 怎么使用 Java 类设计来表示对象之间关系;

  • 怎么通过 MyBatis 配置来映射上面两者(翻译)。

2、对象关系分类

  • 泛化关系

  • 实现关系

  • 依赖关系

  • 关联关系

  • 聚合关系

  • 组合关系

3、关联关系

A 对象依赖 B 对象,并且把 B 作为 A 的一个成员变量,则 A 和 B 存在关联关系。在 UML 中依赖通常使用实线箭头表示。

3.1、关联关系分类

3.1.1、按照导航性分

若通过 A 对象中的某一个属性可以访问到 B 对象,则说 A 可以导航到 B。

  • 单向:只能从 A 通过属性导航到 B,B 不能导航到 A。

  • 双向:A 可以通过属性导航到 B,B 也可以通过属性导航到 A。

3.1.2、按照多重性分
  • 一对一

  • 一对多

  • 多对一

  • 多对多。

4、判断对象的关系

  • 判断都是从对象的实例上面来看的;

  • 判断关系需要根据对象的属性;

  • 判断关系必须确定具体需求

二、单向多对一之保存

拷贝之前的项目,改项目名为 many2one,再导入。

1、需求

保存一个部门和两个员工,且这两个员工都是这个部门的。

2、表设计

3、类设计

package cn.domain;
​
@Setter
@Getter
@ToString
public class Department {
    private Long id;
    private String name;
}
package cn.domain;
​
@Setter
@Getter
@ToString
public class Employee {
    private Long id;
    private String name;
    // 关联属性
    private Department dept;
}

4、Mapper 接口和 Mapper XML 文件编写

注意 Mapper XML 放置的位置

package cn.mapper;
​
public interface DepartmentMapper {
    void save(Department dept);
}
<!-- 
    useGeneratedKeys=true 获取数据库保存数据的的主键值
    keyProperty="id" 主键设置设置对象的 id 属性
-->
<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO department(name) VALUES(#{name})
</insert>
package cn.mapper;
​
public interface EmployeeMapper {
    void save(Employee employee);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO employee(name, dept_id) VALUES(#{name}, #{dept.id})
</insert>

5、编写单元测试类

public class Many2oneTest {
    // 保存一个部门和两个员工,且这两个员工都是这个部门的
    @Test
    public void testSave() throws Exception {
        Department dept = new Department();
        dept.setName("开发部");
        
        Employee e1 = new Employee();
        e1.setName("张三");
        e1.setDept(dept); // 设置关系
        Employee e2 = new Employee();
        e2.setName("李四");
        e2.setDept(dept); // 设置关系
        
        SqlSession session = MyBatisUtil.getSession();
        DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class);
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        // 先保存部门再保存员工
        departmentMapper.save(dept);
        employeeMapper.save(e1);
        employeeMapper.save(e2);
        
        session.commit();
        session.close();
    }
}

三、单向多对一之额外 SQL 查询

1、需求

根据员工 id 查询员工,并知道该员工的所在的部门。

2、修改员工的 Mapper 接口及 Mapper XML

Employee get(Long id);
<select id="get" resultType="Employee">
    SELECT id, name, dept_id FROM employee WHERE id = #{id}
</select>

3、编写单元测试方法

public class Many2oneTest {
    @Test
    public void testGet() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
        Employee employee = employeeMapper.get(1L);
        System.out.println(employee);
        session.close();
    }
}

4、存在的问题

发现查询出来的员工部门为 null,原因是当结果集的列名与对象的属性名不一致。

那么我们可以在 EmployeeMapper.xml,通过 resultMap 元素来解决。

<select id="get" resultMap="baseResultMap">
    SELECT id, name, dept_id FROM employee WHERE id = #{id}
</select>
​
<resultMap type="Employee" id="baseResultMap">
    <!-- 什么列名对应值封装到对象的什么属性上 -->
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="dept_id" property="dept.id"/>
</resultMap>

但问题只查询出部门的 id,但要查询出部门的名称怎么办呢?

5、手动发送额外 SQL

既然已获得部门的 id,那么再在 DepartmentMapper 中提供 get 方法根据 id 查询部门对象,再把该部门对象设置到员工对象的 dept 属性上。

5.1、修改部门的 Mapper 接口及 Mapper XML

Department get(Long id);
<select id="get" resultType="Department">
    SELECT id, name FROM department WHERE id = #{id}
</select>

5.2、修改单元测试方法

public class Many2oneTest {
    @Test
    public void testGet() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
        Employee employee = employeeMapper.get(1L); // 此时员工对象 dept 的 id 属性上已存在对应的部门 id 值
    
        // 再调用 DepartmentMapper 接口中根据 id 查询的方法,把对应部门查询出来
        DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class);
        Department dept = departmentMapper.get(employee.getDept().getId());
        // 把查询出来部门对象设置 employee 对象的 dept 属性上
        employee.setDept(dept);
        
        System.out.println(employee);
        session.close();
    }
}

通过控制台发现会额外发送 SQL 去查询,但问题这种操作(发送额外的 SQL 数据和设置 dept 属性操作)都不想自己做怎么办呢?

6、使用 association 发送额外 SQL

6.1、修改员工 Mapper XML 文件

<resultMap type="Employee" id="baseResultMap">
    <!-- 什么列名对应值封装到对象的什么属性上 -->
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <!-- 使用额外 SQL
        association 针对的关联属性配置,非集合类型
        select      发送什么额外 SQL
        column      发送额外 SQL 参数取上一条 SQL 哪个列的值
        property    封装员工对象的什么属性
    -->
    <association select="cn.mapper.DepartmentMapper.get" 
        column="dept_id" property="dept" javaType="Department"/>
</resultMap>

额外 SQL 方式可以不配置 javaType 属性(select 元素那已配置了),后面讲的多表查询方式一定要配置。

6.2、修改单元测试方法

public class Many2oneTest { 
    @Test
    public void testGet() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
        Employee employee = employeeMapper.get(1L);
        System.out.println(employee);
        session.close();
    }
}

四、单向多对一之多表查询

1、额外 SQL 查询 N+1 问题

需求:查询所有员工及其对应部门。假设在 employee 表中有 N 条数据,每一个员工都关联着一个不同的部门 id。当在查询所有员工时,就会发送 N+1 条语句。

  • 1 条:SELECT id, name, dept_id FROM employee

  • N 条:SELECT id, name FROM department WHERE id = ?

2、代码演示问题

2.1、修改员工 Mapper 接口及 Mapper XML

List<Employee> listAll();
<select id="listAll" resultMap="baseResultMap">
    SELECT id, name, dept_id FROM employee
</select>

2.2、编写单元测试方法

public class Many2oneTest {
    @Test
    public void testListAll() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        List<Employee> employees = employeeMapper.listAll();
        System.out.println(employees);
        session.close();
    }
}

3、多表查询

3.1、修改 EmployeeMapper.xml 查询的 SQL

使用多表查询,此时一条 SQL 语句搞定,实现查询所有员工及其对应部门。

<select id="listAll" resultMap="multiTableResultMap">
    SELECT e.id, e.name, d.id AS d_id, d.name AS d_name 
    FROM employee e JOIN department d ON e.dept_id = d.id
</select>

N+1 问题没有,但如何解决结果集映射问题呢?

3.2、使用 resultMap 处理结果集映射

3.2.1、结果集映射方式 1
<resultMap type="Employee" id="multiTableResultMap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="d_id" property="dept.id"/>
    <result column="d_name" property="dept.name"/>
</resultMap>

问题:属性过多,dept. 则会重复写好多。

3.2.2、结果集映射方式 2
<resultMap type="Employee" id="multiTableResultMap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <association property="dept" javaType="Department">
        <result column="d_id" property="id"/>
        <result column="d_name" property="name"/>
    </association>
</resultMap>

问题:当关联表查询的列太多了,那么前缀 d_ 就要写很多。

3.2.3、结果集映射方式 3
<resultMap type="Employee" id="multiTableResultMap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <association columnPrefix="d_" property="dept" javaType="Department">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
    </association>
</resultMap>

五、单向一对多之保存

拷贝之前的项目,改项目名为 one2many,再导入。

1、需求

保存一个部门和两个员工,且这两个员工都是这个部门的。

2、表设计

和多对一表设计是一样的。

3、类设计

package cn.domain;
​
@Setter
@Getter
@ToString
public class Employee {
    private Long id;
    private String name;
}
package cn.domain;

@Setter
@Getter
@ToString
public class Department {
	private Long id;
	private String name;
	// 关联属性,建议集合对象直接 new 出来,避免后面写测试类的时候造成空指针。
	private List<Employee> employees = new ArrayList<>();
}

4、Mapper 接口和 Mapper XML 文件编写

package cn.mapper;

public interface DepartmentMapper {
	void save(Department dept);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO department(name) VALUES(#{name})
</insert>
package cn.mapper;

public interface EmployeeMapper {
	void save(Employee employee);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO employee(name) VALUES(#{name})
</insert>

5、编写单元测试

public class One2manyTest {
    @Test
    public void testSave() throws Exception {
        Department dept = new Department();
        dept.setName("开发部");
        
        Employee e1 = new Employee();
        e1.setName("张三");
        Employee e2 = new Employee();
        e2.setName("李四");
        
        SqlSession session = MyBatisUtil.getSession();
        DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class);
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        departmentMapper.save(dept);    
        employeeMapper.save(e1);
        employeeMapper.save(e2);
    
        session.commit();
        session.close();
    }
}

6、存在的问题及解决办法

问题是保存到员工表中的员工数据没有部门 id。解决办法:

  • 发送额外 SQL 修改员工的部门(性能较低不推荐);

  • 改成双向的关联关系;

  • 在 many 放添加一个 Long 类型的 deptId,在保存部门之后把部门的 id 值设置到员工对象这个 deptId 属性再保存员工。

6.1、修改员工实体类

package cn.domain;
​
@Setter
@Getter
@ToString
public class Employee {
    private Long id;
    private String name;
    private Long deptId; // 这个属性用来封装这个员工的部门 id 值,不是关联属性
}

6.2、修改员工 Mapper XML

<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO employee(name, dept_id) VALUES(#{name}, #{deptId})
</insert>

6.3、修改单元测试方法

public class One2manyTest {
    @Test
    public void testSave() throws Exception {
        Department dept = new Department();
        dept.setName("开发部");
        
        Employee e1 = new Employee();
        e1.setName("张三");
        Employee e2 = new Employee();
        e2.setName("李四");
        
        SqlSession session = MyBatisUtil.getSession();
        DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class);
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        // 先保存部门再保存员工
        departmentMapper.save(dept);
        
        e1.setDeptId(dept.getId());
        e2.setDeptId(dept.getId());
        
        employeeMapper.save(e1);
        employeeMapper.save(e2);
        
        session.commit();
        session.close();
    }
}

六、单向一对多之额外 SQL 查询

1、需求

根据 id 查询部门,并把其部门的员工信息也查询出来。

2、修改部门 Mapper 接口及 Mapper XML

Department get(Long id);
<select id="get" resultType="Department">
    SELECT id, name FROM department WHERE id = #{id}
</select>

3、编写单元测试方法

public class One2manyTest {
    @Test
    public void testGet() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class);
        
        Department department = departmentMapper.get(1L);
        System.out.println(department);
        session.close();
    }
}

4、存在的问题

发现只能查询出部门信息,所属的员工信息没有查询出来,怎么办?

  • 手动额外发送 SQL。通过获取部门的 id 作为参数,再到员工表中根据部门查询员工数据,手动封装到部门的的 employees 属性上。

  • 通过配置使用 MyBatis 来帮我们发送额外 SQL 来完成。

5、使用 collection 发送额外 SQL

5.1、修改部门 Mapper XML

<select id="get" resultMap="baseResultMap">
    SELECT id, name FROM department WHERE id = #{id}
</select>
​
<resultMap type="Department" id="baseResultMap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <!-- 
        若关联属性是集合类型,使用 collection 来配置  
        select      发送额外的 SQL
        column      额外 SQL 参数取之前 SQL 哪里列的值
        property    查询结果封装部门对象什么属性上
    -->
    <collection select="cn.mapper.EmployeeMapper.queryByDeptId" 
        column="id" property="employees"/>
</resultMap>

5.2、修改员工 Mapper XML

<select id="queryByDeptId" resultType="Employee">
    SELECT id, name, dept_id FROM employee WHERE dept_id = #{deptId}
</select>

七、单向多对多之保存

拷贝之前的项目,改项目名为 many2many,再导入。

1、需求

保存两个学生和两个老师,且这两个老师都教了这个两个学生。

2、表设计

3、类设计

package cn.domain;

@Setter
@Getter
@ToString
public class Teacher {
	private Long id;
	private String name;
}
package cn.domain;
​
@Setter
@Getter
@ToString
public class Student {
    private Long id;
    private String name;
    // 关联属性
    private List<Teacher> teachers = new ArrayList<>();
}

4、Mapper 接口和 Mapper XML 文件编写

package cn.mapper;
​
public interface TeacherMapper {
    void save(Teacher teacher);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO teacher(name) VALUES(#{name})
</insert>
package cn.mapper;
​
public interface StudentMapper {
    void save(Student student);
    // 往中间表插入关系数据
    void insertRelation(@Param("teacherId")Long teacherId, @Param("studentId")Long studentId);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO student(name) VALUES(#{name})
</insert>
<insert id="insertRelation">
    INSERT INTO teacher_student(teacher_id, student_id) VALUES (#{teacherId}, #{studentId})
</insert>

5、编写单元测试类

public class Many2manyTest {
    @Test
    public void testSave() throws Exception {
        Teacher teacher1 = new Teacher();
        teacher1.setName("波老师");
        Teacher teacher2 = new Teacher();
        teacher2.setName("罗老师");
        
        Student s1 = new Student();
        s1.setName("小强");
        Student s2 = new Student();
        s2.setName("小红");
        
        s1.getTeachers().add(teacher1);
        s1.getTeachers().add(teacher2);
        // s1 被两个老师教了
        
        s2.getTeachers().add(teacher1);
        s2.getTeachers().add(teacher2);
        // s2 被两个老师教了
        
        SqlSession session = MyBatisUtil.getSession();
        TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
        StudentMapper studentMapper = session.getMapper(StudentMapper.class);
        
        teacherMapper.save(teacher1);
        teacherMapper.save(teacher2);
        
        studentMapper.save(s1);
        studentMapper.save(s2);
        
        // 往中间表存入数据老师教学生的关系数据
        for(Teacher t : s1.getTeachers()) {
            studentMapper.insertRelation(t.getId(), s1.getId());
        }
        for(Teacher t : s2.getTeachers()) {
            studentMapper.insertRelation(t.getId(), s2.getId());
        }
        
        session.commit();
        session.close();
    }
}

八、单向多对多之额外 SQL 查询

1、需求

根据 id 查询学生,并查询教过其的老师。

2、Mapper 接口和 Mapper XML 文件编写

2.1、修改学生 Mapper 接口及 Mapper XML

Student get(Long id);
<resultMap type="Student" id="baseResultMap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <!-- 关联属性,让 MyBatis 发额外 SQL -->
    <collection select="cn.mapper.TeacherMapper.queryByStudentId" 
        column="id" property="teachers"/>
</resultMap>
​
<select id="get" resultMap="baseResultMap">
    SELECT id, name FROM student WHERE id = #{id}
</select>

2.2、修改老师 Mapper XML

<select id="queryByStudentId" resultType="Teacher">
    SELECT t.id, t.name FROM teacher_student ts JOIN teacher t ON ts.teacher_id = t.id
    WHERE ts.student_id = #{studentId}
</select>

3、编写单元测试方法

public class Many2manyTest {
    @Test
    public void testGet() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        StudentMapper studentMapper = session.getMapper(StudentMapper.class);
        Student student = studentMapper.get(2L);
        System.out.println(student);
        session.close();
    }
}

九、单向多对多之删除

1、需求

根据 id 删除学生。

2、Mapper 接口和 Mapper XML 文件编写

2.1、修改学生 Mapper 接口及 Mapper XML

void delete(Long id);
<delete id="delete">
    DELETE FROM student WHERE id = #{id}
</delete>

2.2、编写单元测试方法

public class Many2manyTest {
    @Test
    public void testDelete() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        StudentMapper studentMapper = session.getMapper(StudentMapper.class);
        studentMapper.delete(1L);
        session.commit();
        session.close();
    }
}

3、存在的问题

但问题是:中间还有一些无用数据,因为该学生都已删除了,怎么办?

4、解决办法

4.1、修改学生 Mapper 接口及 Mapper XML

void deleteRelation(Long studentId);
<delete id="deleteRelation">
    DELETE FROM teacher_student WHERE student_id = #{studentId}
</delete>

4.2、修改单元测试方法

public class Many2manyTest {
    @Test
    public void testDelete() throws Exception {
        SqlSession session = MyBatisUtil.getSession();
        StudentMapper studentMapper = session.getMapper(StudentMapper.class);
        
        studentMapper.deleteRelation(1L);
        studentMapper.delete(1L);
        
        session.commit();
        session.close();
    }
}
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java MyBatis中,一对多关系可以通过使用collection来实现。具体而言,MyBatis提供了两种方式来处理一对多关系:嵌套查询和嵌套结果。 1. 嵌套查询: 在嵌套查询中,我们可以通过执行额外的SQL语句来获取与主对象相关联的子对象集合。这需要在映射文件中定义一个额外的select语句,并使用association标签将其与主对象关联起来。然后,在主对象的映射文件中,使用collection标签指定子对象集合的属性名,并引用该额外的select语句。 例如,假设有两个表:Order(订单)和 OrderItem(订单项),一个订单可以包含多个订单项。那么可以按照以下步骤进行配置: a) 定义额外的select语句: ```xml <select id="getOrderItemsByOrderId" resultType="OrderItem"> SELECT * FROM order_item WHERE order_id = #{orderId} </select> ``` b) 在Order的映射文件中,使用collection标签引用该额外的select语句: ```xml <resultMap id="orderResultMap" type="Order"> <!-- 其他属性映射 --> <collection property="orderItems" resultMap="orderItemResultMap"/> </resultMap> ``` 2. 嵌套结果: 在嵌套结果中,我们可以通过一次查询将主对象及其关联的子对象一起加载到内存中。这需要在映射文件中定义一个resultMap,并使用association标签将主对象与子对象关联起来,然后使用collection标签指定子对象集合的属性名。 继续以上面的例子为例: a) 在Order的映射文件中,定义resultMap并使用association和collection标签: ```xml <resultMap id="orderResultMap" type="Order"> <!-- 其他属性映射 --> <collection property="orderItems" ofType="OrderItem"> <id property="id" column="item_id"/> <!-- 其他属性映射 --> </collection> </resultMap> ``` b) 在查询语句中引用该resultMap: ```xml <select id="getOrderById" resultMap="orderResultMap"> SELECT * FROM orders WHERE order_id = #{orderId} </select> ``` 这样配置之后,当执行查询操作时,MyBatis会自动根据配置的方式加载主对象及其关联的子对象集合。 具体作用:通过使用collection实现一对多关系,可以方便地获取主对象关联的子对象集合,从而简化了数据查询和处理的过程。例如,在订单系统中,我们可以轻松地获取某个订单下的所有订单项,或者在商品系统中,获取某个分类下的所有商品列表等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值