在JAVA实体类中,类与类的一对多、多对一、多对多关系通过组合的形式实现;
在数据库中,表之间的一对多、多对一、多对多的关系通过使用外键来实现;
一对多、多对一、多对多关系的查询都通过多表查询完成。
其中多对多的关系都需要通过一个中间类/表来实现,比如产品和订单之间,一个订单中可以有多个产品,一个产品也可以存在与多个订单之中,这种情况就称为多对多的关系,要建立两者之间的连接,需要建立一个中间类/表(订单项),一个订单项实例包含自身属性和产品属性、订单属性。
MyBatis一对多、多对一关系的实现
一对多关系和多对一关系实质上是两个实体类(数据库表之间的关系),比如一个分类中包含多个产品,那么分类和产品之间就是一对多的关系;反过来想,就会有多个产品对应一个分类(假如产品只属于一个分类),这就是一对多的关系和多对一的关系。
一对多
那么在JAVA类层面和数据库层面怎么实现这种关系呢?对于JAVA类层面,分类中含有多种产品,那么在代表分类的JAVA类中添加一个产品类的List对象;对于数据库层面,在一对多中对应的“多”的表中(产品表)增加对应的“一”的主键列作为外键,如此便建立了一对多的关系。
如果只是查询一对多的关系,只通过一查多,配置已经完成,我们可以去简历XML映射文件添加查询语句,但是此处要注意,因为是数据库表之间是通过外键建立联系,只是通过一张表无法查询到对应关系的全部信息,因此只能通过多表查询来完成。
除此之外,我们还要注意到,MyBatis的工作方式是将查询到的字段和返回值类型中的属性进行匹配,要求实体类属性和表列名必须一致(可以定义这种一致性,在总配置文件中可以选择不同的属性和列的匹配方式),现在,多表查询,如果两个表中出现了重复的列呢?两个表中的所有列,如何才能和一个实体类进行匹配呢(包含另一个实体类,但是存储的是类对象,SQL查询结果都为属性列)?这里就需要我们自己定义resultMap对象,用于匹配查询结果和类中的属性。以上述分类和产品为例,定义JAVA类如下:
public class Category {
private int id;
private String name;
List<Product> products;
//省略get set方法
}
定义产品类如下:
public class Product {
private int id;
private String name;
private float price;
}
数据库表结构如下:(分别为分类表和产品表)
与分类的实体类相映射的XML文件中查询分类所定义的产品:
注意:所有的属性都需要配置!!!即使不重名的,否则查询结果不会导入属性中
只有在SQL语句中不查询的列可以不配置(不需要的列信息)
<!-- 一对多查询 -->
<!-- 配置resultMap
type指示此返回结果的类型
id为本个resultMap的标识 用于在查询标签的resultMap属性中使用
id为主键元素 result为非主键元素
column行指代的为查询结果表中生成的列名称(在SQL语句中使用的列别名)
property为java实体类中的字段值 -->
<resultMap type="Category" id="categoryBean">
<id column="cid" property="id" />
<result column='cname' property="name"/>
<!-- 一对多的关系 collection配置分类 的实体类中的产品属性-->
<!-- property: 指的是集合属性的值, ofType:指的是集合中元素的类型 -->
<collection property="products" ofType="Product">
<id column="pid" property="id"/>
<result column="pname" property="name"/>
<result column="price" property="price"/>
</collection>
</resultMap>
<!--查询语句如下-->
<select id="listCategory" resultMap="categoryBean">
select c.id as cid,c.name as cname,p.id as pid,p.name as pname,p.price from category as c left join product as p on c.id = p.id
</select>
多对一
在JAVA类中如何配置多对一的关系呢?多对一就是多个产品拥有共同的分类,但这并不影响他们自己拥有分类,因此只需要在产品类中添加分类对象即可。至于在数据库的层面,我们知道一对多的关系中已经通过外键将两张表联系起来,因此并不需要添加其他表列。
现在,产品类中也多出了类属性,在查询时MybBatis不能将连接查询的表列自动匹配给实体类属性的属性,同样需要配置resultMap
修改后Product类如下:
public class Product {
private int id;
private String name;
private float price;
private Category category;
}
配置与产品类相对应的XML映射文件如下:
<!-- 多对一关系展示 -->
<resultMap type="Product" id="productBean">
<id column="pid" property="id"/>
<result column="pname" property="name"/>
<!-- 即使列名和属性名相同 也要配置 否则resultMap里回缺少此项的值 而是默认值 -->
<result column="price" property="price"/>
<!-- 多对一的关系 使用association-->
<!-- property: 指的是属性名称, javaType:指的是属性的类型 -->
<association property="category" javaType="Category">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</association>
</resultMap>
<!-- select 查询语句 -->
<select id="listProduct" resultMap="productBean">
select p.id as pid,p.name as pname,p.price,c.id as cid,c.name as cname from product as p,category as c where p.cid=c.id
</select>
关于多对多关系为什么要创建中间项?
从类的角度理解为,一个订单类包括多个产品类,而一个产品类又包括多个订单类,enmmm好像没有什么不好,有没有大佬解答下。
从数据库表的角度理解,可以减少信息的冗余,产品里面只存储所有的产品,订单类只存储所有的订单,在订单中没有产品ID,在产品表中没有订单ID, 产品和订单之间的联系通过订单项建立(在一对多和多对一的关系中,我们通过在对应的“多”表中添加了对应的“一”表列)。
这也是数据库设计上要求的!!!
多对多的关系分解为两个一对多、多对一的关系,一个订单对应多个订单项,一个订单项对应一个订单、一个产品,一个产品对应多个订单项。
建立如下java类:
public class Order {
private int id;
private String code;
private List<OrderItem> orderItems;
}
public class OrderItem {
private int id;
private int number;
private Order order;
private Product product;
}
数据库表如下(product、orderItem、order):
通过订单查询订单中的产品(实质为查询订单类属性信息,因此需要配置与订单类重名的XML映射文件):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cn.bean">
<!-- 多对多关系演示 -->
<!-- type返回的对象类型 id命名标识 -->
<resultMap type="Order" id="orderBean">
<!-- Order类的非类属性 -->
<!-- Order表主键 column:查询结果中取的列别名 property:对应Order类中的信息-->
<id column="oid" property="id"/>
<!-- 没有取别名的属性也需要进行配置 -->
<result column="code" property="code"/>
<!-- 对应多个订单项 使用collection-->
<!-- Order类中属性 属性所对应的类型 -->
<collection property="orderItems" ofType="OrderItem">
<!-- 可以不配置不需要的列 -->
<!-- OrderItem中没有什么有用的信息 比如id信息对于我们来说意义不大 不配置此列 -->
<id column="oiid" property="id"/>
<!--!!!!不配置此列会出现查询结果不全的情况 原因未知!!!!!! -->
<result column="number" property="number"/><!-- 产品数量 -->
<!-- OrderItem和Product的一对多关系 -->
<association property="product" javaType="Product">
<id column="pid" property="id"/>
<result column="name" property="name"/>
<result column="price" property="price"/>
</association>
</collection>
</resultMap>
<!-- 查询所有的订单 -->
<select id="listOrder" resultMap="orderBean">
select o.id as oid,o.code,oi.number,p.id as pid,p.name,p.price from `order` as o left join orderItem as oi on o.id=oi.oid inner join product as p on oi.pid=p.id
</select>
</mapper>
前面说不查询的列可以不配置,但是多对多的关系中,中间表的主键项如果不配置的话,就会出现查询结果部分丢失的情况,原因未知。
并不是主键必须配置,如果删除掉resultMap中产品表中关于id的配置,也能够成功查询,可能是因为中间表是个特例?等一个大佬。。。
最后,一个可有可无的提醒,千万记得将映射文件加入mybatis的总配置文件中0.0
思考一个问题,映射文件必须与实体类重名吗?不一定
映射文件中各种标签的id可以重名吗?不可以
如果可以的话根据传入参数的不同会不会是重载呢?不会