mybatis之多表关联查询和懒查询

1,586 阅读8分钟

前言

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');

表关系分析 创建后的表为:

表关系.jpg

简单来说,

  • 一个顾客可以有多个订单,所以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>

(一)一对一查询

第一种:关联查询

为了方便我直接在一个模块里进行一对一和一对多关联查询,先看一下我的目录结构,对要创建的相关文件有一个了解,画框框的为一对一查询。,其余的为一对多查询。 一对一.jpg

目标

根据订单ID查询出订单信息,并且查询出该订单所属的顾客信息,将查询到的结果集封装到Order对象中,所以要有一个order和customer类,将客户信息转成customer对象,然后封装到Order对象中。

一对一查询结果.jpg

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属性。这种情况下避免了资源浪费,在查询某些字段的值的时候不用每次都查询两张表。

我这里还是将一对一和一对多查询放在一个模块下,但是建议分开放,思路相对会清晰一点,框住的还是一对一查询需要创建的表。

分布查询.jpg 可以对比一对一关联查询,分布查询多了接口的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>

说明:

  1. ①第二步只需要查询根据customer_id查询出顾客信息,我们重点放在如何在OrderMapper.xml中将第二步查询到的信息封装给Order的customer属性。
  2. ②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
  1. ofType**,表示POJO中要进行一对多映射的那个属性的泛型的全限定名
  2. 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>

注意:

  1. ①第二步只需要查询根据customer_id查询出订单信息,其余同一对多的关联查询
  2. ②CustomerMapper.xml中:一对多的手动映射是collection
  • select属性:表示调用第二步查询,获取查询结果 ,要写第二步的全限定名
  • column属性:表示将本次查询到的结果集中的哪个字段传给第二步查询,根据customer_id查询订单信息。
  • fetchType="lazy"表示使用懒查询,也就是分布查询。