Mybatis多表关联映射

来源:互联网 发布:淘宝怎么刷流量安全 编辑:程序博客网 时间:2024/06/06 01:52

Mybatis多表关联映射

查询结果集ResultMap

resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事 情。 事实上, 编写相似于对复杂语句联合映射这些等同的代码,也许可以跨过上千行的代码。

有朋友会问,之前的示例中我们没有用到结果集,不是也可以正确地将数据表中的数据映射到Java对象的属性中吗?是的。这正是resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。我们对Mybatis的设置进行了详细的讲解,不知道朋友们是否还记得有这样一个设置变量autoMappingBehavior,其默认值为PARTIAL,这就意味着Mybatis会自动映射没有定义在resultMap中的字段。前提是Java中的属性名称与数据表中的字段名称完全一样(大小写敏感),又或者是Java中使用了驼峰命名规则,但数据表中使用是下划线连词规则,且我们把Mybatis中的设置变量mapUnderscoreToCamelCase设置为true。

  • resultMap元素中,允许有以下直接子元素:
  • constructor - 类在实例化时,用来注入结果到构造方法中(本文中暂不讲解)
  • id - 作用与result相同,同时可以标识出用这个字段值可以区分其他对象实例。可以理解为数据表中的主键,可以定位数据表中唯一一笔记录
  • result - 将数据表中的字段注入到Java对象属性中
  • association - 关联,简单的讲,就是“有一个”关系,如“用户”有一个“帐号”
  • collection - 集合,顾名思议,就是“有很多”关系,如“客户”有很多“订单”
  • discriminator - 使用结果集决定使用哪个个结果映射(暂不涉及)

 

每个元素的用法及属性我会在下面结合使用进行讲解。下面就正式进入今天的主题。

 

案例背景

在第三讲《Mybatis之简单示例》的基础上,我们在数据库中额外创建三张数据表,分别表示销售人员、客户,以及销售和客户多对多的对应关系。每个销售、客户都有一个登录帐号。

 

[sql] view plain copy
  1. CREATE TABLE `customer` (  
  2.   `customer_id` int(10) NOT NULL AUTO_INCREMENT,  
  3.   `customer_name` varchar(200) NOT NULL,  
  4.   `user_id` int(10) DEFAULT NULL,  
  5.   `is_valid` tinyint(4) NOT NULL DEFAULT '1',  
  6.   `created_time` datetime NOT NULL,  
  7.   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
  8.   PRIMARY KEY (`customer_id`),  
  9.   KEY `customer_name` (`customer_name`) USING BTREE  
  10. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;  
  11.   
  12. CREATE TABLE `salesman` (  
  13.   `sales_id` int(10) NOT NULL AUTO_INCREMENT,  
  14.   `sales_name` varchar(64) NOT NULL,  
  15.   `sales_phone` varchar(32) DEFAULT NULL,  
  16.   `sales_fax` varchar(32) DEFAULT NULL,  
  17.   `sales_email` varchar(100) DEFAULT NULL,  
  18.   `user_id` int(10) DEFAULT NULL,  
  19.   `report_to` int(10) DEFAULT '0',  
  20.   `is_valid` tinyint(4) NOT NULL DEFAULT '1',  
  21.   `created_time` datetime DEFAULT NULL,  
  22.   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
  23.   PRIMARY KEY (`sales_id`),  
  24.   KEY `sales_name` (`sales_name`)  
  25. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;  
  26.   
  27. CREATE TABLE `customer_sales` (  
  28.   `id` int(10) NOT NULL AUTO_INCREMENT,  
  29.   `customer_id` int(10) NOT NULL,  
  30.   `sales_id` int(10) NOT NULL,  
  31.   `created_time` datetime NOT NULL,  
  32.   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
  33.   PRIMARY KEY (`id`),  
  34.   UNIQUE KEY `customer_id` (`customer_id`,`sales_id`) USING BTREE,  
  35.   KEY `sales_id` (`sales_id`)  
  36. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;  
 

 

实现销售与登录用户一对一关系

为了巩固上一篇文章《Mybatis系列之接口式编程》中的知识,这里采用Mybatis的接口式编程。无论是对单表进行映射,还是对多表映射,步骤都是相同的,唯一的不同就在映射文件的编写上,所以,这里我们只把重点放在映射文件上,其他部分就一笔提过。

 

首先,我们需要销售创建一个Java类,其中的userInfo属性对应销售的登录用户信息的。

 

[java] view plain copy
  1. package com.emerson.learning.pojo;  
  2.   
  3. import java.sql.Timestamp;  
  4.   
  5. public class Sales {  
  6.     /** 
  7.      *  
  8.      */  
  9.     private int salesId;  
  10.       
  11.     /** 
  12.      *  
  13.      */  
  14.     private String salesName;  
  15.       
  16.     /** 
  17.      *  
  18.      */  
  19.     private String phone;  
  20.       
  21.     /** 
  22.      *  
  23.      */  
  24.     private String fax;  
  25.       
  26.     /** 
  27.      *  
  28.      */  
  29.     private String email;  
  30.       
  31.     /** 
  32.      *  
  33.      */  
  34.     private int isValid;  
  35.   
  36.     /** 
  37.      *  
  38.      */  
  39.     private Timestamp createdTime;  
  40.   
  41.     /** 
  42.      *  
  43.      */  
  44.     private Timestamp updateTime;  
  45.       
  46.     /** 
  47.      *  
  48.      */  
  49.     private User userInfo;  
  50.   
  51.     @Override  
  52.     public String toString() {  
  53.         return "Sales [salesId=" + salesId + ", salesName=" + salesName + ", phone=" + phone + ", fax=" + fax  
  54.                 + ", email=" + email + ", isValid=" + isValid + ", createdTime=" + createdTime + ", updateTime="  
  55.                 + updateTime + ", userInfo=" + userInfo.toString() + "]";  
  56.     }  
  57.   
  58.     public int getSalesId() {  
  59.         return salesId;  
  60.     }  
  61.   
  62.     public void setSalesId(int salesId) {  
  63.         this.salesId = salesId;  
  64.     }  
  65.   
  66.     public String getSalesName() {  
  67.         return salesName;  
  68.     }  
  69.   
  70.     public void setSalesName(String salesName) {  
  71.         this.salesName = salesName;  
  72.     }  
  73.   
  74.     public String getPhone() {  
  75.         return phone;  
  76.     }  
  77.   
  78.     public void setPhone(String phone) {  
  79.         this.phone = phone;  
  80.     }  
  81.   
  82.     public String getFax() {  
  83.         return fax;  
  84.     }  
  85.   
  86.     public void setFax(String fax) {  
  87.         this.fax = fax;  
  88.     }  
  89.   
  90.     public String getEmail() {  
  91.         return email;  
  92.     }  
  93.   
  94.     public void setEmail(String eamil) {  
  95.         this.email = eamil;  
  96.     }  
  97.   
  98.     public int getIsValid() {  
  99.         return isValid;  
  100.     }  
  101.   
  102.     public void setIsValid(int isValid) {  
  103.         this.isValid = isValid;  
  104.     }  
  105.   
  106.     public Timestamp getCreatedTime() {  
  107.         return createdTime;  
  108.     }  
  109.   
  110.     public void setCreatedTime(Timestamp createdTime) {  
  111.         this.createdTime = createdTime;  
  112.     }  
  113.   
  114.     public Timestamp getUpdateTime() {  
  115.         return updateTime;  
  116.     }  
  117.   
  118.     public void setUpdateTime(Timestamp updateTime) {  
  119.         this.updateTime = updateTime;  
  120.     }  
  121.   
  122.     public User getUserInfo() {  
  123.         return userInfo;  
  124.     }  
  125.   
  126.     public void setUserInfo(User userInfo) {  
  127.         this.userInfo = userInfo;  
  128.     }  
  129. }  
第二步,编写Mybatis映射文件,需要注意的是映射文件的名称空间,要与我们编写的接品的全限定名一致(包名+接口名)。
[xml] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"   
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  5.   
  6. <mapper namespace="com.emerson.learning.dao.ISalesDao">  
  7.     <resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">  
  8.         <id property="salesId" column="sales_id" />  
  9.         <result property="salesName" column="sales_name" />  
  10.         <result property="phone" column="sales_phone" />  
  11.         <result property="fax" column="sales_fax" />  
  12.         <result property="email" column="sales_email" />  
  13.   
  14.         <!-- 定义多对一关联信息(每个销售人员对应一个登录帐号) -->  
  15.         <association property="userInfo" column="user_id" javaType="User" select="selectUser">  
  16.             <id property="userId" column="userId" />  
  17.             <result property="userName" column="user_name" />  
  18.             <result property="userPassword" column="user_password" />  
  19.             <result property="nickName" column="nick_name" />  
  20.             <result property="email" column="email" />  
  21.             <result property="isValid" column="is_valid" />  
  22.             <result property="createdTime" column="created_time" />  
  23.             <result property="updateTime" column="update_time" />  
  24.         </association>  
  25.     </resultMap>  
  26.       
  27.     <select id="selectUser" resultType="User">  
  28.         SELECT user_id, user_name, user_password, nick_name, email, is_valid, created_time  
  29.         FROM sys_user WHERE user_id = #{id}  
  30.     </select>  
  31.       
  32.     <select id="getById" parameterType="int" resultMap="salesResultMap" >  
  33.         SELECT sales_id, sales_name, sales_phone, sales_fax, sales_email, user_id, is_valid, created_time, update_time  
  34.         FROM salesman WHERE sales_id=#{id}  
  35.     </select>  
  36. </mapper>  

第三步,将映射文件注册到Mybatis中。这一步很简单,简单到很多朋友会忘掉。却也是最重要的一环。

 

[xml] view plain copy
  1. <mappers>  
  2.     <mapper resource="com/emerson/learning/mapping/User.xml" />  
  3.     <mapper resource="com/emerson/learning/mapping/Sales.xml" />  
  4. </mappers>  

 

第四步,编写接口。

 

[java] view plain copy
  1. package com.emerson.learning.dao;  
  2.   
  3. import com.emerson.learning.pojo.Sales;  
  4.   
  5. public interface ISalesDao {  
  6.       
  7.     public Sales getById(int id);  
  8.   
  9. }  

 

第四步,编写测试用例。

 

[java] view plain copy
  1. package com.emerson.learning.dao;  
  2.   
  3. import static org.junit.Assert.*;  
  4.   
  5. import java.io.IOException;  
  6. import java.io.Reader;  
  7.   
  8. import org.apache.ibatis.io.Resources;  
  9. import org.apache.ibatis.session.SqlSession;  
  10. import org.apache.ibatis.session.SqlSessionFactory;  
  11. import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  12. import org.junit.After;  
  13. import org.junit.Before;  
  14. import org.junit.Test;  
  15.   
  16. import com.emerson.learning.dao.ISalesDao;  
  17. import com.emerson.learning.pojo.Sales;  
  18.   
  19. public class SalesDaoTest {  
  20.   
  21.     private Reader reader;  
  22.     private SqlSessionFactory sqlSessionFactory;  
  23.   
  24.     @Before  
  25.     public void setUp() throws Exception {  
  26.         try {  
  27.             reader = Resources.getResourceAsReader("mybatis.xml");  
  28.         } catch (IOException e) {  
  29.             e.printStackTrace();  
  30.         }  
  31.         sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);  
  32.     }  
  33.   
  34.     @After  
  35.     public void tearDown() throws Exception {  
  36.     }  
  37.   
  38.     @Test  
  39.     public void getById() {  
  40.         SqlSession session = sqlSessionFactory.openSession();  
  41.         try {  
  42.             ISalesDao sd = session.getMapper(ISalesDao.class);  
  43.             Sales sales = sd.getById(2);  
  44.             assertNotNull(sales);  
  45.             System.out.println(sales);  
  46.         } finally {  
  47.             session.close();  
  48.         }  
  49.     }  
  50.   
  51. }  
 下面我们就针对第二步,映射文件中的resultMap编写进行详细讲解。
[xml] view plain copy
  1. <resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">  
  2.     <id property="salesId" column="sales_id" />  
  3.     <result property="salesName" column="sales_name" />  
  4.     <result property="phone" column="sales_phone" />  
  5.     <result property="fax" column="sales_fax" />  
  6.     <result property="email" column="sales_email" />  
  7.     <result property="isValid" column="is_valid" />  
  8.     <result property="createdTime" column="createdTime" />  
  9.     <result property="updateTime" column="update_time" />  
  10.   
  11.     <!-- 定义多对一关联信息(每个销售人员对应一个登录帐号) -->  
  12.     <association property="userInfo" column="user_id" javaType="User" select="selectUser">  
  13.         <id property="userId" column="userId" />  
  14.         <result property="userName" column="user_name" />  
  15.         <result property="userPassword" column="user_password" />  
  16.         <result property="nickName" column="nick_name" />  
  17.         <result property="email" column="email" />  
  18.         <result property="isValid" column="is_valid" />  
  19.         <result property="createdTime" column="created_time" />  
  20.         <result property="updateTime" column="update_time" />  
  21.     </association>  
  22. </resultMap>  
和其他元素一样,我们都需要为其取一个唯一的id,并指定其在Java中对应的类型,由于我没有在Mybatis配置文件中为Sales类指定别名,所以这里使用的是全限定名。
[xml] view plain copy
  1. <resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">  
 使用id和result元素指定数据表中字段与Java类中属性的映射关系,除了我phone、fax和email三行映射代码,其余的全部可以省去不写。为什么?这个就像前面示例中使用到的User类一样,Mybatis会自动帮助我们完成映射工作,不需要我们额外编写代码。那么为什么phone、fax和email这三个字段的映射关系不能省略呢?这是因为我在编写Sales类的时候埋下了伏笔,我故意不按照按驼峰规则对这三个属性进行命名,同时也不与数据表中的字段名相同,为了确保可以正确的将字段映射到属性上,我们必须手工编写映射在代码,明确地告诉Mybatis我们的映射规则。
[xml] view plain copy
  1. <resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">  
  2.     <result property="phone" column="sales_phone" />  
  3.     <result property="fax" column="sales_fax" />  
  4.     <result property="email" column="sales_email" />  
  5. </resultMap>  
 下面重点来了,association元素来帮助我们完成销售与登录用户对应关系的映射。她实现了“有一个”的关系映射,我们需要做的只是告诉Mybatis,这个关系是通过哪一个字段来建立关联的,被关联的对象类型是什么,以及将关联对象映射到哪个属性上面。如果被关联对象的数据结构比较简单,就如本文中的登录用户表这样,那么可以有更简单的写法。
[xml] view plain copy
  1. <association property="userInfo" column="user_id" javaType="User" select="selectUser" />  

我们还需要告诉Mybatis,加载关联的方式。MyBatis 在这方面会有两种不同的方式:

  • 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。
  • 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。

嵌套查询

我们在这里先使用嵌套查询来实现。使用属性select指定了关联数据的查询语句。
[xml] view plain copy
  1. <select id="selectUser" resultType="User">  
  2.     SELECT user_id, user_name, user_password, nick_name, email, is_valid, created_time  
  3.     FROM sys_user WHERE user_id = #{id}  
  4. </select>  
 当对Sales进行映射的时候,Mybatis会使用这个名为selectUser的查询语句去获取相关联的数据信息。这种方法使用起来很简单。但是简单,不代表最好。对于大型数据集合和列表这种方式将会有性能上的问题,就是我们熟知的 “N+1 查询问题”。概括地讲,N+1 查询问题可以是这样引起的:
  • 你执行了一个单独的 SQL 语句来获取结果列表(就是“+1”)。
  • 对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)。

这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。

MyBatis 能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消耗。然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加载,这样的行为可能是很糟糕的。

嵌套结果

下面我们就来讲一下另一种实式方式:嵌套结果。使用这种方式,就可以有效地避免了N+1问题。

[xml] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"   
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  5.   
  6. <mapper namespace="com.emerson.learning.dao.ISalesDao">  
  7.     <resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">  
  8.         <id property="salesId" column="sales_id" />  
  9.         <result property="salesName" column="sales_name" />  
  10.         <result property="phone" column="sales_phone" />  
  11.         <result property="fax" column="sales_fax" />  
  12.         <result property="email" column="sales_email" />  
  13.         <result property="isValid" column="is_valid" />  
  14.         <result property="createdTime" column="created_time" />  
  15.         <result property="updateTime" column="update_time" jdbcType="TIMESTAMP" />  
  16.   
  17.         <!-- 定义多对一关联信息(嵌套结果方式) -->  
  18.         <association property="userInfo" resultMap="userResult" />  
  19.     </resultMap>  
  20.   
  21.     <resultMap id="userResult" type="User">  
  22.         <id property="userId" column="user_id" />  
  23.         <result property="userName" column="user_name" />  
  24.         <result property="userPassword" column="user_password" />  
  25.         <result property="nickName" column="nick_name" />  
  26.         <result property="email" column="user_email" />  
  27.         <result property="isValid" column="user_is_valid" />  
  28.         <result property="createdTime" column="user_created_time" />  
  29.         <result property="updateTime" column="user_update_time" />  
  30.     </resultMap>  
  31.   
  32.     <select id="getById" parameterType="int" resultMap="salesResultMap">  
  33.         SELECT  
  34.         sales_id, sales_name, sales_phone, sales_fax, sales_email,  
  35.         salesman.is_valid, salesman.created_time, salesman.update_time,  
  36.         sys_user.user_id as user_id, user_name, user_password, nick_name,  
  37.         email as user_email,  
  38.         sys_user.is_valid as user_is_valid, sys_user.created_time as  
  39.         user_created_time,  
  40.         sys_user.update_time as user_update_time  
  41.         FROM  
  42.         salesman left outer join sys_user using(user_id)  
  43.         WHERE sales_id=#{id}  
  44.     </select>  
  45. </mapper>  

和嵌套查询相比,使用嵌套结果方式,在映射文件上主要有以下三处修改:

一、修改association元素,无需指定column,另外将resultType改为使用resultMap。为什么?这是因为后面我们会把select语句改为多表关联查询,这样就会有些字段名是冲突的,我们不得不使用别名。这一点对于Mybatis而言,就相当于字段名发生了变化,那么就需要我们手工来维护映射关系。另外,我们也无需指定javaType属性了,因为在resultMap中,已经指定了对应的Java实体类,这里就可以省略了。

[xml] view plain copy
  1. <association property="userInfo" resultMap="userResult" />  

二、为关联结果集编写映射关系,大家可以看到,好多字段名称已经发生了变化,如is_valid这个字段由于salesman和sys_user表中都存在这个字段,所以我们不得不为其起了一个别名user_is_valid。

[xml] view plain copy
  1. <resultMap id="userResult" type="User">  
  2.         <id property="userId" column="user_id" />  
  3.         <result property="userName" column="user_name" />  
  4.         <result property="userPassword" column="user_password" />  
  5.         <result property="nickName" column="nick_name" />  
  6.         <result property="email" column="user_email" />  
  7.         <result property="isValid" column="user_is_valid" />  
  8.         <result property="createdTime" column="user_created_time" />  
  9.         <result property="updateTime" column="user_update_time" />  
  10.     </resultMap>  

三、修改查询语句,由单表查询改表多表关联查询

[sql] view plain copy
  1. <select id="getById" parameterType="int" resultMap="salesResultMap">  
  2.     SELECT sales_id, sales_name, sales_phone, sales_fax, sales_email,  
  3.            salesman.is_valid, salesman.created_time, salesman.update_time,  
  4.            sys_user.user_id as user_id, user_name, user_password, nick_name,  
  5.            email as user_email,  
  6.            sys_user.is_valid as user_is_valid, sys_user.created_time as  
  7.            user_created_time,  
  8.            sys_user.update_time as user_update_time  
  9.     FROM salesman left outer join sys_user using(user_id)  
  10.     WHERE sales_id=#{id}  
  11. </select>  

至此,关联映射已讲解完了。还有集合映射没有讲,哇咔咔,内空实在是太多了〜〜〜〜今晚通宵也未必能写得完了。暂时先写到这儿吧,下回再继续讲解如何实现多对多的集合映射。


1 0
原创粉丝点击