在实际开发中,会存在很多表。比如一个电商项目可能有:分类表、产品表、顾客表、订单表等。这些表之间是存在关系的。
表与表之间的三种关系
判断技巧 | 例子 | |
---|---|---|
一对多 | 分别从两个角度思考问题:比如班级对学生。 从班级的角度看问题:一个班级可以对应很多学生。 从学生角度看问题:一个学生只能对应一个班级。 |
班级对学生、部门对员工、用户对订单、人对银行卡、客户和订单、分类和商品 |
多对多 | 学生与课程、用户与角色 | |
一对一 | 人对身份证 |
一对多
建表原则
先分清楚一方和多方,根据上面表格的判断技巧来判断,这样很容易搞清楚。在多方的表中添加一个字段,作为该表的外键,指向一方的主键。
例如一个人可以有很多银行卡,一个银行卡只能对应一个人。在从表(多方)里面新建一个外键指向主表(一方)的主键。这里从表是bank_card 、主表是person。bank_card里面的person_id做为外键指向person表的主键。
SQL语句
查询所有人对应的银行卡信息
一个人可以拥有多个银行卡,查询的是人,从person表出发(所谓从哪里出发指的是FROM 后先跟什么表)
SELECT
person.* ,
bank_card.id AS bank_id,bank_card.`no`,bank_card.amount
FROM person LEFT JOIN bank_card ON person.id = bank_card.fn_person_id
本节末尾有其对应的JavaBean及SQL样例数据。
MyBatis对应的mapper
<select id="getPersonBankCard" resultMap="bankCards">
SELECT
person.* ,
bank_card.id AS bank_id,bank_card.`no`,bank_card.amount
FROM person LEFT JOIN bank_card ON person.id = bank_card.fn_person_id
</select>
<resultMap id="bankCards" type="com.lff.beans.Person">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<collection property="bankCards" ofType="com.lff.beans.BankCard">
<id property="id" column="bank_id" />
<result property="no" column="no" />
<result property="amount" column="amount" />
</collection>
</resultMap>
外界使用
try(SqlSession session = MyBatisUtils.openSession()){
List<Person> personList = session.selectList("person.getPersonBankCard");
System.out.println(personList);
}
查询每张银行卡把关联的人也查出来
从bank_card银行卡出发,根据条件关联其对应的人
SELECT
bank_card.*,
person.*
FROM bank_card LEFT JOIN person ON person.id = bank_card.fn_person_id
多对多
如果是多对多,通常会创建一个中间表,把表的关系拆成两个一对多。在中间表至少包含两个字段,作为该表的外键指向一方表的主键。中间表不仅能有效的节省存储空间也能更容易的维护表与表之间的关系。
例子:人与角色之间的关系,一个人可以有很多个角色。一个角色可以有很多个人。
中间表至少包含两个字段:person_id外键、role_id外键。这两个外键作为联合主键使用。
如上表,person表里面的Jack既有作为丈夫的角色也有作为厨师的角色
SQL语句
查询每个人都对应哪些角色
从person表出发,关联中间表后,再查询角色表
SELECT
person.*,
role.`name` AS role_name,role.duty
FROM person LEFT JOIN person_role ON person.id = person_role.person_id
LEFT JOIN role ON role.id = person_role.role_id
MyBatis对应的mapper
<select id="getPersonRoles" resultMap="roles">
SELECT
person.*,
role.`name` AS role_name,role.duty
FROM person LEFT JOIN person_role ON person.id = person_role.person_id
LEFT JOIN role ON role.id = person_role.role_id
</select>
<resultMap id="roles" type="com.lff.beans.Person">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<collection property="roles" ofType="com.lff.beans.Role">
<result property="name" column="role_name" />
<result property="duty" column="duty" />
</collection>
</resultMap>
外界使用
try(SqlSession session = MyBatisUtils.openSession()){
List<Person> personList = session.selectList("person.getPersonRoles");
System.out.println(personList);
}
查询每个角色都有什么人
SQL语句,从role角色表出发,先关联中间表,关联后再查询person表
SELECT
role.id AS role_id ,role.`name` AS role_name ,role.duty,
person.id AS person_id, person.`name` AS person_name,person.age
FROM role LEFT JOIN person_role ON role.id = person_role.role_id
LEFT JOIN person ON person.id = person_role.person_id
一对一
建表原则:
- 可以都放在一张表中
- 也可以在任意一方添加一个外键指向另一方的主键。从表的唯一外键和主表的主键,形成主外键关系, 外键唯一用UNIQUE修饰
示例:人和人的身份证
id_card有一个外键引用person表的id。
每一个人都有一个唯一的身份证号
SQL查询语句
查询所有的人以及对应的身份证
从person表出发,根据条件查询其对应的身份证
SELECT person.* , id_card.`no`,id_card.address,id_card.person_id FROM person JOIN id_card ON person.id = id_card.person_id
MyBatis对应的mapper
<?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="person">
<select id="personList" resultMap="personMap">
SELECT person.* , id_card.`no`,id_card.address,id_card.person_id FROM person JOIN id_card ON person.id = id_card.person_id
</select>
<resultMap id="personMap" type="com.lff.beans.Person">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<association property="idCard" javaType="com.lff.beans.IdCard">
<id property="id" column="id" />
<result property="no" column="no" />
<result property="address" column="address" />
</association>
</resultMap>
</mapper>
外界使用
try(SqlSession session = MyBatisUtils.openSession()){
List<Person> personList = session.selectList("person.personList");
}
查询所有的身份证以及对应的人
从id_card表出发,根据条件查询出对应的人。
SELECT id_card.* ,person.`name`,person.age FROM id_card JOIN person ON id_card.person_id = person.id
MyBatis对应的mapper
<?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="idCard">
<select id="idCardList" resultMap="idCardMap">
SELECT id_card.* ,person.`name`,person.age FROM id_card JOIN person ON id_card.person_id = person.id
</select>
<resultMap id="idCardMap" type="com.lff.beans.IdCard">
<id property="id" column="id" />
<result property="no" column="no" />
<result property="address" column="address" />
<association property="person" javaType="com.lff.beans.Person">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
</association>
</resultMap>
</mapper>
外界使用
try(SqlSession session = MyBatisUtils.openSession()){
List<IdCard> idCardList = session.selectList("idCard.idCardList");
}
综合案例
查询出人对应的信息,包括姓名、年龄、身份证信息、银行卡信息、角色信息。
SQL语句
SELECT
person.* ,
id_card.`no` AS id_no,id_card.address,
bank_card.id AS bank_card_id,bank_card.`no` AS bank_card_no,bank_card.amount ,
role.`name` AS role_name,role.duty
FROM person JOIN id_card ON person.id = id_card.person_id
LEFT JOIN bank_card ON person.id = bank_card.fn_person_id
LEFT JOIN person_role ON person.id = person_role.person_id
LEFT JOIN role ON role.id = person_role.role_id
- 因为要查询出的是人,所以FROM后应先跟person表
- 使用join关联出身份证信息
- 使用LEFT JOIN查询出银行卡信息
- LEFT JOIN person_role ON person.id = person_role.person_id
多对多的情况先关联中间表,先有中间表后,后面才能使用中间表里面的信息
当有多个表的字段相同时,要起一个别名,否则查询出来的字段会覆盖前面的。
MyBatis对应的mapper
<select id="getPersonAllInfo" resultMap="allInfo">
SELECT
person.* ,
id_card.`no` AS id_no,id_card.address,
bank_card.id AS bank_card_id,bank_card.`no` AS bank_card_no,bank_card.amount ,
role.`name` AS role_name,role.duty
FROM person JOIN id_card ON person.id = id_card.person_id
LEFT JOIN bank_card ON person.id = bank_card.fn_person_id
LEFT JOIN person_role ON person.id = person_role.person_id
LEFT JOIN role ON role.id = person_role.role_id
</select>
<resultMap id="allInfo" type="com.lff.beans.Person">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<!-- 关联的身份证信息:idCard为Person类的属性名-->
<association property="idCard" javaType="com.lff.beans.IdCard">
<!-- 这里的属性property都是类IdCard的属性名,column是数据库查询出来的字段-->
<result property="no" column="id_no" />
<result property="address" column="address" />
</association>
<!-- 当属性值是一个列表时如Person类的 List<BankCard> bankCards,使用collection标签,ofType对应列表里面每一个对象对应的类-->
<collection property="bankCards" ofType="com.lff.beans.BankCard">
<!-- 这里的属性property都是类BankCard的属性名,column是数据库查询出来的字段-->
<result property="no" column="bank_card_no" />
<result property="amount" column="amount" />
</collection>
<collection property="roles" ofType="com.lff.beans.Role">
<result property="name" column="role_name" />
<result property="duty" column="duty" />
</collection>
</resultMap>
外界使用
try(SqlSession session = MyBatisUtils.openSession()){
List<Person> personList = session.selectList("person.getPersonAllInfo");
System.out.println(personList);
}
本节使用的SQL样例
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for bank_card
-- ----------------------------
DROP TABLE IF EXISTS `bank_card`;
CREATE TABLE `bank_card` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`no` int(11) NOT NULL,
`amount` decimal(18,2) DEFAULT NULL,
`fn_person_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fn_person_id` (`fn_person_id`),
CONSTRAINT `fn_person_id` FOREIGN KEY (`fn_person_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of bank_card
-- ----------------------------
BEGIN;
INSERT INTO `bank_card` VALUES (1, 123456, 1000.00, 1);
INSERT INTO `bank_card` VALUES (2, 234567, 300.00, 1);
INSERT INTO `bank_card` VALUES (3, 345678, 200.00, 3);
INSERT INTO `bank_card` VALUES (4, 456789, 500.00, 4);
COMMIT;
-- ----------------------------
-- Table structure for id_card
-- ----------------------------
DROP TABLE IF EXISTS `id_card`;
CREATE TABLE `id_card` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`no` int(30) DEFAULT NULL,
`address` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`person_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_person_id` (`person_id`),
CONSTRAINT `fk_person_id` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of id_card
-- ----------------------------
BEGIN;
INSERT INTO `id_card` VALUES (1, 123999, '北京市朝阳区。。。', 1);
INSERT INTO `id_card` VALUES (2, 123888, '北京市昌平区。。。', 2);
INSERT INTO `id_card` VALUES (3, 123777, '北京市海淀区。。。', 3);
INSERT INTO `id_card` VALUES (4, 123666, '北京市通州区。。。', 4);
COMMIT;
-- ----------------------------
-- Table structure for person
-- ----------------------------
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8mb4 DEFAULT NULL,
`age` int(3) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id` (`id`),
KEY `age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of person
-- ----------------------------
BEGIN;
INSERT INTO `person` VALUES (1, 'Jack', 18);
INSERT INTO `person` VALUES (2, 'Mike', 22);
INSERT INTO `person` VALUES (3, 'Jose', 28);
INSERT INTO `person` VALUES (4, 'Cliff', 35);
COMMIT;
-- ----------------------------
-- Table structure for person_role
-- ----------------------------
DROP TABLE IF EXISTS `person_role`;
CREATE TABLE `person_role` (
`person_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`person_id`,`role_id`),
KEY `r_id` (`role_id`),
CONSTRAINT `p_id` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`),
CONSTRAINT `r_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of person_role
-- ----------------------------
BEGIN;
INSERT INTO `person_role` VALUES (2, 1);
INSERT INTO `person_role` VALUES (1, 2);
INSERT INTO `person_role` VALUES (1, 3);
COMMIT;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_bin NOT NULL,
`duty` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, '程序员', '编码大神');
INSERT INTO `role` VALUES (2, '丈夫', '爱妻子');
INSERT INTO `role` VALUES (3, '厨师', '能够做一手好菜');
INSERT INTO `role` VALUES (4, '摄影爱好者', '拍照达人');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
本节涉及的JavaBean
Person类
package com.lff.beans;
import java.util.List;
public class Person {
private Integer id;
private String name;
private Integer age;
private IdCard idCard;
private List<BankCard> bankCards;
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
public List<BankCard> getBankCards() {
return bankCards;
}
public void setBankCards(List<BankCard> bankCards) {
this.bankCards = bankCards;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
银行卡类
package com.lff.beans;
import java.math.BigDecimal;
public class BankCard {
private Integer id;
private String no;
private BigDecimal amount;
private Person person;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
身份证类
package com.lff.beans;
public class IdCard {
private Integer id;
private String no;
private String address;
private Person person;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
角色类
package com.lff.beans;
import java.util.List;
public class Role {
private Integer id;
private String name;
private String duty;
private List<Person> persons;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDuty() {
return duty;
}
public void setDuty(String duty) {
this.duty = duty;
}
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
}