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