高级映射(一):一对一、一对多,多对多查询总结

多表之间的数据交互

其实一对一和一对多映射,在前面的配置中已经接触到,我没在日志里直接说明,是因为想要在之后写一篇总结日志(就是本篇),总结这些高级映射的配置。例如一对一查询在关联的嵌套结果集查询中就涉及到,一对多查询则在这个基础上再加上一个或多个嵌套结果集,它们可以是一个实体类,或者是一个集合。多对多查询稍微有点复杂,举个例子来说,一个商城管理系统中,一名顾客在一个购物清单中可以有多件商品,而一件商品可以被多名顾客选购,它们之间的关系是多对对。假设现在有这么一个需求,查询数据表库中,所有的顾客信息,包括它们的购物清单,以及清单里面的商品信息,怎么做?下面会说。

无论是一对一、一对多还是多对多查询,都涉及到不同表之间的数据交互,因为它们之间需要主外键关系来关联,例如顾客表和购物清单表都有顾客id属性,根据这个属性来查询不同顾客对应的购物清单,有了主外键关系后,就可以进行高级映射了。

 

事例模型

为了展示三种映射示例,首先建立一个商场管理系统模型,里面有三张数据表,分别是顾客表user,购物车(商品清单)表shoppingcart和商品表products。顾客和商品清单之间是一对多的关系,因为一名顾客可以多次进商场购物,每一次购物生成一个购物清单(购物车),而每一张购物清单只属于一名顾客,所以顾客表和购物车表之间是一对多的关系。   还有商品表product,因为一张购物清单中可以有多件商品,所以商品表和购物车表为一对多的关系。因为一名顾客可以购买多件商品,一件商品也可以属于多名顾客,所以顾客和商品又构成多对多关系。我们在测试时,可以建立基本的三张表,:顾客表、购物车表和商品表(当然最好多加几张表,例如商品评价表),它们之间的查询可以组合出一对一、一对多以及多对多的关系,理清各张表的关系后,就可以进行测试了。

 

一对一嵌套结果集查询

从上面的事例模型中可以看到,含一对一关系的两张表有,购物车表和用户表,要注意其中的顺序不要弄乱,购物车和顾客是一对一关系,但反过来不是,顾客和购物车是一对多的关系。在查询需求中,这种多表之间一对一查询的场景,典型的有:一对一嵌套结果集查询。举个例子,假如我们要查询购物车表中,某一张购物清单信息,以及对应的该名顾客的个人信息。有两种配置方式,或是说两种方法实现:

resultType结合查询包装类

实现上面的一对一嵌套结果集查询,如果使用resultType来配置SQL映射的话,因为涉及到两张表的交互,且最后只能映射到一个实体类中,所以需要新建一个查询包装类,来包括其中含有的两张表的结果集字段对应的属性(这句话表述的不好- -、)。也就是说,新创建一个查询包装类,里面既含有购物车表中的字段对应的属性,也含有用户表中字段对应的属性,这样才能接收结果集中包含的两个表里的数据。来看看这个查询包装类:

// 购物车表一对多查询包装类
public class ShoppingCartInstance extends ShoppingCart implements Serializable {	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String username; //顾客姓名
	private String gender; //性别
	private String province; //省会
	private String city; //城市

	public ShoppingCartInstance() {
		
	}
	
	public ShoppingCartInstance(int cartId, int userId, int productId, 
			String productName, int number, 
			double price, User user, String username, String gender, String province, 
			String city) {
		super(cartId, userId, productId, productName, number, price);
		this.username = username;
		this.gender = gender;
		this.province = province;
		this.city = city;
	}
    // 省略get()和set()方法
}

我们的查询包装类采用继承的方式得到购物车实体类的所有属性,当然你也可以重新一个一个自己定义,这样来舍去一些不需要的属性。接着在查询包装类中追加顾客表中的属性,对应返回结果集中顾客数据表的字段。

      数据包装类准备好后,接下来可以写我们的SQL语句和映射配置了:

<!-- 一对一查询:resultType实现 -->
	<select id="queryShoppingCartInstance1" parameterType="int" resultType="ShoppingCartInstance">
		SELECT
			S.cartId, S.productName, S.userId, S.price, S.number,
			U.id, U.username, U.gender, U.province, U.city
		FROM ShoppingCart S left outer join user U on S.userId = U.id 
		WHERE S.cartId = #{id}
	</select>

SQL语句根据id查询出购物清单,并根据外键,顾客id关联另一张数据表user顾客表。

	public void TestAssociationQuery() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		List<ShoppingCartInstance> resultList = sqlSession.selectList("queryShoppingCartInstance1", 2);
		StringBuffer result = new StringBuffer();
		double totalAmount;
		
		System.out.println("顾客姓名: " + resultList.get(0).getUsername());
		System.out.println("性别: " + resultList.get(0).getGender());
		System.out.println("商品清单:" + "\r\n");
		
		for(int i=0; i<resultList.size(); i++) {
			ShoppingCartInstance shoppingCartInstance = resultList.get(i);
			result.append("商品名:" + shoppingCartInstance.getProductName() + "\r\n");
			result.append("商品数量:" + shoppingCartInstance.getNumber() + "\r\n");
			totalAmount = (shoppingCartInstance.getPrice()*shoppingCartInstance.getNumber());
			result.append("商品总价:" + String.format("%.2f", totalAmount) + "\r\n");
			
			System.out.println(result.toString());
			result.setLength(0);
		}
		
		sqlSession.close();
	}

最后写个简单的测试用例,查询购物车id为2的商品清单信息,以及对应的顾客信息。

 

resultMap结合association标签

使用resultMap配置的话,灵活性就增强了许多,首先resultMap允许我们配置返回结果集中字段名和Java包装类中属性名的一一映射关系。且resultMap中提供的如association标签和collection标签,功能强大,可以配置多表之间的交互。还是上面的例子采用resultMap配置的话,在查询包装类中,我们可以直接追加一个User类的实例:

public class ShoppingCartInstance extends ShoppingCart implements Serializable {	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
//	private String username; //顾客姓名
//	private String gender; //性别
//	private String province; //省会
//	private String city; //城市
	private User user; //对应用户
	private Product products;

	public ShoppingCartInstance() {
		
	}
	
	public ShoppingCartInstance(int cartId, int userId, int productId, 
			String productName, int number, 
			double price, User user) {
		super(cartId, userId, productId, productName, number, price);
//		this.username = username;
//		this.gender = gender;
//		this.province = province;
//		this.city = city;
		this.user = user;
	}
    // 省略get()和set()方法
}

这个User类实例就是用来接收返回结果集中的顾客信息。接下来就配置它们的映射关系:

<resultMap type="shoppingCartInstance" id="shoppingCartResult">
		<id property="id" column="shoppingCart_id"/>
		<result property="cartId" column="cart_id"/>
		<result property="productName" column="product_name"/>
		<result property="price" column="product_price"/>
		<result property="number" column="product_number"/>
		<result property="productId" column="product_id"/>
		<association property="user" column="cartUser_id" javaType="com.mybatis.po.User" 
		resultMap="userResult"/>
	</resultMap>
	
	<resultMap type="com.mybatis.po.User" id="userResult">
		<id property="id" column="user_id"/>
		<!-- 配置User类中的映射关系 -->
		<result property="username" column="user_name"/>
		<result property="gender" column="user_gender"/>
		<result property="province" column="user_province"/>
		<result property="city" column="user_city"/>
	</resultMap>

在查询包装类的映射关系配置resultMap中,type指明接收的实体类是shoppingCartInstance,下面除了配置各个数据库字段和实体类属性的映射外,还用assocaition标签配置关联的嵌套结果集,也就是查询包装类中的User类实例,并且指明User类的映射关系resultMap是引用外部的userResult(也就是下面那个)。

      配置完resultMap,那SQL语句就很简单,把resultType修改为resultMap就可以了:

<!-- 一对多查询SQL语句 -->
	<select id="queryShoppingCartInstance2" parameterType="int" resultMap="shoppingCartResult">
		SELECT
			S.cartId	as cart_id,
			S.productName	as product_name,
			S.userId	as cartUser_id,
			S.price	as product_price,
			S.number	as product_number,
			U.id	as user_id,
			U.username	as user_name,
			U.gender	as user_gender,
			U.province	as user_province,
			U.city	as user_city
		FROM ShoppingCart S left outer join user U on S.userId = U.id 
		WHERE S.cartId = #{id}
	</select>

最后是测试用例:

​
	public void TestAssociationQuery() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		List<ShoppingCartInstance> resultList = sqlSession.selectList("queryShoppingCartInstance2", 2);
		StringBuffer result = new StringBuffer();
		double totalAmount;
		
		System.out.println("顾客姓名: " + resultList.get(0).getUser().getUsername());
		System.out.println("性别: " + resultList.get(0).getUser().getGender());
		System.out.println("商品清单:" + "\r\n");
		
		for(int i=0; i<resultList.size(); i++) {
			ShoppingCartInstance shoppingCartInstance = resultList.get(i);
			result.append("商品名:" + shoppingCartInstance.getProductName() + "\r\n");
			result.append("商品数量:" + shoppingCartInstance.getNumber() + "\r\n");
			totalAmount = (shoppingCartInstance.getPrice()*shoppingCartInstance.getNumber());
			result.append("商品总价:" + String.format("%.2f", totalAmount) + "\r\n");
			
			System.out.println(result.toString());
			result.setLength(0);
		}
		
		sqlSession.close();
	}

​

 

一对多查询

一对多查询,假如我们在上面例子的基础上,加上一个,查询出购物清单中每一件商品的一些商品信息,如顾客对件商品的评价,怎么做?

包装类和SQL映射配置

首先看查询包装类,既然是在上一个例子的基础上,增加查询顾客对商品的评价信息,那么只需要在原有的查询包装类中,加上一个结果集属性即可,因为一件商品可以被多名顾客做评价,所以商品的评价属性我们用一个集合List来表示:

// 一对多查询包装类
public class ShoppingCartInstance extends ShoppingCart implements Serializable {	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private User user; //对应用户
	private List<ProductAppraise> productAppraise; //商品评价集合

	public ShoppingCartInstance() {
		
	}
	
	public ShoppingCartInstance(int cartId, int userId, int productId, 
			String productName, int number, 
			double price, User user, List<ProductAppraise> productAppraise) {
		super(cartId, userId, productId, productName, number, price);
		this.user = user;
		this.productAppraise = productAppraise;
	}   
    // 省略get()和set()方法
}

查询包装类定义好后,接下来到SQL语句和映射配置。因为有嵌套结果集和结合的缘故,很明显我们要用resultMap来配置映射关系:

<!-- 一对多映射配置 -->
	<resultMap type="shoppingCartInstance" id="multiShoppingCartResult" 
	extends="shoppingCartResult">
		<collection property="productAppraise" ofType="ProductAppraise">
			<!-- 主键 -->
			<id column="id" property="id"/>
			<result property="productId" column="productAppraise_id"/>
			<result property="productScore" column="product_score"/>
			<result property="userId" column="user_id"/>
		</collection>
	</resultMap>

因为我们的需求是在上面的查询基础上,增加查询商品的评价,所以映射配置我们只需继承上面的resultMap,然后增加一个嵌套集合配置,使用collection标签即可。

      配置还没完,还有最重要的SQL语句:

<!-- 一对多查询SQL语句 -->
	<select id="queryShoppingCartInstance2" parameterType="int" resultMap="shoppingCartResult">
		SELECT
			S.cartId	as cart_id,
			S.productName	as product_name,
			S.userId	as cartUser_id,
			S.price	as product_price,
			S.number	as product_number,
			U.id	as user_id,
			U.username	as user_name,
			U.gender	as user_gender,
			U.province	as user_province,
			U.city	as user_city
		FROM ShoppingCart S left outer join user U on S.userId = U.id 
		WHERE S.cartId = #{id}
	</select>

SQL语句中,查询三张表中的数据,根据外键顾客id来关联顾客和对应的购物车,根据商品号productId来关联购物车中的商品和商品评价信息。

测试用例

最后是一对多查询的测试用例:

// 一对多查询测试用例
public void TestAssociationQuery() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		List<ShoppingCartInstance> resultList = sqlSession.selectList("queryShoppingCartInstance4");
		StringBuffer result = new StringBuffer();
		double totalAmount;
		
		System.out.println("顾客姓名: " + resultList.get(0).getUser().getUsername());
		System.out.println("性别: " + resultList.get(0).getUser().getGender());
		System.out.println("商品清单:" + "\r\n");
		
		for(int i=0; i<resultList.size(); i++) {
			ShoppingCartInstance shoppingCartInstance = resultList.get(i);
			result.append("商品名:" + shoppingCartInstance.getProductName() + "\r\n");
			result.append("商品数量:" + shoppingCartInstance.getNumber() + "\r\n");
			totalAmount = (shoppingCartInstance.getPrice()*shoppingCartInstance.getNumber());
			result.append("商品总价:" + String.format("%.2f", totalAmount) + "\r\n");
			
			List<ProductAppraise> appraiseList = shoppingCartInstance.getProductAppraise();
			for(int j=0; j<appraiseList.size(); j++) {
				ProductAppraise productAppraise = appraiseList.get(j);
				System.out.println("商品评分:" + productAppraise.getProductScore());
			}
			
			System.out.println(result.toString());
			result.setLength(0);
		}
		
		sqlSession.close();
	}

测试用例中,每一次从结果集中拿到一个shoppingCartInstance对象后,第18行,都要从对象中再获取商品评价的集合,然后从商品评价集合中在获取商品评分,这里要注意一下。

测试结果:

 

多对多查询

还有更复杂的需求,就是要用到多对多查询,由前面事例模型可知,具有多对多关系的是顾客和商品之间,因为一名顾客可以购买多种商品,同一种商品也可以被多名顾客购买,这就形成了很明显的多对多关系。假如现在有这么一个需求,查询所有的顾客,以及这些顾客对应的商品清单,还有清单里所有商品的商品id信息。怎么做?还是先从查询包装类开始:

// 多对多查询包装类
public class ShoppingCartInstance extends ShoppingCart implements Serializable {	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private User user; //对应用户
	private Product products;

	public ShoppingCartInstance() {
		
	}
	
	public ShoppingCartInstance(int cartId, int userId, int productId, 
			String productName, int number, 
			double price, User user, Product products) {
		super(cartId, userId, productId, productName, number, price);
		this.user = user;
		this.products = products;
	}
    // 省略get()和set()方法
}

为了简化示例,我们只查询商品信息中的商品id信息,再查询包装类中,我们在上面的示例基础上,移除商品评价集合,追加一个商品结果集实体类实例对象products,用来接收商品信息。

多个association标签

接下来到SQL映射配置:

    <resultMap type="shoppingCartInstance" id="shoppingCartResult">
		<id property="id" column="shoppingCart_id"/>
		<result property="cartId" column="cart_id"/>
		<result property="productName" column="product_name"/>
		<result property="price" column="product_price"/>
		<result property="number" column="product_number"/>
		<result property="productId" column="product_id"/>
		<association property="user" column="cartUser_id" javaType="com.mybatis.po.User" 
		resultMap="userResult"/>
		<association property="products" column="cartUser_id" javaType="com.mybatis.po.Product">
			<result property="productId" column="products_id"/>
		</association>
	</resultMap>
	
	<resultMap type="com.mybatis.po.User" id="userResult">
		<id property="id" column="user_id"/>
		<!-- 配置User类中的映射关系 -->
		<result property="username" column="user_name"/>
		<result property="gender" column="user_gender"/>
		<result property="province" column="user_province"/>
		<result property="city" column="user_city"/>
	</resultMap>

在多对多映射配置中,注意用了两个association标签,这是可以的,因为我们的查询包装类中,嵌套了两个实体类,一个顾客类和一个商品类。两个association标签配置有一点不同就是,顾客映射关系配置使用了外部的resultMap,而商品信息映射关系配置直接在association标签内配置,两种方法都可以。SQL映射配置完后,到SQL语句:

<!-- 多对多查询SQL示例语句 -->
	<select id="queryShoppingCartInstance4" resultMap="shoppingCartResult">
		SELECT
			S.cartId	as cart_id,
			S.productName	as product_name,
			S.userId	as cartUser_id,
			S.price	as product_price,
			S.number	as product_number,
			S.productId as product_id,
			U.id	as user_id,
			U.username	as user_name,
			U.gender	as user_gender,
			U.province	as user_province,
			U.city	as user_city,
			P.productId as products_id
		FROM Products P, ShoppingCart S, user U 
		WHERE S.userId = U.id AND S.productId = P.productId
	</select>

这个比较简单,注意外键的关联即可,通过顾客id关联顾客和顾客的商品清单,通过商品id关联购物清单中的商品和商品信息。最后是测试用例:

// 多对多测试用例
public void TestAssociationQuery() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		List<ShoppingCartInstance> resultList = sqlSession.selectList("queryShoppingCartInstance4");
		StringBuffer result = new StringBuffer();
		double totalAmount;
		
		for(int i=0; i<resultList.size(); i++) {
			ShoppingCartInstance shoppingCartInstance = resultList.get(i);
			result.append("顾客姓名: " + shoppingCartInstance.getUser().getUsername() + "\r\n");
			result.append("性别: " + shoppingCartInstance.getUser().getGender() + "\r\n");
			result.append("商品id:" + shoppingCartInstance.getProducts().getProductId() + "\r\n");
			result.append("商品名:" + shoppingCartInstance.getProductName() + "\r\n");
			result.append("商品数量:" + shoppingCartInstance.getNumber() + "\r\n");
			totalAmount = (shoppingCartInstance.getPrice()*shoppingCartInstance.getNumber());
			result.append("商品总价:" + String.format("%.2f", totalAmount) + "\r\n");

			System.out.println(result.toString());
			result.setLength(0);
		}
		
		sqlSession.close();
	}

也比较简洁明了,因为所有信息都在一个查询包装类中,我们可以一一拿出来:

 

完整实现已上传GitHub:

https://github.com/justinzengtm/SSM-Framework

发布了97 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/justinzengTM/article/details/99043667