关联映射概述
关联映射关系
在关系型数据库中,表与表之间存在着三种关联映射关系,分别为一对一关系、一对多关系和多对多关系。
一对一关系:一个数据表中的一条记录最多可以和另一个数据表中的一条记录相关。例如,现实生活中学生与校园卡就属于一对一的关系,一个学生只能拥有一张校园卡,一张校园卡只能属于一个学生。
一对多关系:主键数据表中的一条记录可以和另外一个数据表的多条记录相关。但另外一个数据表中的记录只能与主键数据表中的某一条记录相关。例如,现实中班级与学生的关系就属于一对多的关系,一个班级可以有很多学生,但一个学生只能属于一个班级。
多对多关系:一个数据表中的一条记录可以与另外一个数据表任意数量的记录相关,另外一个数据表中的一条记录也可以与本数据表中任意数量的记录相关。例如,现实中学生与教师属于多对多的关系,一名学生可以由多名教师授课,一名教师可以为多名学生授课。
Java对象如何描述事物之间的关系
数据表之间的关系实质上描述的是数据之间的关系,除了数据表,在Java中,还可以通过对象来描述数据之间的关系。通过Java对象描述数据之间的关系,其实就是使对象的属性与另一个对象的属性相互关联。
Java对象描述数据之间的关联映射关系有三种,分别是一对一、一对多和多对多。
一对一
就是在本类中定义与之关联的类的对象作为属性,例如,A类中定义B类对象b作为属性,在B类中定义A类对象a作为属性。
一对多
就是一个A类对象对应多个B类对象的情况,例如,定义在A类中,定义一个B类对象的集合作为A类的属性;在B类中,定义A类对象a作为B类的属性。
多对多
在两个相互关联的类中,都可以定义多个与之关联的类的对象。例如,在A类中定义B类类型的集合作为属性,在B类中定义A类类型的集合作为属性。
需求设计和开发时代码时:一定要认真、仔细、耐心去分析需求,切勿粗心大意,多思考,考虑事务时要充分。
代码实现
以一对一查询为例,能够使用<association>元素处理一对一关联关系。
在现实生活中,一对一关联关系是十分常见的。例如,一个人只能有一个身份证,同时一个身份证也只会对应一个人。人与身份证之间的关联关系如图。
案例:
案例具体实现步骤如下。
(1)创建数据库:
CREATE DATABASE IF NOT EXISTS mybatis; USE mybatis; # 创建一个名称为tb_idcard的表 CREATE TABLE tb_idcard( id INT PRIMARY KEY AUTO_INCREMENT, CODE VARCHAR(18) ); # 插入2条数据 INSERT INTO tb_idcard(CODE) VALUES('152221198711020624'); INSERT INTO tb_idcard(CODE) VALUES('152201199008150317'); # 创建一个名称为tb_person的表 CREATE TABLE tb_person( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32), age INT, sex VARCHAR(8), card_id INT UNIQUE, FOREIGN KEY(card_id) REFERENCES tb_idcard(id) ); # 插入2条数据
'Rose',22,' female',1;
'jack',23,'male',2;
(2)持久化类IDCard类:创建持久化类IdCard,用于封装身份证属性。
public class IdCard { private Integer id; // 主键id private String code; // 身份证号码 // 省略getter/setter方法 @Override public String toString() { return "IdCard [id=" + id + ", code=" + code + "]"; } }
持久化类Person类:创建持久化类Person,用于封装个人属性。
public class Person { private Integer id; // 主键id private String name; // 姓名 private Integer age; // 年龄 private String sex; // 性别 private IdCard card; // 人员关联的证件 // 省略getter/setter方法,重写的toString()方法 }
(3)编写IdCardMapper.xml文件:创建身份证映射文件IdCardMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.IdCardMapper">
<!-- 根据id查询证件信息 -->
<select id="findCodeById" parameterType="Integer" resultType="com.gqx.bean.IdCard">
SELECT * from tb_idcard where id=#{id}
</select>
</mapper>
编写PersonMapper.xml文件:创建人员映射文件PersonMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。
<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gqx.mapper.PersonMapper">
<resultMap type="com.gqx.bean.Person" id="IdCardWithPersonResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!-- 一对一:association使用select属性引入另外一条SQL语句 -->
<association property="card" column="card_id" javaType="com.gqx.bean.IdCard"
select="mapper.IdCardMapper.findCodeById"/>
</resultMap>
<!-- 嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 -->
<select id="findPersonById" parameterType="Integer"
resultMap="IdCardWithPersonResult">
SELECT * from tb_person where id=#{id}
</select>
</mapper>
(4)引入映射文件:在核心配置文件mybatis-config.xml中,引入IdCardMapper.xml和PersonMapper.xml映射文件,并为com.itheima.pojo包下的所有实体类定义别名。
<!-- mapping文件路径配置 -->
<mappers>
<mapper resource="mapper/IdCardMapper.xml" />
<mapper resource="mapper/PersonMapper.xml" />
</mappers>
(5)编写测试类:在测试类MyBatisTest中,编写测试方法findPersonByIdTest()。
@Test
public void userFindByIdTest() {
//读取文件名
String resources = "mybatis-config.xml";
//创建流
Reader reader = null;
try {
//读取mybatis-config.xml文件内容到reader对象中
reader = Resources.getResourceAsReader(resources);
} catch (IOException e) {
e.printStackTrace();
}
//初始化mybatis数据库,创建SqlSessionFactory类的实例
SqlSessionFactory sqlMapper = new
SqlSessionFactoryBuilder().build(reader);
//创建SqlSession实例
SqlSession session = sqlMapper.openSession();
Person person = session.selectOne("findPersonById", 1);
// 3、输出查询结果信息
System.out.println(person);
//关闭session
session.close();
}
一对一关联关系介绍
在MyBatis中,通过<association>元素来处理一对一关联关系。<association>元素提供了一系列属性用于维护数据表之间的关系。<association>元素属性如下:
<association>元素是<resultMap>元素的子元素,它有两种配置方式,嵌套查询方式和嵌套结果方式,下面对这两种配置方式分别进行介绍。
a.嵌套查询方式:嵌套查询是指通过执行另外一条SQL映射语句来返回预期的复杂类型。
<association property="card" column="card_id" javaType="com.gqx.bean.IdCard"
select="mapper.IdCardMapper.findCodeById"/>
b.嵌套结果方式:嵌套结果是使用嵌套结果映射来处理重复的联合结果的子集。
<association property="card" javaType="com.itheima.pojo.IdCard"> <id property="id" column="card_id" /> <result property="code" column="code" /> </association>
一对多查询
掌握一对多查询,能够使用<collection>元素处理一对多关联关系。
与一对一的关联关系相比,接触更多的关联关系是一对多(或多对一)。例如一个用户可以有多个订单,多个订单也可以归一个用户所有。用户和订单的关联关系如图。
在MyBatis中,通过<collection>元素来处理一对多关联关系。<collection>元素的属性大部分与<association>元素相同,但其还包含一个特殊属性一ofType。ofType属性与javaType属性对应,它用于指定实体类对象中集合类属性所包含的元素的类型。 <collection>元素是<resultMap>元素的子元素,<collection >元素有嵌套查询和嵌套结果两种配置方式。
接下来以用户和订单之间的一对多关联关系为例,详细讲解如何在MyBatis中处理一对多关联 关系,具体步骤如下。
(1)在名为mybatis的数据库中,创建两个数据表,分别为tb_user(用户数据表)和tb_orders(订单表),同时在表中预先插入几条测试数据。
CREATE DATABASE IF NOT EXISTS mybatis; USE mybatis; # 创建一个名称为tb_user的表 CREATE TABLE tb_user ( id int(32) PRIMARY KEY AUTO_INCREMENT, username varchar(32), address varchar(256) ); # 插入3条数据,其他语句省略 INSERT INTO tb_user VALUES ('1', '小明', '北京'); INSERT INTO tb_user VALUES ('2', '小红', '杭州'); INSERT INTO tb_user VALUES ('3', '小白', '咸宁'); # 创建一个名称为tb_orders的表 CREATE TABLE tb_orders ( id int(32) PRIMARY KEY AUTO_INCREMENT, number varchar(32) NOT NULL, user_id int(32) NOT NULL, FOREIGN KEY(user_id) REFERENCES tb_user(id) ); # 插入3条数据 INSERT INTO tb_orders VALUES ('1', '1000011', '1'); INSERT INTO tb_orders VALUES ('2', '1000012', '1'); INSERT INTO tb_orders VALUES ('3', '1000013', '2');
(2)创建持久化类Orders,并在类中定义订单id和订单编号等属性。
public class Orders { private Integer id; //订单id private String number; //订单编号 // 省略getter/setter方法 @Override public String toString() { return "Orders [id=" + id + ", number=" + number + "]"; } }
创建持久化类Users,并在类中定义用户编号、用户姓名、 用户地址以及用户关联的订单等属性。
public class Users { private Integer id; // 用户编号 private String username; // 用户姓名 private String address; // 用户地址 private List<Orders> ordersList; // 用户关联的订单 // 省略getter/setter方法 @Override public String toString() { return "User [id=" + id + ", username=" + username + ", address="+ address + ", ordersList=" + ordersList + "]"; } }
(3)创建用户实体映射文件UsersMapper.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为映射的根节点--> <!-- mapper为映射的根节点,namespace指定Dao接口的完整类名 mybatis会依据这个接口动态创建一个实现类去实现这个接口, 而这个实现类是一个Mapper对象--> <mapper namespace="mapper.UsersMapper"> <!-- 一对多:查看某一用户及其关联的订单信息 注意:当关联查询出的列名相同,则需要使用别名区分 --> <select id="findUserWithOrders" parameterType="Integer" resultMap="UserWithOrdersResult"> SELECT u.*,o.id as orders_id,o.number from tb_user u,tb_orders o WHERE u.id=o.user_id and u.id=#{id} </select> <resultMap type="com.gqx.bean.Users" id="UserWithOrdersResult"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="address" column="address"/> <!-- 一对多关联映射:collection ofType表示属性集合中元素的类型,List<Orders>属性即Orders类 --> <collection property="ordersList" ofType="com.gqx.bean.Orders"> <id property="id" column="orders_id"/> <result property="number" column="number"/> </collection> </resultMap> </mapper>
(4) 在核心配置文件mybatis-config.xml中,引入UsersMapper.xml,将UsersMapper.xml映射文件加载到程序中。
<mapper resource="mapper/UserMapper.xml"/>
(5)在测试类MyBatisTest中,编写测试方法。
@Test public void userFindByIdTest() { //读取文件名 String resources = "mybatis-config.xml"; //创建流 Reader reader = null; try { //读取mybatis-config.xml文件内容到reader对象中 reader = Resources.getResourceAsReader(resources); } catch (IOException e) { e.printStackTrace(); } //初始化mybatis数据库,创建SqlSessionFactory类的实例 SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); //创建SqlSession实例 SqlSession session = sqlMapper.openSession(); Users users = session.selectOne("findUserWithOrders", 1); // 3、输出查询结果信息 System.out.println(users.toString()); //关闭session session.close(); }
补充
<collection>元素是<resultMap>元素的子元素,<collection >元素有嵌套查询和嵌套结果两种配置方式。
a.嵌套查询方式
<collection property="ordersList" column="id" ofType="com.itheima.pojo.Orders" select=" com.itheima.mapper.OrdersMapper.selectOrders"/>
b.嵌套结果方式
<collection property="ordersList"ofType="com.itheima.pojo.Orders"> <id property="id" column="orders_id" /> <result property="number" column="number" /> </collection>
多对多查询
在实际项目开发中,多对多的关联关系非常常见。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品属于多对多关联关系,订单和商品之间的关联关系如图。
在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表中的订单id作为外键关联订单表的id,中间表中的商品id作为外键关联商品表的id。这三个表之间的关系如图。
下面以订单表与商品表之间的多对多关系为例,讲解如何使用MyBatis处理多对多的关系
(1)在名为mybatis的数据库中创建名称为tb_product的商品表和名称为tb_ordersitem 的中间表,同时在表中预先插入几条数据。
CREATE DATABASE IF NOT EXISTS mybatis; USE mybatis; # 创建一个名称为tb_product的表 CREATE TABLE tb_product ( id INT(32) PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(32), price DOUBLE ); # 插入3条数据 INSERT INTO tb_product VALUES ('1', 'Java基础入门', '44.5'); INSERT INTO tb_product VALUES ('2', 'Java Web程序开发入门', '38.5'); INSERT INTO tb_product VALUES ('3', 'SSM框架整合实战', '50'); # 创建一个名称为tb_ordersitem 的中间表 CREATE TABLE tb_ordersitem ( id INT(32) PRIMARY KEY AUTO_INCREMENT, orders_id INT(32), product_id INT(32), FOREIGN KEY(orders_id) REFERENCES tb_orders(id), FOREIGN KEY(product_id) REFERENCES tb_product(id) ); # 插入3条数据 INSERT INTO tb_ordersitem VALUES ('1', '1', '1'); INSERT INTO tb_ordersitem VALUES ('2', '1', '3'); INSERT INTO tb_ordersitem VALUES ('3', '3', '3');
(2)创建持久化类
创建持久化订单类
public class Orders { private Integer id; //订单id private String number; //订单编号 //关联商品集合属性 private List<Product> productList; // 省略getter/setter方法 @Override public String toString() { return "Orders{" + "id=" + id + ", number='" + number + '\'' + ", productList=" + productList + '}'; }
创建持久化类Product,并在类中定义商品id、商品名称、商品单价等属性,以及与订单关联的属性。
public class Product { private Integer id;
private String name; private Double price; private List<Orders> orders; //关联订单属性 // 省略getter/setter方法 @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", price=" + price + "]";} }
(3)创建订单实体映射文件OrdersMapper.xml,用于编写订单信息的查询SQL语句,并在映射文件中编写多对多关联映射查询的配置信息。
<?xml version="1.0" encoding="UTF8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="mapper.OrdersMapper"> <!-- 多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 --> <select id="findOrdersWithPorduct" parameterType="Integer" resultMap="OrdersWithProductResult"> select * from tb_orders WHERE id=#{id} </select> <resultMap type="com.gqx.bean.Orders" id="OrdersWithProductResult"> <id property="id" column="id"/> <result property="number" column="number"/> <collection property="productList" column="id" ofType="Product" select="mapper.ProductMapper.findProductById"> </collection> </resultMap> </mapper>
创建商品实体映射文件ProductMapper.xml,用于编写订单与商品信息的关联查询SQL语句。
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="mapper.ProductMapper"> <select id="findProductById" parameterType="Integer" resultType="com.gqx.bean.Product"> SELECT * from tb_product where id IN( SELECT product_id FROM tb_ordersitem WHERE orders_id = #{id} ) </select> </mapper>
(4)将新创建的映射文件OrdersMapper.xml和ProductMapper.xml的文件路径配置到核心配置文件mybatis-config.xml中。
(5)在测试类MyBatisTest中,编写多对多关联查询的测试方法findOrdersTest()。
@Test public void userFindByIdTest() { //读取文件名 String resources = "mybatis-config.xml"; //创建流 Reader reader = null; try { //读取mybatis-config.xml文件内容到reader对象中 reader = Resources.getResourceAsReader(resources); } catch (IOException e) { e.printStackTrace(); } //初始化mybatis数据库,创建SqlSessionFactory类的实例 SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); //创建SqlSession实例 SqlSession session = sqlMapper.openSession(); Orders orders = session.selectOne("findOrdersWithPorduct", 1); // 3、输出查询结果信息 System.out.println(orders.toString()); //关闭session session.close(); }
换用嵌套结果方式查询:
<!-- 多对多嵌套结果查询:查询某订单及其关联的商品详情 --> <select id="findOrdersWithPorduct2" parameterType="Integer" resultMap="OrdersWithPorductResult2"> select o.*,p.id as pid,p.name,p.price from tb_orders o,tb_product p,tb_ordersitem oi WHERE oi.orders_id=o.id and oi.product_id=p.id and o.id=#{id} </select> <!-- 自定义手动映射类型 --> <resultMap type="com.gqx.bean.Orders" id="OrdersWithPorductResult2"> <id property="id" column="id"/> <result property="number" column="number"/> <!-- 多对多关联映射:collection --> <collection property="productList" ofType="com.gqx.bean.Product"> <id property="id" column="pid"/> <result property="name" column="name"/> <result property="price" column="price"/> </collection> </resultMap>