`
禹爸爸
  • 浏览: 80204 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

Mybatis系列(八)集合映射

阅读更多

Mybatis系列之集合映射

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

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

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

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

	
	/**
	 * 
	 */
    private List<Customer> customers;

	public Sales() {
		super();
		this.setCustomers(new ArrayList<Customer>());
	}

	public List<Customer> getCustomers() {
		return customers;
	}

	protected void setCustomers(List<Customer> customers) {
		this.customers = customers;
	}
同时增加一个客户类。
package com.emerson.learning.pojo;

import java.sql.Timestamp;

public class Customer {

	/**
	 * 
	 */
	private int customerId;

	/**
	 * 
	 */
	private String customerName;

	/**
	 * 
	 */
	private int isValid;

	/**
	 * 
	 */
	private Timestamp createdTime;

	/**
	 * 
	 */
	private Timestamp updateTime;

	/**
	 * 
	 */
	private User userInfo;

	@Override
	public String toString() {
		return "Customer [customerId=" + customerId + ", customerName=" + customerName + ", isValid=" + isValid
				+ ", createdTime=" + createdTime + ", updateTime=" + updateTime + ", userInfo=" + userInfo + "]";
	}

	public int getCustomerId() {
		return customerId;
	}

	public void setCustomerId(int customerId) {
		this.customerId = customerId;
	}

	public String getCustomerName() {
		return customerName;
	}

	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}

	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;
	}
}

 

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

嵌套查询 

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

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

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

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

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

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

select用于指定查询语句。

 

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

<select id="getCustomerForSales" resultType="com.emerson.learning.pojo.Customer">
    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 = #{id}
</select>

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

 

运行测试用例,看到如下结果就说明我们的映射文件是正确的了。
Opening JDBC Connection
Created connection 632249781.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@25af5db5]
==>  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=? 
==> Parameters: 2(Integer)
<==    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
<==        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
====>  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 = ? 
====> Parameters: 2(Integer)
<====    Columns: customer_id, customer_name, user_id, is_valid, created_time, update_time
<====        Row: 161, 客户1, null, 1, 2015-10-01 20:24:05.0, 2015-10-01 20:24:05.0
<====        Row: 163, 客户2, null, 1, 2015-10-01 20:24:05.0, 2015-10-01 20:24:05.0
<====        Row: 164, 客户3, null, 1, 2015-10-01 20:24:05.0, 2015-10-01 20:24:05.0
<====      Total: 3
<==      Total: 1

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

嵌套结果

<resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">
    <id property="salesId" column="sales_id" />
    <result property="salesName" column="sales_name" />
    <result property="phone" column="sales_phone" />
    <result property="fax" column="sales_fax" />
    <result property="email" column="sales_email" />
    <result property="isValid" column="is_valid" />
    <result property="createdTime" column="created_time" />
    <result property="updateTime" column="update_time" />

    <!-- 定义多对一关联信息(嵌套结果方式) -->
    <association property="userInfo" resultMap="userResult" />

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

    <collection property="customers" ofType="com.emerson.learning.pojo.Customer">
        <id property="customerId" column="customer_id" />
        <result property="customerName" column="customer_name" />
        <result property="isValid" column="is_valid" />
        <result property="createdTime" column="created_time" />
        <result property="updateTime" column="update_time" />
        <!-- 映射客户与登录用户的关联关系,请注意columnPrefix属性 -->
        <association property="userInfo" resultMap="userResult" columnPrefix="cu_" />
    </collection>
</resultMap>

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

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

<select id="getById" parameterType="int" resultMap="salesResultMap">
    SELECT
        s.sales_id, s.sales_name, s.sales_phone, s.sales_fax, s.sales_email,
        s.is_valid, s.created_time, s.update_time,
        su.user_id as user_id, su.user_name, su.user_password, su.nick_name,
        su.email as user_email,
        su.is_valid as user_is_valid,
        su.created_time as user_created_time,
        su.update_time as user_update_time,
        c.customer_id, c.customer_name, c.is_valid as customer_is_valid,
        c.created_time as customer_created_time,
        c.update_time as customer_update_time,
        cu.user_id as cu_user_id, cu.user_name as cu_user_name, cu.user_password as cu_user_password, 
        cu.nick_name as cu_nick_name, cu.email as cu_user_email, cu.is_valid as cu_user_is_valid,
        cu.created_time as cu_user_created_time, cu.update_time as cu_user_update_time
    FROM
        salesman s LEFT OUTER JOIN sys_user su ON s.user_id = su.user_id
        INNER JOIN customer_sales cs USING(sales_id)
        LEFT OUTER JOIN customer c USING(customer_id)
        LEFT OUTER JOIN sys_user cu ON c.user_id = cu.user_id
    WHERE sales_id=#{id}
</select>

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

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

<resultMap id="userResult" type="User">
    <id property="userId" column="user_id" />
    <result property="userName" column="user_name" />
    <result property="userPassword" column="user_password" />
    <result property="nickName" column="nick_name" />
    <result property="email" column="user_email" />
    <result property="isValid" column="user_is_valid" />
    <result property="createdTime" column="user_created_time" />
    <result property="updateTime" column="user_update_time" />
</resultMap>

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

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

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

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

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

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

 

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

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

	<resultMap id="salesResultMap" type="com.emerson.learning.pojo.Sales">
		<id property="salesId" column="sales_id" />
		<result property="salesName" column="sales_name" />
		<result property="phone" column="sales_phone" />
		<result property="fax" column="sales_fax" />
		<result property="email" column="sales_email" />
		<result property="isValid" column="is_valid" />
		<result property="createdTime" column="created_time" />
		<result property="updateTime" column="update_time" />

		<!-- 定义多对一关联信息(嵌套查询方式) -->
		<!-- <association property="userInfo" column="user_id" javaType="User" 
			select="selectUser" fetchType="lazy"> </association> -->

		<!-- 定义多对一关联信息(嵌套结果方式) -->
		<association property="userInfo" resultMap="com.emerson.learning.xml.user.userResult" />

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

		<collection property="customers" ofType="com.emerson.learning.pojo.Customer">
			<id property="customerId" column="customer_id" />
			<result property="customerName" column="customer_name" />
			<result property="isValid" column="is_valid" />
			<result property="createdTime" column="created_time" />
			<result property="updateTime" column="update_time" />
			<association property="userInfo" resultMap="com.emerson.learning.xml.user.userResult" columnPrefix="cu_" />
		</collection>
	</resultMap>

 

分享到:
评论

相关推荐

    基于java的企业级应用开发:MyBatis的关联映射.ppt

    * * * * * MyBatis的关联映射 为什么学习MyBatis关联关系? 9.1 关联关系概述 实际的开发中,对数据库的操作常常会涉及到多张表,这在面向对象中就涉及到了对象与对象之间的关联关系。针对多表之间的操作,MyBatis...

    MyBatis执行SQL并将结果映射成Java对象.docx

    5. Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中 该对象包括SQL语句 输入参数映射信息 输出结果映射信息 其中输入参数和输出结果的映射类型包括HashMap集合对象 POJO对象类型

    MyBatis基本使用总结

    MyBatis基本使用总结 Mybatis 的核心配置文件于实体类的映射文件,mapper 代理动态代理的调用方法。

    mybatis批量添加实现1

    一、引言Mybatis可以直接传入一个集合,通过标签将集合中的数据遍历出来实现批量新增不同的数据库批量新增有区别,如下实现两种方式的批量新增二、实现2.1映射文

    mybatis generater 自动生成实体类和映射文件jar包集合

    里面包含log4j-1.2.17.jar、mybatis-3.3.0.jar、mybatis-generator-core-1.3.7.jar、mysql-connector-java-5.1.10.jar四个jar包以及一个命令文档,方便大家一次下载全部齐全,不需要到处去找jar包

    springmybatis

    mybatis实战教程mybatis in action之八mybatis 动态sql语句 mybatis实战教程mybatis in action之九mybatis 代码生成工具的使用 mybatis SqlSessionDaoSupport的使用附代码下载 转自:...

    agile-mybatis : Mybatis扩展

    结果POJO映射 查询返回结果可以直接声明为POJO或POJO集合,其映射过程是依赖于cloud.agileframework:common-util对象深度转换器实现,所以支持 识别驼峰与下划线等风格属性的互转。只需声明,无需额外调用,以最低的...

    MyBatis-3-User-Guide用户手册(中文英文)

    什么是 MyBatis?\ 5 入门\ 5 从 XML 中构建 SqlSessionFactory \ 5 不使用 XML 构建 SqlSessionFactory\ 6 从 SqlSessionFactory 中获取 SqlSession\ 6 探究已映射的 SQL语句 \ 7 命名空间的一点注释 \ 8 ...

    MyBatis中文指南,MyBatis中文帮助文档

    什么是MyBatis?............................................................................................................. 5 入门........................................................................

    mybatis知识点.xmind

    mybiats框架知识点思维导图整理。包含了mybiats的几个关键类,执行浅析,整体设计,源码的几个主要部件,xml映射配置,元素集合,缓存及鉴别器,动态sql等内容

    mybatis中批量插入的两种方式(高效插入)

    MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。这篇文章主要介绍了mybatis中批量插入的两种方式(高效插入)的相关资料,非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧

    mybatis1

    Mybatis基地 此工程为mybatis的基本使用 Mybatis-aop 此工程是基于aop实现的读写分离 ...解答:不能,项目在启动的时候,会加载所有的Mapper,提示存在相同的全限定名ID:映射的语句集合已经包含全限定名的值。方法,

    mybatis-plus-generator:从表生成映射器,实体,服务,serviceImpl

    mybatis-plus-generator 当表数量太多时,需要建立以上这些就会很耗费时间。因此,Mybatis-plus-generator提供快速建立功能。从表生成控制器,映射器,实体,服务,serviceImpl

    mybatis3中文文档

    什么是MyBatis?............................................................................................................. 5 入门........................................................................

    Mybatis中的resultType和resultMap查询操作实例详解

    resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,resultMap解决复杂查询是的映射问题。这篇文章主要介绍了Mybatis中的resultType和resultMap查询操作实例详解,需要的朋友可以参考下

    springmvc入门程序(下载后你不会后悔的)

    springmvc框架原理(掌握...springmvc和mybatis整合(掌握) springmvc注解开发:(掌握) 常用的注解学习 参数绑定(简单类型、pojo、集合类型(明天讲)) 自定义参数绑定(掌握) springmvc和struts2区别

    springmvc第一天课堂笔记

    springmvc和mybatis整合(掌握) springmvc注解开发:(掌握) 常用的注解学习 参数绑定(简单类型、pojo、集合类型(明天讲)) 自定义参数绑定(掌握) springmvc和struts2区别 springmvc和struts2的...

    springmvc基础.docx

    springmvc基础 包含代码+知识点+详细解释 ...4. springmvc和mybatis整合 5. springmvc注解开发 常用的注解学习 参数绑定(简单类型、pojo、集合类型) 自定义参数绑定 6. springmvc和struts2区别

Global site tag (gtag.js) - Google Analytics