三、mybatis多表关联查询和分布查询
前言
mybatis多表关联查询和懒查询,这篇文章通过一对一和一对多的实例来展示多表查询。不过需要掌握数据输出的这方面的知识。之前整理过了mybatis入门案例和mybatis数据输出,多表查询是在前面的基础上完成的。如果不熟练的先回去巩固一下。
准备工作
这里先将两个查询要完成的共同步骤先完成
1.物理建模
创建两个表,一个customer表,一个order表。
CREATE TABLE `t_customer` (
`customer_id` INT NOT NULL AUTO_INCREMENT,
`customer_name` VARCHAR(100),
PRIMARY KEY (`customer_id`)
);
CREATE TABLE `t_order` (
`order_id` INT NOT NULL AUTO_INCREMENT,
`order_name` VARCHAR(100),
`customer_id` INT,
PRIMARY KEY (`order_id`)
);
INSERT INTO `t_customer` (`customer_name`) VALUES ('tom');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('笔记本', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('电脑', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('桌子', '1');
表关系分析
创建后的表为:
简单来说,
- 一个顾客可以有多个订单,所以
t_customer
表和t_order
表示一对多关系 - 一个订单对应一个客户或者多个订单对应一个客户,所以
t_order
表和t_customer
表可以看成一对一或者多对一关系
2.引入依赖
在pom.xml中引入相关依赖,并将log4j的配置文件复制到resources路径下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>day02-mybatis02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
<scope>runtime</scope>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
3.全局配置文件
这里设置驼峰映射,别名配置,环境配置和路径映射,别名和路径用的是包扫描,因此在映射配置文件中做相应的修改即可。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--驼峰映射-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--类型别名配置-->
<typeAliases>
<!-- <typeAlias type="pojo.Employee" alias="employee"></typeAlias>-->
<!--
采用包扫描的方式,一次性对某个包中的所有类配置别名,每个类的别名就是它的类名,不区分大小写
-->
<package name="pojo"/>
</typeAliases>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--
dataSource:数据源
1. POOLED 表示使用内置连接池
2. UNPOOLED 表示不使用连接池
3. JNDI
-->
<property name="username" value="root"></property>
<property name="password" value="888888"></property>
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--resource=映射路径-->
<!-- <mapper resource="mappers/EmployeeMapper.xml"/>-->
<package name="mappers"/>
</mappers>
</configuration>
(一)一对一查询
第一种:关联查询
为了方便我直接在一个模块里进行一对一和一对多关联查询,先看一下我的目录结构,对要创建的相关文件有一个了解,画框框的为一对一查询。,其余的为一对多查询。
目标
根据订单ID查询出订单信息,并且查询出该订单所属的顾客信息,将查询到的结果集封装到Order对象中,所以要有一个order和customer类,将客户信息转成customer对象,然后封装到Order对象中。
1、逻辑建模
在pojo类下建order和customer,要注意的是,因为我们的目标是要根据订单Id查询出订单信息和顾客信息,而订单信息中有一个cutomer_id是和顾客表关联的,查询出来的是一条信息,所以我们在order类中要声明属性customer,将客户信息转成customer对象,封装到订单中。
package pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
private Integer customerId;
private String customerName;
}
package pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Integer orderId;
private String orderName;
//表示Order和Customer的对一关系
private Customer customer;
}
2、创建持久层接口
因为在全局文件中配置的映射路径是包扫描<package name="mappers"/>
,所以持久层接口在建在mappers包下
package mappers;
import pojo.Order;
public interface OrderMapper {
/*根据orderId查询订单信息并且查询该订单的顾客信息查询出来,结果集封装到Order对象中*/
Order selectOrderWithCustomerByOrderId(Integer orderId);
}
3、创建映射配置文件
映射配置文件名字和在resources下的位置要与接口一致。
<?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="mappers.OrderMapper">
<!--
手动映射,
autoMapping属性:对于可以自动映射的字段进行自动映射
-->
<resultMap id="OrderMap" type="Order" autoMapping="true">
<!--
association标签进行一对一映射,类型是javaType
property属性:表示要对POJO的哪个属性进行一对一映射
javaType属性:表示要进行一对一映射的那个属性的类型(全限定名)
-->
<association property="customer" javaType="Customer" autoMapping="true"></association>
</resultMap>
<select id="selectOrderWithCustomerByOrderId" resultMap="OrderMap">
select * from t_order `to`,t_customer tc where `to`.customer_id=tc.customer_id and `to`.order_id=#{orderId}
</select>
</mapper>
这里有三点需要注意:
- ①多表查询都需要用到手动映射,之前的数据输出是resulType,手动映射的数据输出是resultMap,在这里设置
autoMapping=ture
,表示能自动映射的自动映射,就不必要写id,result属性。 - ②在手动映射中,association标签进行一对一映射,类型是javaType,javaType写要封装的类型(这里注意要和一对多查询区分开)
- ③select标签,要引用前面写的手动映射,准备sql语句。
4、测试程序
import mappers.OrderMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
public class Test1v1 {
private OrderMapper orderMapper ;
private InputStream is;
private SqlSession sqlSession;
@Before
public void init() throws Exception{
//目标:获取EmployeeMapper接口的代理对象,并且使用该对象调用selectEmployee(1)方法,然后返回Employee对象
//1. 将全局配置文件转成字节输入流
is = Resources.getResourceAsStream("mybatis-config.xml");
//2. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3. 使用构建者模式创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//4. 使用工厂模式创建一个SqlSession对象
sqlSession = sqlSessionFactory.openSession();
//5. 使用动态代理模式,创建EmployeeMapper接口的代理对象
orderMapper = sqlSession.getMapper(OrderMapper.class);
}
@After
public void after() throws Exception{
//提交事务!!!
sqlSession.commit();
//7. 关闭资源
is.close();
sqlSession.close();
}
@Test
public void testSelectOrderWithCustomerByOrderId(){
System.out.println(orderMapper.selectOrderWithCustomerByOrderId(2));
}
}
第二种:分布查询(懒查询)
分布查询则需要查询分布两张表,将第二步查询到的结果赋值给Order对象的customer属性。这种情况下避免了资源浪费,在查询某些字段的值的时候不用每次都查询两张表。
我这里还是将一对一和一对多查询放在一个模块下,但是建议分开放,思路相对会清晰一点,框住的还是一对一查询需要创建的表。
可以对比一对一关联查询,分布查询多了接口的CustomerMapper以及映射文件的CustomerMapper.xml文件。
目标
- 第一步:根据order_id查询出订单信息,得到customer_id
- 第二步:根据customer_id查询出顾客信息
- 第三步:将第二步查询到的结果赋值给Order对象的customer属性
1.逻辑建模
和一对一关联查询一样
2.创建持久性接口
- OrderMapper接口,根据order_id查询出订单信息:
public interface OrderMapper {
Order selectOrderByOrderId(Integer orderId);
}
- CustomerMapper接口,根据customer_id查询出顾客信息:
public interface CustomerMapper {
Customer selectCustomerByCustomerId(Integer customerId);
}
3.创建映射配置文件
OrderMapper.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="mapper.OrderMapper">
<resultMap id="OrderWithCustomerMap" type="order" autoMapping="true">
<!--
将第二步查询到的结果赋值给Order对象的customer属性
select属性:表示调用第二步查询,获取查询结果
column属性:表示将本次查询到的结果集中的哪个字段传给第二步查询
-->
<association property="customer" javaType="Customer"
autoMapping="true" column="customer_id"
select="mapper.CustomerMapper.selectCustomerByCustomerId"
fetchType="lazy"
></association>
</resultMap>
<!--第一步查询-->
<select id="selectOrderByOrderId" resultMap="OrderWithCustomerMap">
SELECT * FROM t_order WHERE order_id =#{orderId};
</select>
</mapper>
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="mapper.CustomerMapper">
<select id="selectCustomerByCustomerId" resultType="customer">
SELECT * FROM t_customer WHERE customer_id=#{customerId};
</select>
</mapper>
说明:
- ①第二步只需要查询根据customer_id查询出顾客信息,我们重点放在如何在OrderMapper.xml中将第二步查询到的信息封装给Order的customer属性。
- ②OrderMapper.xml中:我们知道,只要涉及多表查询,我们都必须设置手动映射,而一对一的手动映射是association
- select属性:表示调用第二步查询,获取查询结果 ,要写第二步的全限定名
- column属性:表示将本次查询到的结果集中的哪个字段传给第二步查询,根据customer_id查询顾客信息。
- fetchType="lazy"表示使用懒查询,也就是分布查询。
4.测试程序
import mapper.OrderMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
public class Test1v1 {
private OrderMapper orderMapper ;
private InputStream is;
private SqlSession sqlSession;
@Before
public void init() throws Exception{
//目标:获取EmployeeMapper接口的代理对象,并且使用该对象调用selectEmployee(1)方法,然后返回Employee对象
//1. 将全局配置文件转成字节输入流
is = Resources.getResourceAsStream("mybatisConfig.xml");
//2. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3. 使用构建者模式创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//4. 使用工厂模式创建一个SqlSession对象
sqlSession = sqlSessionFactory.openSession();
//5. 使用动态代理模式,创建EmployeeMapper接口的代理对象
orderMapper = sqlSession.getMapper(OrderMapper.class);
}
@After
public void after() throws Exception{
//提交事务!!!
sqlSession.commit();
//7. 关闭资源
is.close();
sqlSession.close();
}
@Test
public void testSelectOrderByOrderId(){
System.out.println(orderMapper.selectOrderByOrderId(1));
}
}
(二)一对多查询
第一种 关联查询
目标
根据顾客id查询顾客信息和订单信息
1.逻辑建模
一个顾客对应多个订单,查询出来的有多条数据。所以需要在顾客中声明一个泛型为Order的集合:
- Customer1
package pojo;
import java.util.List;
public class Customer1 {
private Integer customerId;
private String customerName;
private List<Order1> orderList;
@Override
public String toString() {
return "Customer1{" +
"customerId=" + customerId +
", customerName='" + customerName + ''' +
", orderList=" + orderList +
'}';
}
}
- Order1
package pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order1 {
private Integer orderId;
private String orderName;
}
2. 创建持久性接口
package mappers;
import pojo.Customer1;
public interface CustomerMapper {
Customer1 selectCustomerWithOrderList(Integer customerId);
}
3. 编写配置文件
<?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="mappers.CustomerMapper">
<!--
一对多映射:collection标签
property属性表示要对POJO的哪个属性进行一对多映射
ofType属性表示POJO中要进行一对多映射的那个属性的泛型的全限定名
-->
<resultMap id="customerMap" type="Customer1" autoMapping="true">
<!--这里不能省略-->
<id column="customer_id" property="customerId"></id>
<result column="customer_name" property="customerName"></result>
<collection property="orderList" ofType="Order1" autoMapping="true"></collection>
</resultMap>
<select id="selectCustomerWithOrderList" resultMap="customerMap">
select * from t_order `to`,t_customer tc where `to`.customer_id=tc.customer_id and tc.customer_id=#{customerId}
</select>
</mapper>
注意:
- ①在手动映射中,collection标签进行一对多映射(一对一是association,javaType)
- ofType**,表示POJO中要进行一对多映射的那个属性的泛型的全限定名
- property属性表示要对POJO的哪个属性进行一对多映射。
- ②这里和一对一查询不同还有:手动映射中的id是不可以省略的,
因为我们查询时结果有多行,自动映射先看方法的返回值,返回值是Customer1,底层会调用一个对象方法selectOne,多条数据底层调用的是selectList方法,根据接口的方法可以看到返回值是一个对象,只有一条数据,调用selectOne方法,而我们查询出来的有多条,所以手动映射的id,result一定要加上。
但是注意:如果我们没有设置自动映射的情况下,result属性一定要写,不然只能打印一个空值。
4.测试程序
import mappers.CustomerMapper;
import mappers.OrderMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
public class Test1vn {
private CustomerMapper customerMapper ;
private InputStream is;
private SqlSession sqlSession;
@Before
public void init() throws Exception{
//目标:获取EmployeeMapper接口的代理对象,并且使用该对象调用selectEmployee(1)方法,然后返回Employee对象
//1. 将全局配置文件转成字节输入流
is = Resources.getResourceAsStream("mybatis-config.xml");
//2. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3. 使用构建者模式创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//4. 使用工厂模式创建一个SqlSession对象
sqlSession = sqlSessionFactory.openSession();
//5. 使用动态代理模式,创建EmployeeMapper接口的代理对象
customerMapper = sqlSession.getMapper(CustomerMapper.class);
}
@After
public void after() throws Exception{
//提交事务!!!
sqlSession.commit();
//7. 关闭资源
is.close();
sqlSession.close();
}
@Test
public void testSelectCustomerWithOrderList(){
System.out.println(customerMapper.selectCustomerWithOrderList(1));
}
}
第二种 分布查询(懒查询)
目标
- 第一步:根据customer_id查询出顾客信息
- 第二步:根据customer_id查询出订单信息
- 第三步:将查询到的订单信息封装到orderList集合中
1.逻辑建模
和一对多关联查询一样
2.创建持久性接口
CustomerMapper1接口:
package mapper;
import pojo.Customer;
import pojo.Customer1;
public interface CustomerMapper1 {
Customer1 selectCustomer1ByCustomerId(Integer customerId);
}
OrderMapper1 接口:
package mapper;
import pojo.Order1;
import java.util.List;
public interface OrderMapper1 {
List<Order1> selectOrder1ByOrderId(Integer OrderId);
}
3. 映射配置文件
CustomerMapper1.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="mapper.CustomerMapper1">
<resultMap id="customerWithOrderMap" type="customer1" autoMapping="true">
<id column="customer_id" property="customerId" ></id>
<collection property="orderList1" ofType="Order1"
select="mapper.OrderMapper1.selectOrder1ByOrderId"
column="customer_id" fetchType="lazy"></collection>
</resultMap>
<select id="selectCustomer1ByCustomerId" resultMap="customerWithOrderMap">
SELECT * FROM t_customer WHERE customer_id = #{customerId};
</select>
</mapper>
OrderMapper1.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="mapper.OrderMapper1">
<select id="selectOrder1ByOrderId" resultType="order1">
SELECT * FROM t_order WHERE customer_id = #{customerId};
</select>
</mapper>
注意:
- ①第二步只需要查询根据customer_id查询出订单信息,其余同一对多的关联查询
- ②CustomerMapper.xml中:一对多的手动映射是collection
- select属性:表示调用第二步查询,获取查询结果 ,要写第二步的全限定名
- column属性:表示将本次查询到的结果集中的哪个字段传给第二步查询,根据customer_id查询订单信息。
- fetchType="lazy"表示使用懒查询,也就是分布查询。