转:Mybatis系列之集合映射

上篇文章我们讲了关联映射,实现了销售与登录用户之间的关联。本文我们接着来讲一讲集合映射,实现销售与客户的多对多关系。

实现销售与客户多对多关系

本文中仍延用《Mybatis系列之关联映射》中的映射接口和测试用例,这里仅对增加和修改的内容进行讲解。

第一步,在动手编写映射文件之前,我们需要对Sales类增加一个List属性,用以保存销售员对应的客户列表。

  1. /**
  2. *
  3. */
  4. private List<Customer> customers;
  5. public Sales() {
  6. super();
  7. this.setCustomers(new ArrayList<Customer>());
  8. }
  9. public List<Customer> getCustomers() {
  10. return customers;
  11. }
  12. protected void setCustomers(List<Customer> customers) {
  13. this.customers = customers;
  14. }

同时增加一个客户类。

  1. package com.emerson.learning.pojo;
  2. import java.sql.Timestamp;
  3. public class Customer {
  4. /**
  5. *
  6. */
  7. private int customerId;
  8. /**
  9. *
  10. */
  11. private String customerName;
  12. /**
  13. *
  14. */
  15. private int isValid;
  16. /**
  17. *
  18. */
  19. private Timestamp createdTime;
  20. /**
  21. *
  22. */
  23. private Timestamp updateTime;
  24. /**
  25. *
  26. */
  27. private User userInfo;
  28. @Override
  29. public String toString() {
  30. return "Customer [customerId=" + customerId + ", customerName=" + customerName + ", isValid=" + isValid
  31. + ", createdTime=" + createdTime + ", updateTime=" + updateTime + ", userInfo=" + userInfo + "]";
  32. }
  33. public int getCustomerId() {
  34. return customerId;
  35. }
  36. public void setCustomerId(int customerId) {
  37. this.customerId = customerId;
  38. }
  39. public String getCustomerName() {
  40. return customerName;
  41. }
  42. public void setCustomerName(String customerName) {
  43. this.customerName = customerName;
  44. }
  45. public int getIsValid() {
  46. return isValid;
  47. }
  48. public void setIsValid(int isValid) {
  49. this.isValid = isValid;
  50. }
  51. public Timestamp getCreatedTime() {
  52. return createdTime;
  53. }
  54. public void setCreatedTime(Timestamp createdTime) {
  55. this.createdTime = createdTime;
  56. }
  57. public Timestamp getUpdateTime() {
  58. return updateTime;
  59. }
  60. public void setUpdateTime(Timestamp updateTime) {
  61. this.updateTime = updateTime;
  62. }
  63. public User getUserInfo() {
  64. return userInfo;
  65. }
  66. public void setUserInfo(User userInfo) {
  67. this.userInfo = userInfo;
  68. }
  69. }

第二步,修改映射文件。我们先使用嵌套查询方式来实现为销售加载客户列表。首先在resultMap中增加客户集合映射的定义。

嵌套查询

  1. <!-- 定义一对多集合信息(每个销售人员对应多个客户) -->
  2. <collection property="customers" javaType="ArrayList" column="sales_id" ofType="Customer" select="getCustomerForSales" />

集合映射的定义与关联映射定义很相似,除了关键字不同外,还多了两个属性JavaType和ofType。

property用于指定在Java实体类是保存集合关系的属性名称

JavaType用于指定在Java实体类中使用什么类型来保存集合数据,多数情况下这个属性可以省略的。

column用于指定数据表中的外键字段名称。

ofType用于指定集合中包含的类型。

select用于指定查询语句。

然后再定义查询客户的查询语句。

  1. <select id="getCustomerForSales" resultType="com.emerson.learning.pojo.Customer">
  2. SELECT c.customer_id, c.customer_name, c.user_id, c.is_valid,
  3. c.created_time, c.update_time
  4. FROM customer c INNER JOIN customer_sales s USING(customer_id)
  5. WHERE s.sales_id = #{id}
  6. </select>

需 要注意的是,无论是关联还是集合,在嵌套查询的时候,查询语句的定义都不需要使用parameterType属性定义传入的参数类型,因为通常作为外键 的,都是简单数据类型,查询语句会自动使用定义在association或是collection元素上column属性作为传入参数的。

运行测试用例,看到如下结果就说明我们的映射文件是正确的了。

  1. Opening JDBC Connection
  2. Created connection 632249781.
  3. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@25af5db5]
  4. ==>  Preparing: SELECT sales_id, sales_name, sales_phone, sales_fax, sales_email, salesman.is_valid, salesman.created_time, salesman.update_time, sys_user.user_id as user_id, user_name, user_password, nick_name, email as user_email, sys_user.is_valid as user_is_valid, sys_user.created_time as user_created_time, sys_user.update_time as user_update_time FROM salesman left outer join sys_user using(user_id) WHERE sales_id=?
  5. ==> Parameters: 2(Integer)
  6. <==    Columns: sales_id, sales_name, sales_phone, sales_fax, sales_email, is_valid, created_time, update_time, user_id, user_name, user_password, nick_name, user_email, user_is_valid, user_created_time, user_update_time
  7. <==        Row: 2, Bing Gu, 021-3418 1999, null, Bing.Gu@emerson.com, 1, 2015-10-01 20:21:26.0, 2015-10-01 21:56:40.0, 25, binggu, 5f4dcc3b5aa765d61d8327deb882cf99, Bing Gu, null, 1, 2015-09-30 22:04:34.0, 2015-09-30 22:04:34.0
  8. ====>  Preparing: SELECT c.customer_id, c.customer_name, c.user_id, c.is_valid, c.created_time, c.update_time FROM customer c INNER JOIN customer_sales s USING(customer_id) WHERE s.sales_id = ?
  9. ====> Parameters: 2(Integer)
  10. <====    Columns: customer_id, customer_name, user_id, is_valid, created_time, update_time
  11. <====        Row: 161, 客户1, null, 1, 2015-10-01 20:24:05.0, 2015-10-01 20:24:05.0
  12. <====        Row: 163, 客户2, null, 1, 2015-10-01 20:24:05.0, 2015-10-01 20:24:05.0
  13. <====        Row: 164, 客户3, null, 1, 2015-10-01 20:24:05.0, 2015-10-01 20:24:05.0
  14. <====      Total: 3
  15. <==      Total: 1

我们看到这里与数据库进行了两查询交互,说明这里仍然存在着“N+1”的问题。下面,我们改用嵌套结果的方式来实现销售与客户的映射关系。

嵌套结果

  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="created_time" />
  9. <result property="updateTime" column="update_time" />
  10. <!-- 定义多对一关联信息(嵌套结果方式) -->
  11. <association property="userInfo" resultMap="userResult" />
  12. <!-- 定义一对多集合信息(每个销售人员对应多个客户) -->
  13. <!-- <collection property="customers" column="sales_id" select="getCustomerForSales" /> -->
  14. <collection property="customers" ofType="com.emerson.learning.pojo.Customer">
  15. <id property="customerId" column="customer_id" />
  16. <result property="customerName" column="customer_name" />
  17. <result property="isValid" column="is_valid" />
  18. <result property="createdTime" column="created_time" />
  19. <result property="updateTime" column="update_time" />
  20. <!-- 映射客户与登录用户的关联关系,请注意columnPrefix属性 -->
  21. <association property="userInfo" resultMap="userResult" columnPrefix="cu_" />
  22. </collection>
  23. </resultMap>

这里将客户的映射关系直接写在了销售的resultMap中。上述代码与关联映射十分相似,只是有一点需要朋友们留心,那就是在对客户数据进行映射的时候,我们使用了association元素的一个新的属性columnPrefix。这个属性是做什么用的呢?从名字上理解,就是给每个栏位之前加上前缀。Bingo!答对了,那么什么情况下会使用到这个属性呢?后面我们会结合着修改后的查询语句来说明这个属性的使用场景。请耐心的往下看。:)

映射结果修改好了,紧接着我们就要修改查询语句了。

  1. <select id="getById" parameterType="int" resultMap="salesResultMap">
  2. SELECT
  3. s.sales_id, s.sales_name, s.sales_phone, s.sales_fax, s.sales_email,
  4. s.is_valid, s.created_time, s.update_time,
  5. su.user_id as user_id, su.user_name, su.user_password, su.nick_name,
  6. su.email as user_email,
  7. su.is_valid as user_is_valid,
  8. su.created_time as user_created_time,
  9. su.update_time as user_update_time,
  10. c.customer_id, c.customer_name, c.is_valid as customer_is_valid,
  11. c.created_time as customer_created_time,
  12. c.update_time as customer_update_time,
  13. cu.user_id as cu_user_id, cu.user_name as cu_user_name, cu.user_password as cu_user_password,
  14. cu.nick_name as cu_nick_name, cu.email as cu_user_email, cu.is_valid as cu_user_is_valid,
  15. cu.created_time as cu_user_created_time, cu.update_time as cu_user_update_time
  16. FROM
  17. salesman s LEFT OUTER JOIN sys_user su ON s.user_id = su.user_id
  18. INNER JOIN customer_sales cs USING(sales_id)
  19. LEFT OUTER JOIN customer c USING(customer_id)
  20. LEFT OUTER JOIN sys_user cu ON c.user_id = cu.user_id
  21. WHERE sales_id=#{id}
  22. </select>


个语句乍看起来有些复杂,其实很容易理解。这里用到了四张数据表,销售、客户、客房销售关系表和登录用户表。具体的字段我就不说了,主要说一下这个登录用
户表。这张数据表在查询语句中出现了两次,为什么呢?因为销售与登录用户有关联关系,同样地,客户也与登录用户表有关联关系,所以我们需要对用户表进行两
次Join操作。

那么问题来了,销售要用户有关联,客户也要与用户有关联,这种映射语句应该如何写呢?难道要对用户表写两次映射?聪明的朋友一定会说,我们可以复用之前写过的用户映射结果集呀!答案是肯定的。我们不妨在这里再次贴出这段代码,一起回忆一下。

  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>

数据表中的字段与Java实体类中的属性的映射关系是一一对应的,Mybatis会根据我们定义的映射关系,将数据表中字段的映射到Java实体类属性上。


是我们的查询语句中对用户表进行了两次Join操作,第一次是销售与用户的Join,第二次是客户与用户的Join。而SQL语句是不允许在同一条查询语
句中出现相同字段名的(虽然我们有时候会这样写,但是数据库会自动帮我们为重名的字段名起个别名的,比如在字段名后添加数字)。如果我们为第二次Join
进来的用户表中的字段使用别名方式,那么就会导致映射的到客户类中的用户信息缺失,因为字段名与我们在映射文件中的定义不一致。如何解决这个问题呢?这时
候该columnPrefix属性出场了。

Mybatis也考虑到这种情况的出现,她允许我们在重复出现的字段名前加上一个统一的字符前缀,这样就可以有效的避免字段重名,又可以复用之前定义的映射结果集。


上述的查询语句中,我们为第二次Join进来的用户表中的字段都加上了“cu_”做为区分重名字段的前缀,同时使用columnPrefix属性告诉
Mybatis在第二次对用户表映射的时候,将字段名是以“cu_”打头的字段值映射到Java实体类属性当中。这样就可以正确的把客户与用户的关联信息
映射到Customer对象当中了。

  1. <association property="userInfo" resultMap="userResult" columnPrefix="cu_" />

上述的表达可能有些臃肿,不知道小伙朋友们明白了没有。理工男的写作水平,你们懂的。

彩蛋奉上(共享不同映射文件中的结果集)


们之前在User.xml文件中定义过用户表的映射结果集,现在在Sales.xml中也需要使用到同样的结果集,是否可以直接跨文件引用呢?答案是肯定
的了,不然对于同一个映射结果集,我们要多处编写,多处维护,这样不仅工作量大,对日后的维护也带来了一定的麻烦。我们只需要在引用处使用结果集的全限定
名就可以了。

  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="created_time" />
  9. <result property="updateTime" column="update_time" />
  10. <!-- 定义多对一关联信息(嵌套查询方式) -->
  11. <!-- <association property="userInfo" column="user_id" javaType="User"
  12. select="selectUser" fetchType="lazy"> </association> -->
  13. <!-- 定义多对一关联信息(嵌套结果方式) -->
  14. <association property="userInfo" resultMap="com.emerson.learning.xml.user.userResult" />
  15. <!-- 定义一对多集合信息(每个销售人员对应多个客户) -->
  16. <!-- <collection property="customers" column="sales_id" select="getCustomerForSales"
  17. /> -->
  18. <collection property="customers" ofType="com.emerson.learning.pojo.Customer">
  19. <id property="customerId" column="customer_id" />
  20. <result property="customerName" column="customer_name" />
  21. <result property="isValid" column="is_valid" />
  22. <result property="createdTime" column="created_time" />
  23. <result property="updateTime" column="update_time" />
  24. <association property="userInfo" resultMap="com.emerson.learning.xml.user.userResult" columnPrefix="cu_" />
  25. </collection>
  26. </resultMap>

附录

Mybatis系列(一)入门

Mybatis系列(二)配置

Mybatis系列(三)简单示例

Mybatis系列(四)映射文件

Mybatis系列(五)动态SQL

Mybatis系列(六)接口式编程

Mybatis系列(七)关联映射

Mybatis系列(九)Spring & Mybatis整合

转:Mybatis系列之集合映射的更多相关文章

  1. 深入浅出Mybatis系列八-mapper映射文件配置之select、resultMap

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之inse ...

  2. 深入浅出Mybatis系列七-mapper映射文件配置之insert、update、delete

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(六)---objectFactory.p ...

  3. mybatis系列-07-输出映射

    7.1     resultType 使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功. 如果查询出来的列名和pojo中的属性名全部不一致,没有创建 ...

  4. mybatis系列-06-输入映射

    通过parameterType指定输入参数的类型,类型可以是简单类型.hashmap.pojo的包装类型 6.1     传递pojo的包装对象 6.1.1     需求 完成用户信息的综合查询,需要 ...

  5. Mybatis系列(四)映射文件

    转自:https://blog.csdn.net/chris_mao/article/details/48811507 Mybatis的真正强大,在于她对SQL的映射,这也是她吸引人的地方.实现相同的 ...

  6. Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件

    封面:洛小汐 作者:潘潘 若不是生活所迫,谁愿意背负一身才华. 前言 上节我们介绍了 < Mybatis系列全解(四):全网最全!Mybatis配置文件 XML 全貌详解 >,内容很详细( ...

  7. 深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap

    上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之insert.update.delete>介绍了insert.update.delete的用法,本篇将介绍select ...

  8. 深入浅出Mybatis系列(七)---mapper映射文件配置之insert、update、delete

    上篇文章<深入浅出Mybatis系列(六)---objectFactory.plugins.mappers简介与配置>简单地给mybatis的配置画上了一个句号.那么从本篇文章开始,将会介 ...

  9. 深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap good

    上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之insert.update.delete>介绍了insert.update.delete的用法,本篇将介绍select ...

随机推荐

  1. 设计模式——模版方法模式详解(论沉迷LOL对学生的危害)

    .  实例介绍 在本例中,我们使用一个常见的场景,我们每个人都上了很多年学,中学大学硕士,有的人天生就是个天才,中学毕业就会微积分,因此得了诺贝尔数学奖:也有的人在大学里学了很多东西,过得很充实很满意 ...

  2. fsync体会

    看这个链接:http://www.postgresql.org/docs/9.1/static/runtime-config-wal.html 是这样说的: fsync (boolean) If th ...

  3. 深入浅出 Webpack

    深入浅出 Webpack 评价 Webpack 凭借强大的功能与良好的使用体验,已经成为目前最流行,社区最活跃的打包工具,是现代 Web 开发必须掌握的技能之一.作者结合自身的实战经验,介绍了 Web ...

  4. iOS下原生与JS交互(总结)

    iOS开发免不了要与UIWebView打交道,然后就要涉及到JS与原生OC交互,今天总结一下JS与原生OC交互的两种方式. JS调用原生OC篇(我自己用的方式二,简单方便) 方式一 第一种方式是用JS ...

  5. Java Web前后端分离的思考与实践

    第一节 Java Web开发方式的变化 Web开发虽然是我们常说的B/S模式,其实本质上也是一种特殊的C/S模式,只不过C和S的选择余地相对要窄了不少,而且更标准化.不论是采用什么浏览器和后端框架,W ...

  6. Tomcat突然用开发工具启动不起来,只报了个红色的警告,没有其他任何异常

    碰到这个问题是,是因为我的catalina.bat文件做了配置修改,导致与工具这边的启动设置起了冲突 下面这个是我在Catalina.bat中新增的配置,删掉这个就可以了 set JAVA_OPTS= ...

  7. [GraphSAGE] docker安装与程序运行

    安装Docker与程序运行 1. requirements.txt Problem: Downloading https://files.pythonhosted.org/packages/69/cb ...

  8. python基础训练营03——字典、集合、判断、循环

    一.字典dict: 相比列表list而言,列表list像一本书,如果要查书中的某一个内容,需要把书从前往后翻一遍,直到找到想要获取的东西:而字典dict,就像现实中的字典一样,通过查找特定的字或者词( ...

  9. 软件工程项目组Z.XML会议记录 2013/11/20

    软件工程项目组Z.XML会议记录 [例会时间]2013年11月20日星期三21:00-22:00 [例会形式]小组讨论 [例会地点]学生公寓3号楼会客厅 [例会主持]李孟 [会议记录]李孟 会议整体流 ...

  10. mysql初始(6)

    随着mysql的运用不断加深,一些更复杂点的用法又需要总结起来. 1.将一个表中的数据插入到另一个表中: a.两张表字段相同,并且数据全部插入,命令如下:  INSERT INTO 目标表 SELEC ...