多表查询是在企业中必不可少的,无论多么简单的项目里都会出现多表查询操作。因为只要是关系型数据库,在设计表时都需要按照范式进行设计,为了减少数据冗余,都会拆成多个表。当需要表中数据时,在进行联合查询。
在MySQL学习时,知道表之间关系分为:一对一、一对多、多对多。这三种关系又细分为单向和双向。如果学习的是Hibernate框架,必须要严格区分开表之间的关系,然后才能使用Hibernate框架。但是在MyBatis框架中只有两种情况:当前表对应另外表是一行数据还是多行数据。转换到实体类上:当前实体类包含其他实体类一个对象还是多个对象。再转换到MyBatis的映射文件上:在<resultMap>标签里面使用<association>还是<collection>标签就可以。
所以:在学习MyBatis多表查询时其实就是在学习<association>标签和<collection>标签。其中如果一个实体类关联另一个实体类的一个对象使用<association>。如果一个实体类关联另一个实体类的List集合对象,需要使用<collection>。
所以分析的思路是:先分析需求->分析数据库设计对应关系->创建实体类->根据实体类关联属性类型决定使用哪个标签。
这两个标签根据编写的SQL,分为N+1查询和联合查询两种方式。两种方式优缺点:
N+1方式:
优点:SQL简单。支持延迟加载。
缺点:多做N次查询。
联合查询方式:
优点:一次查询。
缺点:SQL相对复杂。不支持延迟加载。
MyBatis多表查询时一定需要使用<resultMap>标签,因为<association>标签和<collection>标签是<resultMap>的子标签。所以数据库设计时列名和实体类的属性名是否相同无所谓了,下面在设计数据库时按照数据库设计的命名规范进行设计。
案例:
数据库准备 tb_dept部门表 tb_customer客户表 tb_address地址表
create table tb_dept(
dep_id int(11) primary key auto_increment COMMENT '部门主键',
dep_name varchar(32) COMMENT '部门名称',
dept_addr varchar(255) COMMENT '部门地址'
);
insert into tb_dept value(DEFAULT,'教学部','无忧工业园');
insert into tb_dept value(DEFAULT,'行政部','无忧工业园');
insert into tb_dept value(DEFAULT,'财务部','无忧工业园');
create table tb_customer(
id int(11) PRIMARY key auto_increment,
name varchar(32) COMMENT '姓名',
username varchar(32) COMMENT '登录名',
password varchar(32) COMMENT '登录密码'
);
create table tb_address(
id int(11) PRIMARY key auto_increment,
province varchar(32) COMMENT '省',
city varchar(32) COMMENT '市',
address varchar(255) COMMENT '具体地址',
customer_id int(11) REFERENCES tb_customer(id)
);
insert into tb_customer values (DEFAULT,'张三','zhangsan','123');
insert into tb_customer values (DEFAULT,'李四','李四','123');
insert into tb_customer values (DEFAULT,'王五','wangwu','123');
insert into tb_address values(DEFAULT,"广东","广州市","越秀区",1)
insert into tb_address values(DEFAULT,"广东","广州市","越秀区",1)
insert into tb_address values(DEFAULT,"广东","广州市","天河区",2)
insert into tb_address values(DEFAULT,"广东","广州市","天河区",2)
insert into tb_address values(DEFAULT,"广东","广州市","白云区",3)
insert into tb_address values(DEFAULT,"广东","广州市","白云区",3)
Address实体类 没有在文档里面粘贴getter/setter和toString(),太占地方 需要读者自己编写(要加空构造器)
import java.io.Serializable;
import java.util.Objects;
public class Address implements Serializable {
private Integer id;
private String province;
private String city;
private String address;
private Customer customer;
}
Customer实体类 没有在文档里面粘贴getter/setter和toString(),太占地方 需要读者自己编写(要加空构造器)
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class Customer implements Serializable {
private Integer id;
private String name;
private String username;
private String password;
private List<Address> addressList;
}
案例实现功能:
查询全部地址,同时查询地址对应的客户数据
当存在调用和被调用关系时,按照正常编程习惯,都是先编写被调用方。
N+1次实现 当查询Customer表中N调数据时,需要编写1条查询全部的SQL,和N条根据外键列值作为另一张表主键查询条件的N条SQL语句。
被调用方 客户
创建接口com.bjsxt.mapper.CustomerMapper
package com.xu.mapper;
import com.xu.pojo.Customer;
import java.util.List;
/**
* 客户访问接口
*/
public interface CustomerMapper {
Customer selectAllById();
}
在包com.xu.mappers下创建CustomerMapper.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.xu.mapper.CustomerMapper">
<select id="selectById" resultType="Customer">
select id,name,username,password from tb_customer where id =#{id}
</select>
</mapper>
创建接口com.bjsxt.mapper.AddressMapper
import com.xu.pojo.Address;
import java.util.List;
/**
* 地址数据访问接口
*/
public interface AddressMapper {
List<Address> selectAll();
}
在包com.xu.mapper下创建.AddressMapper.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.xu.mapper.AddressMapper">
<resultMap id="addMap" type="Address">
<id column="id" property="id"></id>
<result column="province" property="province"/>
<result column="city" property="city"/>
<result column="address" property="address"/>
<!--非集合类型的引用类型 customer,属性类型是Customer
property -非集合类型引用类型属性名
javaType -非集合类型引用类型的具体类型 可以是使用包名.类名 或者别名
select - 这个属性的值用哪个SQL去查询 赋值规则是 namespace.select标签id
column - 当前resultMap使用在哪一个select标签上,那么使用这个标签中的SQL查询结果的哪一个字段的值作为参数
调用select属性对应的SQL。
如果只需要一个参数,则直接写查询SQL中的字段名
如果select属性对应的SQL需要多个参数(包括一个),则使用语法{占位变量名:查询SQL的字段名} 类似json格式
-->
<association property="customer" javaType="Customer"
select="com.xu.mapper.CustomerMapper.selectById"
column="{id:customer_id}">
</association>
</resultMap>
<select id="selectAll" resultType="addMap">
select id,province,city,address,costomer_id from tb_address
</select>
一次实现
接口com.bjsxt.mapper.AddressMapper 添加方法
List<Address> selectAll2();
AddressMapper.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.xu.mapper.AddressMapper">
<!--执行处理流程
1.执行sql
2.按照resultMap中的id和result子标签,封装Address类型对象,并给普通属性赋值
3.按照association中的id和子标签 封装Customer类型对象,并给普通属性赋值
-->
<resultMap id="addMap1" type="Address">
<id column="id" property="id"></id>
<result column="province" property="province"/>
<result column="city" property="city"/>
<result column="address" property="address"/>
<!-- 如果用于描述的非集合类型引用类型属性,且这个这个属性的值,是通过多表联合查询的到的结果
配置时,不需要提供select和column属性,需要增加子标签.-->
<association property="customer" javaType="Customer">
<id column="cusId" property="id"></id>
<result column="name" property="name"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
</association>
</resultMap>
<!-- 一次查询,是使用关联查询实现。也就是多表联合查询实现。
一条SQL查出一切需要的数据
-->
<select id="selectAll2" resultMap="">
select addr.id,addr.province,addr.city,addr.address,cus.id as cusId,cus.name,cus.password from tb_address as addr left join tb_customer as cus on addr.customer_id = cus.id
</select>
</mapper>
查询全部客户,同时查询客户对应的地址集合
一次查询实现
CustomerMapper接口添加方法
List<Customer> selectAll2();
配置CustomerMapper.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.xu.mapper.CustomerMapper">
<resultMap id="cusMap" type="Customer">
<id column="cusId" property="id"></id>
<result column="name" property="name"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<collection property="addressList" javaType="java.util.ArrayList" ofType="Address">
<!-- 从数据库查询出来的每行数据对的哪个属性 -->
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="address" property="address"></result>
</collection>
</resultMap>
<select id="selectAll2" resultMap="cusMap1">
select cus.id as cusId,cus.name,cus.username,cus.password,addr.id,addr.city,addr.address from
tb_customer as cus left join tb_address as addr on cus.id = addr.customer_id
</select>
</mapper>