Mybatis系列之多表关联映射
引言
之前的文章里有提到过使用Mybatis对数据库单表进行映射,执行增删改查操作。但是在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存在着一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表。那么Mybatis中是如何实现这种多表关系的映射呢?这就是本文的主题。
在进入主题之前,我们需要先把之前文章《Mybatis系列之映射文件》中没有提及的知识点做一些补充。
查询结果集ResultMap
resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事 情。 事实上, 编写相似于对复杂语句联合映射这些等同的代码,也许可以跨过上千行的代码。
有朋友会问,之前的示例中我们没有用到结果集,不是也可以正确地将数据表中的数据映射到Java对象的属性中吗?是的。这正是resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。在《Mybatis系列之配置》中我们对Mybatis的设置进行了详细的讲解,不知道朋友们是否还记得有这样一个设置变量autoMappingBehavior,其默认值为PARTIAL,这就意味着Mybatis会自动映射没有定义在resultMap中的字段。前提是Java中的属性名称与数据表中的字段名称完全一样(大小写敏感),又或者是Java中使用了驼峰命名规则,但数据表中使用是下划线连词规则,且我们把Mybatis中的设置变量mapUnderscoreToCamelCase设置为true。
- resultMap元素中,允许有以下直接子元素:
- constructor - 类在实例化时,用来注入结果到构造方法中(本文中暂不讲解)
- id - 作用与result相同,同时可以标识出用这个字段值可以区分其他对象实例。可以理解为数据表中的主键,可以定位数据表中唯一一笔记录
- result - 将数据表中的字段注入到Java对象属性中
- association - 关联,简单的讲,就是“有一个”关系,如“用户”有一个“帐号”
- collection - 集合,顾名思议,就是“有很多”关系,如“客户”有很多“订单”
- discriminator - 使用结果集决定使用哪个个结果映射(暂不涉及)
每个元素的用法及属性我会在下面结合使用进行讲解。下面就正式进入今天的主题。
案例背景
在第三讲《Mybatis之简单示例》的基础上,我们在数据库中额外创建三张数据表,分别表示销售人员、客户,以及销售和客户多对多的对应关系。每个销售、客户都有一个登录帐号。
- CREATE TABLE `customer` (
- `customer_id` int(10) NOT NULL AUTO_INCREMENT,
- `customer_name` varchar(200) NOT NULL,
- `user_id` int(10) DEFAULT NULL,
- `is_valid` tinyint(4) NOT NULL DEFAULT '1',
- `created_time` datetime NOT NULL,
- `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- PRIMARY KEY (`customer_id`),
- KEY `customer_name` (`customer_name`) USING BTREE
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- CREATE TABLE `salesman` (
- `sales_id` int(10) NOT NULL AUTO_INCREMENT,
- `sales_name` varchar(64) NOT NULL,
- `sales_phone` varchar(32) DEFAULT NULL,
- `sales_fax` varchar(32) DEFAULT NULL,
- `sales_email` varchar(100) DEFAULT NULL,
- `user_id` int(10) DEFAULT NULL,
- `report_to` int(10) DEFAULT '0',
- `is_valid` tinyint(4) NOT NULL DEFAULT '1',
- `created_time` datetime DEFAULT NULL,
- `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- PRIMARY KEY (`sales_id`),
- KEY `sales_name` (`sales_name`)
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- CREATE TABLE `customer_sales` (
- `id` int(10) NOT NULL AUTO_INCREMENT,
- `customer_id` int(10) NOT NULL,
- `sales_id` int(10) NOT NULL,
- `created_time` datetime NOT NULL,
- `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- PRIMARY KEY (`id`),
- UNIQUE KEY `customer_id` (`customer_id`,`sales_id`) USING BTREE,
- KEY `sales_id` (`sales_id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
实现销售与登录用户一对一关系
为了巩固上一篇文章《Mybatis系列之接口式编程》中的知识,这里采用Mybatis的接口式编程。无论是对单表进行映射,还是对多表映射,步骤都是相同的,唯一的不同就在映射文件的编写上,所以,这里我们只把重点放在映射文件上,其他部分就一笔提过。
首先,我们需要销售创建一个Java类,其中的userInfo属性对应销售的登录用户信息的。
- package com.emerson.learning.pojo;
-
- import java.sql.Timestamp;
-
- public class Sales {
- /**
- *
- */
- private int salesId;
-
- /**
- *
- */
- private String salesName;
-
- /**
- *
- */
- private String phone;
-
- /**
- *
- */
- private String fax;
-
- /**
- *
- */
- private String email;
-
- /**
- *
- */
- private int isValid;
-
- /**
- *
- */
- private Timestamp createdTime;
-
- /**
- *
- */
- private Timestamp updateTime;
-
- /**
- *
- */
- private User userInfo;
-
- @Override
- public String toString() {
- return "Sales [salesId=" + salesId + ", salesName=" + salesName + ", phone=" + phone + ", fax=" + fax
- + ", email=" + email + ", isValid=" + isValid + ", createdTime=" + createdTime + ", updateTime="
- + updateTime + ", userInfo=" + userInfo.toString() + "]";
- }
-
- public int getSalesId() {
- return salesId;
- }
-
- public void setSalesId(int salesId) {
- this.salesId = salesId;
- }
-
- public String getSalesName() {
- return salesName;
- }
-
- public void setSalesName(String salesName) {
- this.salesName = salesName;
- }
-
- public String getPhone() {
- return phone;
- }
-
- public void setPhone(String phone) {
- this.phone = phone;
- }
-
- public String getFax() {
- return fax;
- }
-
- public void setFax(String fax) {
- this.fax = fax;
- }
-
- public String getEmail() {
- return email;
- }
-
- public void setEmail(String eamil) {
- this.email = eamil;
- }
-
- public int getIsValid() {
- return isValid;
- }
-
- public void setIsValid(int isValid) {
- this.isValid = isValid;
- }
-
- public Timestamp getCreatedTime() {
- return createdTime;
- }
-
- public void setCreatedTime(Timestamp createdTime) {
- this.createdTime = createdTime;
- }
-
- public Timestamp getUpdateTime() {
- return updateTime;
- }
-
- public void setUpdateTime(Timestamp updateTime) {
- this.updateTime = updateTime;
- }
-
- public User getUserInfo() {
- return userInfo;
- }
-
- public void setUserInfo(User userInfo) {
- this.userInfo = userInfo;
- }
- }
第二步,编写Mybatis映射文件,需要注意的是映射文件的名称空间,要与我们编写的接品的全限定名一致(包名+接口名)。