MyBatis多表映射

1 多表映射概念

1.1 多表查询结果映射思路

MyBatis 思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序查询需求,那就太好了,而 ResultMap 就是 MyBatis 就是完美答案。

官方例子:我们如何映射下面这个语句?

   <!-- 非常复杂的语句 -->
   <select id="selectBlogDetails" resultMap="detailedBlogResultMap">
     select
          B.id as blog_id,
          B.title as blog_title,
          B.author_id as blog_author_id,
          A.id as author_id,
          A.username as author_username,
          A.password as author_password,
          A.email as author_email,
          A.bio as author_bio,
          A.favourite_section as author_favourite_section,
          P.id as post_id,
          P.blog_id as post_blog_id,
          P.author_id as post_author_id,
          P.created_on as post_created_on,
          P.section as post_section,
          P.subject as post_subject,
          P.draft as draft,
          P.body as post_body,
          C.id as comment_id,
          C.post_id as comment_post_id,
          C.name as comment_name,
          C.comment as comment_text,
          T.id as tag_id,
          T.name as tag_name
     from Blog B
          left outer join Author A on B.author_id = A.id
          left outer join Post P on B.id = P.blog_id
          left outer join Comment C on P.id = C.post_id
          left outer join Post_Tag PT on PT.post_id = P.id
          left outer join Tag T on PT.tag_id = T.id
     where B.id = #{id}
   </select>

你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 虽然它看起来令人望而生畏,但其实非常简单。

   <!-- 非常复杂的结果映射 -->
   <resultMap id="detailedBlogResultMap" type="Blog">
     <constructor>
       <idArg column="blog_id" javaType="int"/>
     </constructor>
     <result property="title" column="blog_title"/>
     <association property="author" javaType="Author">
       <id property="id" column="author_id"/>
       <result property="username" column="author_username"/>
       <result property="password" column="author_password"/>
       <result property="email" column="author_email"/>
       <result property="bio" column="author_bio"/>
       <result property="favouriteSection" column="author_favourite_section"/>
     </association>
     <collection property="posts" ofType="Post">
       <id property="id" column="post_id"/>
       <result property="subject" column="post_subject"/>
       <association property="author" javaType="Author"/>
       <collection property="comments" ofType="Comment">
         <id property="id" column="comment_id"/>
       </collection>
       <collection property="tags" ofType="Tag" >
         <id property="id" column="tag_id"/>
       </collection>
     </collection>
   </resultMap>

你现在可能看不懂,接下来我们要学习将多表查询结果使用ResultMap标签映射到实体类对象上!

2 实体类设计方案

多表关系回顾:(双向查看)

  • 一对一

    夫妻关系,人和身份证号

  • 一对多| 多对一

    用户和用户的订单,锁和钥匙

  • 多对多

    老师和学生,部门和员工
    实体类设计关系(查询):(单向查看)

  • 对一 : 夫妻一方对应另一方,订单对应用户都是对一关系

    实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!

    例如:

  public class Customer {
  
    private Integer customerId;
    private String customerName;
  
  }
  
  public class Order {
  
    private Integer orderId;
    private String orderName;
    private Customer customer;// 体现的是对一的关系
  
  }  
  
  • 对多: 用户对应的订单,讲师对应的学生或者学生对应的讲师都是对多关系:

    实体类设计:对多关系下,类中只要包含对方类型集合属性即可!

  public class Customer {
  
    private Integer customerId;
    private String customerName;
    private List<Order> orderList;// 体现的是对多的关系
  }
  
  public class Order {
  
    private Integer orderId;
    private String orderName;
    private Customer customer;// 体现的是对一的关系
    
  }
  
  //查询客户和客户对应的订单集合  不要管!

多表结果实体类设计小技巧:

对一,属性中包含对方对象

对多,属性中包含对方对象集合

只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!

无论多少张表联查,实体类设计都是两两考虑!

在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和对应的客户,就不要关注客户中的订单集合!

3 多表映射案例准备

数据库:

CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );

CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) ); 

INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');

INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1'); 

实际开发时,一般在开发过程中,不给数据库表设置外键约束。
原因是避免调试不方便。
一般是功能开发完成,再加外键约束检查是否有bug。

实体类设计:

稍后会进行订单关联客户查询,也会进行客户关联订单查询,所以在这先练习设计

@Data
public class Customer {

  private Integer customerId;
  private String customerName;
  private List<Order> orderList;// 体现的是对多的关系
  
}  

@Data
public class Order {
  private Integer orderId;
  private String orderName;
  private Customer customer;// 体现的是对一的关系
  
}  

4 对一映射

4.1 需求说明

根据ID查询订单,以及订单关联的用户的信息!

4.2 OrderMapper接口

   public interface OrderMapper {
     Order selectOrderWithCustomer(Integer orderId);
   }

4.3 OrderMapper.xml配置文件

   <!-- 创建resultMap实现“对一”关联关系映射 -->
   <!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
   <!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
   <resultMap id="selectOrderWithCustomerResultMap" type="order">
   
     <!-- 先设置Order自身属性和字段的对应关系 -->
     <id column="order_id" property="orderId"/>
   
     <result column="order_name" property="orderName"/>
   
     <!-- 使用association标签配置“对一”关联关系 -->
     <!-- property属性:在Order类中对一的一端进行引用时使用的属性名 -->
     <!-- javaType属性:一的一端类的全类名 -->
     <association property="customer" javaType="customer">
   
       <!-- 配置Customer类的属性和字段名之间的对应关系 -->
       <id column="customer_id" property="customerId"/>
       <result column="customer_name" property="customerName"/>
   
     </association>
   
   </resultMap>
   
   <!-- Order selectOrderWithCustomer(Integer orderId); -->
   <select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">
   
     SELECT order_id,order_name,c.customer_id,customer_name
     FROM t_order o
     LEFT JOIN t_customer c
     ON o.customer_id=c.customer_id
     WHERE o.order_id=#{orderId}
   
   </select>

4.4 Mybatis全局注册Mapper文件

   <!-- 注册Mapper配置文件:告诉Mybatis我们的Mapper配置文件的位置 -->
   <mappers>
   
     <!-- 在mapper标签的resource属性中指定Mapper配置文件以“类路径根目录”为基准的相对路径 -->
     <mapper resource="mappers/OrderMapper.xml"/>
   
   </mappers>

4.5 junit测试程序

   @Slf4j
   public class MyBatisTest {
   
       private SqlSession session;
       // junit会在每一个@Test方法前执行@BeforeEach方法
   
       @BeforeEach
       public void init() throws IOException {
           session = new SqlSessionFactoryBuilder()
                   .build(
                           Resources.getResourceAsStream("mybatis-config.xml"))
                   .openSession();
       }
   
       @Test
       public void testRelationshipToOne() {
       
         OrderMapper orderMapper = session.getMapper(OrderMapper.class);
         // 查询Order对象,检查是否同时查询了关联的Customer对象
         Order order = orderMapper.selectOrderWithCustomer(2);
         log.info("order = " + order);
       
       }
   
       // junit会在每一个@Test方法后执行@@AfterEach方法
       @AfterEach
       public void clear() {
           session.commit();
           session.close();
       }
   }

4.6 关键词

在“对一”关联关系中,我们的配置比较多,但是关键词就只有:associationjavaType

5 对多映射

5.1 需求说明

查询客户和客户关联的订单信息!

5.2 CustomerMapper接口

   public interface CustomerMapper {
   
     Customer selectCustomerWithOrderList(Integer customerId);
   
   }

5.3 CustomerMapper.xml文件

  <!-- 配置resultMap实现从CustomerOrderList的“对多”关联关系 -->
   <resultMap id="selectCustomerWithOrderListResultMap"
   
     type="customer">
   
     <!-- 映射Customer本身的属性 -->
     <id column="customer_id" property="customerId"/>
   
     <result column="customer_name" property="customerName"/>
   
     <!-- collection标签:映射“对多”的关联关系 -->
     <!-- property属性:在Customer类中,关联“多”的一端的属性名 -->
     <!-- ofType属性:集合属性中元素的类型 -->
     <collection property="orderList" ofType="order">
   
       <!-- 映射Order的属性 -->
       <id column="order_id" property="orderId"/>
   
       <result column="order_name" property="orderName"/>
   
     </collection>
   
   </resultMap>
   
   <!-- Customer selectCustomerWithOrderList(Integer customerId); -->
   <select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">
     SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
     FROM t_customer c
     LEFT JOIN t_order o
     ON c.customer_id=o.customer_id
     WHERE c.customer_id=#{customerId}
   </select>

5.4 Mybatis全局注册Mapper文件

   <!-- 注册Mapper配置文件:告诉Mybatis我们的Mapper配置文件的位置 -->
   <mappers>
     <!-- 在mapper标签的resource属性中指定Mapper配置文件以“类路径根目录”为基准的相对路径 -->
     <mapper resource="mappers/OrderMapper.xml"/>
     <mapper resource="mappers/CustomerMapper.xml"/>
   </mappers>

5.5 junit测试程序

   @Test
   public void testRelationshipToMulti() {
   
     CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);
     // 查询Customer对象同时将关联的Order集合查询出来
     Customer customer = customerMapper.selectCustomerWithOrderList(1);
     log.info("customer.getCustomerId() = " + customer.getCustomerId());
     log.info("customer.getCustomerName() = " + customer.getCustomerName());
     List<Order> orderList = customer.getOrderList();
     for (Order order : orderList) {
       log.info("order = " + order);
     }
   }

5.6 关键词

在“对多”关联关系中,同样有很多配置,但是提炼出来最关键的就是:collectionofType

4 多表映射总结

4.1 多表映射优化

setting属性属性含义可选值默认值
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL

我们可以将autoMappingBehavior设置为full,进行多表resultMap映射的时候,可以省略符合列和属性命名映射规则(列名=属性名,或者开启驼峰映射也可以自定映射)的result标签!

修改mybati-sconfig.xml:

<!--开启resultMap自动映射 -->
<setting name="autoMappingBehavior" value="FULL"/>

修改teacherMapper.xml

<resultMap id="teacherMap" type="teacher">
    <id property="tId" column="t_id" />
    <!-- 开启自动映射,并且开启驼峰式支持!可以省略 result!-->
<!--        <result property="tName" column="t_name" />-->
    <collection property="students" ofType="student" >
        <id property="sId" column="s_id" />
<!--            <result property="sName" column="s_name" />-->
    </collection>
</resultMap>

4.2 多表映射总结

关联关系配置项关键词所在配置文件和具体位置
对一association标签/javaType属性/property属性Mapper配置文件中的resultMap标签内
对多collection标签/ofType属性/property属性Mapper配置文件中的resultMap标签内
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值