场景模型:

用户订单模型,从查询订单信息出发,认为是一对多模型

关键解析:

一对多关联查询主要用到了mybatis提供的标签
collection标签表示一对多关联查询映射
该标签主要有如下属性:

  1. property(常用):映射到列结果的字段或属性。
  2. column:数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
  3. javaType(常用):一个 Java 类的完全限定名,或一个类型别名。 如果你映射到一个 JavaBeanMyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
  4. jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
  5. ofType(常用):用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。
  6. select:用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
  7. resultMap(常用):结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 将会将包含重复或部分数据重复的结果集。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许你 “串联”结果映射,以便解决嵌套结果集的问题。
  8. typeHandler:类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
  9. notNullColumn:默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。
  10. columnPrefix(常用):当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。
  11. resultSet:这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
  12. foreignColumn:指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
  13. autoMapping:如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select resultMap 元素使用。默认值:未设置(unset)。
  14. fetchType:可选的。有效值为 lazy eager 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

这里我们可以看到,collection属性相比association属性大部分都相同,collection属性新增了一个ofType属性这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。
注:常用字段已经标注
部分内容来自mybatis官方文档之XML解析器篇https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

代码案例:

这里的代码使用了两种方法来写,一种方式是将级联查询拼接成关联查询,一种方式是先去查主表,然后根据主表的数据再去查子表。分次查询可以配置延迟加载,见延迟加载篇。

一、一次查询

用户pojo

  1. @Data
  2. public class User {
  3. private Integer id;
  4. private String name;
  5. }

订单pojo

  1. @Data
  2. public class Order {
  3. private Integer id;
  4. private Integer userId;
  5. private String desc;
  6. }

用户扩展pojo

  1. @Data
  2. public class UserExt extends User {
  3. List<Order> orders;
  4. @Override
  5. public String toString() {
  6. return "UserExt{" +
  7. "orders=" + orders +
  8. "} " + super.toString();
  9. }
  10. }

用户mapper

  1. @Mapper
  2. public interface UserMapper {
  3. UserExt query(Integer id);
  4. }

用户mapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.example.kkbstudy.mapper.UserMapper">
  4. <resultMap id="orderMap" type="com.example.kkbstudy.pojo.Order">
  5. <result property="id" column="id" jdbcType="INTEGER"/>
  6. <result property="userId" column="user_id" jdbcType="INTEGER"/>
  7. <result property="desc" column="desc" jdbcType="VARCHAR"/>
  8. </resultMap>
  9. <resultMap id="userMap" type="com.example.kkbstudy.pojo.User">
  10. <result property="id" column="id" jdbcType="INTEGER"/>
  11. <result property="name" column="name" jdbcType="VARCHAR"/>
  12. </resultMap>
  13. <resultMap id="userExtMap" type="com.example.kkbstudy.pojo.UserExt" extends="userMap">
  14. <!-- 这里使用resultMap 可以不指定ofType,因为我们已经在resultMap指定过了 -->
  15. <collection property="orders" resultMap="orderMap">
  16. </collection>
  17. </resultMap>
  18. <select id="query" resultMap="userExtMap">
  19. select `user`.id,`user`.name,`order`.id,`order`.user_id,`order`.desc from `user`
  20. left join `order` on `order`.user_id = `user`.id
  21. where `user`.id = #{id}
  22. </select>
  23. </mapper>

编写单元测试

  1. @SpringBootTest
  2. class KkbStudyApplicationTests {
  3. @Autowired
  4. private UserMapper userMapper;
  5. @Test
  6. void test2() {
  7. UserExt query = userMapper.query(2);
  8. System.err.println(query);
  9. }
  10. }

输出

  1. 2021-05-21 11:09:00.694 DEBUG 17768 --- [ main] c.e.kkbstudy.mapper.UserMapper.query : ==> Preparing: select `user`.id,`user`.name,`order`.id,`order`.user_id,`order`.desc from `user` left join `order` on `order`.user_id = `user`.id where `user`.id = ?
  2. 2021-05-21 11:09:00.723 DEBUG 17768 --- [ main] c.e.kkbstudy.mapper.UserMapper.query : ==> Parameters: 2(Integer)
  3. 2021-05-21 11:09:00.744 DEBUG 17768 --- [ main] c.e.kkbstudy.mapper.UserMapper.query : <== Total: 2
  4. UserExt{orders=[Order(id=2, userId=2, desc=第一笔订单), Order(id=2, userId=2, desc=第二笔订单)]} User(id=2, name=李四)

可以看到,这里只查询了一次数据库,并且用户对象和订单列表已经自动封装至用户扩展pojo里。

二、分次查询

分词查询与一次查询的pojo类不变,区别点在于,查询主表的时候不用写关联查询了,只需要写主表的单表查询即可。

用户mapper

  1. @Mapper
  2. public interface UserMapper {
  3. UserExt query2(Integer id);
  4. }

用户mapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.example.kkbstudy.mapper.UserMapper">
  4. <resultMap id="userMap" type="com.example.kkbstudy.pojo.User">
  5. <result property="id" column="id" jdbcType="INTEGER"/>
  6. <result property="name" column="name" jdbcType="VARCHAR"/>
  7. </resultMap>
  8. <resultMap id="userExtMap2" type="com.example.kkbstudy.pojo.UserExt" extends="userMap">
  9. <collection property="orders" column="id" ofType="com.example.kkbstudy.pojo.Order"
  10. select="com.example.kkbstudy.mapper.OrderMapper.simpleQuery">
  11. </collection>
  12. </resultMap>
  13. <select id="query2" resultMap="userExtMap2">
  14. select `user`.id,`user`.name from `user`where `user`.id = #{id}
  15. </select>
  16. </mapper>

订单Mapper

  1. @Mapper
  2. public interface OrderMapper {
  3. List<Order> simpleQuery(Integer id);
  4. }

订单Mapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.example.kkbstudy.mapper.OrderMapper">
  4. <resultMap id="orderMap" type="com.example.kkbstudy.pojo.Order">
  5. <result property="id" column="id" jdbcType="INTEGER"/>
  6. <result property="userId" column="user_id" jdbcType="INTEGER"/>
  7. <result property="desc" column="desc" jdbcType="VARCHAR"/>
  8. </resultMap>
  9. <select id="simpleQuery" resultMap="orderMap">
  10. select id,user_id,`desc` from `order` where user_id = #{id}
  11. </select>
  12. </mapper>

编写单元测试

  1. @SpringBootTest
  2. class KkbStudyApplicationTests {
  3. @Test
  4. void test3() {
  5. UserExt query = userMapper.query2(2);
  6. System.err.println(query);
  7. }
  8. }

输出

  1. 2021-05-21 11:07:30.268 DEBUG 10232 --- [ main] c.e.kkbstudy.mapper.UserMapper.query2 : ==> Preparing: select `user`.id,`user`.name from `user`where `user`.id = ?
  2. 2021-05-21 11:07:30.302 DEBUG 10232 --- [ main] c.e.kkbstudy.mapper.UserMapper.query2 : ==> Parameters: 2(Integer)
  3. 2021-05-21 11:07:30.331 DEBUG 10232 --- [ main] c.e.k.mapper.OrderMapper.simpleQuery : ====> Preparing: select id,user_id,`desc` from `order` where user_id = ?
  4. 2021-05-21 11:07:30.332 DEBUG 10232 --- [ main] c.e.k.mapper.OrderMapper.simpleQuery : ====> Parameters: 2(Integer)
  5. 2021-05-21 11:07:30.335 DEBUG 10232 --- [ main] c.e.k.mapper.OrderMapper.simpleQuery : <==== Total: 2
  6. 2021-05-21 11:07:30.339 DEBUG 10232 --- [ main] c.e.kkbstudy.mapper.UserMapper.query2 : <== Total: 1
  7. UserExt{orders=[Order(id=1, userId=2, desc=第一笔订单), Order(id=2, userId=2, desc=第二笔订单)]} User(id=2, name=李四)

可以看到这里查询了两次数据库,最终组装成了我们的UserExt。