Java EE之SSM框架整合开发 -- (7) MyBatis映射器

本章主要内容:核心配置文件、SQL映射文件、级联查询

前言

1.MyBatis实现查询时,返回的结果集有几种常见的存储方式?请举例说明。

答:可以使用Map存储,也可以使用POJO存储。

<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap"  resultType="map">
select * from user
</select>
<!-- 使用自定义结果集类型 -->
<resultMap type="com.pojo.MapUser" id="myResult">
<!-- property是com.pojo.MapUser类中的属性-->
<!-- column是查询结果的列名,可以来自不同的表 -->
<id property="m_uid" column="uid"/>
<result property="m_uname" column="uname"/>
<result property="m_usex" column="usex"/>
</resultMap>

2.在MyBatis中针对不同的数据库软件,<insert>元素如何将主键回填

答:MySQL、SQL Server等数据库的表格可以采用自动递增的字段作为主键。自动回填示例如下:

<!-- 添加一个用户,成功后将主键值回填给uid(po类的属性)-->
<insert id="addUser" parameterType="com.po.MyUser"
keyProperty="uid" useGeneratedKeys="true">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>

如果实际工程中使用的数据库不支持主键自动递增(如Oracle),或者取消了主键自动递增的规则时,可以使用MyBatis的<selectKey>元素来自定义生成主键。具体配置示例代码如下:

<insert id="insertUser" parameterType="com.po.MyUser">
<!-- 先使用selectKey元素定义主键,然后再定义SQL语句 -->
<selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
select if(max(uid) is null, 1 , max(uid)+1) as newUid from user
</selectKey>
insert into user (uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>

3.在MyBatis中,如何给SQL语句传递参数

答:1)传递一个基本数据参数,如:

<!-- 根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer"
resultType="com.po.MyUser">
select * from user where uid = #{uid}
</select>

2)使用Map接口传递多个参数,如:

<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser"  resultType="com.po.MyUser" parameterType="map">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>

上述SQL文件中参数名u_name和u_sex是Map的key。

3)使用Java Bean传递多个参数,如:

package com.pojo;
public class SeletUserParam {
private String u_name;
private String u_sex;
//此处省略setter和getter方法
}
<select id="selectAllUser"  resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">

select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>

7.1 MyBatis配置文件概述

MyBatis的核心配置文件配置了很多影响MyBatis行为的信息,这些信息通常只会配置在一个文件中,并且不会轻易改动。另外,与Spring框架整合后,MyBatis的核心配置文件信息将配置到Spring的配置文件中。因此,在实际开发中需要编写或修改MyBatis的核心配置文件的情况不多

MyBatis配置文件中大标签configuration下子标签包括:
configuration
|--- properties
|--- settings
|--- typeAliases
|--- typeHandlers
|--- objectFactory
|--- plugins
|--- environments
|--- |--- environment
|--- |--- |--- transactionManager
|--- |--- |__ dataSource
|__ mappers

这里不做过多赘述,欲知详情见文末链接文章。

7.2 MyBatis映射器概述

映射器是Mybatis中最复杂最重要的组件,由一个接口(Dao)加上XML(SQL映射文件)组成。映射器也可以使用注解完成,但是实际应用不多。因为注解不适合复杂SQL,可读性也差,并且没有XML文件的上下文互相引用功能。

7.3 <select>元素

在SQL映射文件中<select>元素用于映射SQL的select语句,其示例代码如下:

<!-- 根据uid查询一个用户信息 -->
  <select id="selectUserById" parameterType="Integer" 
    resultType="com.po.MyUser">
    select * from user where uid = #{uid}
  </select>

上述示例代码中,id的值是唯一标识符,它接收一个Integer类型的参数返回一个MyUser类型的对象结果集自动映射到MyUser属性

下图是<select>元素的常用属性:

7.3.1 使用Map接口传递多个参数

在实际开发中,查询SQL语句经常需要多个参数,比如多条件查询。多个参数传递时,<select>元素的parameterType属性值的类型是什么呢?在MyBatis中允许Map接口通过键值对传递多个参数。

假设数据操作接口中有个实现查询陈姓男性用户信息功能的方法:

public List<MyUser> selectAllUser(Map<String, Object> param);

此时,传递给映射器的是一个Map对象,使用它在SQL中设置对应的参数,对应SQL文件代码如下:

<!-- 查询陈姓男性用户信息 -->
  <select id="selectAllUser"  resultType="com.po.MyUser" parameterType="map">
    select * from user 
    where uname like concat('%',#{u_name},'%')
    and usex = #{u_sex}
  </select>

上述SQL文件中参数名u_name和u_sex是Map的key。

对应的UserController代码为:

//查询多个用户
Map<String, Object> map = new HashMap<>();
map.put("u_name", "陈");
map.put("u_sex", "男");
List<MyUser> list = userDao.selectAllUser(map);

7.3.2 使用Java Bean传递多个参数

首先,在ch7应用的src目录下,创建一个名为com.pojo的包,在包中创建一个POJO类SeletUserParam。

其次,将Dao接口中的selectAllUser方法修改为:

public List<MyUser> selectAllUser(SeletUserParam param);

再次,将com.mybatis包中的SQL映射文件UserMapper.xml中的“查询陈姓男性用户信息”代码修改为:

<!-- 查询陈姓男性用户信息 -->
  <select id="selectAllUser"  resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
    select * from user 
    where uname like concat('%',#{u_name},'%')
    and usex = #{u_sex}
  </select>

最后,将com.controller包中UserController的“查询多个用户”代码片段修改:

//查询多个用户
SeletUserParam su = new SeletUserParam();
su.setU_name("陈");
su.setU_sex("男");
List<MyUser> list = userDao.selectAllUser(su);
for (MyUser myUser : list) {
      System.out.println(myUser);
}

当然,实际应用中要看情况选择使用Map还是JavaBean来传递参数:如果参数较少,用Map比较方便。如果参数较多,建议使用JavaBean。

7.4 <insert>元素

<insert>元素用于映射插入语句,MyBatis执行完一条插入语句后,将返回一个整数表示其影响的行数。它的属性与<select>元素的属性大部分相同,在本节讲解它的几个特有属性。具体如下:

keyProperty:该属性的作用是将插入或更新操作时的返回值赋值给PO类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以在多个值之间用逗号隔开。

keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第一列时需要设置。如果是联合主键时,可以在多个值之间用逗号隔开。

useGeneratedKeys:该属性将使MyBatis使用JDBC的getGeneratedKeys()方法获取由数据库内部生产的主键,如MySQL、SQL Server等自动递增的字段,其默认值为false。

7.4.1 主键(自动递增)回填

MySQL、SQL Server等数据库的表格可以采用自动递增的字段作为主键。有时可能需要使用这个刚刚产生的主键,用以关联其他业务

映射文件写法:

<!-- 添加一个用户,成功后将主键值回填给uid(po类的属性),#{uname}为com.po.MyUser的属性值-->
  <insert id="addUser" parameterType="com.po.MyUser" 
  keyProperty="uid" useGeneratedKeys="true">
    insert into user (uname,usex) values(#{uname},#{usex})
  </insert>

在Controller类中的调用:

//添加一个用户
    MyUser addmu = new MyUser();
    addmu.setUname("陈恒");
    addmu.setUsex("男");
    int add = userDao.addUser(addmu);
    System.out.println("添加了" + add + "条记录");
    System.out.println("添加记录的主键是" + addmu.getUid());

7.4.2 自定义主键

如果实际工程中使用的数据库不支持主键自动递增(如Oracle),或者取消了主键自动递增的规则时,可以使用MyBatis的<selectKey>元素来自定义生成主键。如下Mapping配置:

<insert id="addUser" parameterType="com.po.MyUser">
    <!-- 先使用selectKey 元素定义主键,然后在定义SQL语句 -->
    <selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
      select if(max(uid) is null, 1, max(uid)+1) as newUid from user
    </selectKey>
    insert into user (uname,usex) values(#{uname},#{usex})
  </insert>

这句话是取得user表中id最大值,再+1,实际情况比这个复杂:

select if(max(uid) is null, 1, max(uid)+1) as newUid from user

而 <selectKey> 元素中的 order="BEFORE" 是代表 <selectKey> 元素先于SQL查询语句执行,order="AFTER" 意义则相反。

7.5 <update>与<delete>元素

<update>和<delete>元素比较简单,它们的属性和<insert>元素、<select>元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。

<!-- 修改一个用户 -->
  <update id="updateUser" parameterType="com.po.MyUser">
    update user set uname = #{uname},usex = #{usex} where uid = #{uid}
  </update>
  <!-- 删除一个用户 -->
  <delete id="deleteUser" parameterType="Integer"> 
    delete from user where uid = #{uid}
  </delete>

7.6 <sql>元素

<sql>元素的作用在于可以定义SQL语句的一部分(代码片段),方便后面的SQL语句引用它,比如反复使用的列名。

<sql id="comColumns">uid,uname,usex</sql>
<select id="selectUser" resultType="com.po.MyUser">
  select <include refid="comColumns"/> from user
</select>

7.7 <resultMap>元素

<resultMap>元素表示结果映射集,是MyBatis中最重要也是最强大的元素。主要用来定义映射规则、级联的更新以及定义类型转化器等。

7.7.1 <resultMap>元素结构

<resultMap type="" id="">
    <constructor><!-- 类在实例化时,用来注入结果到构造方法 -->
  <idArg/><!-- ID参数,结果为ID -->
  <arg/><!-- 注入到构造方法的一个普通结果 -->
    </constructor>
    <id/><!-- 用于表示哪个列是主键 -->
    <result/><!-- 注入到字段或JavaBean属性的普通结果 -->
    <association property=""/><!-- 用于一对一关联 -->
    <collection property=""/><!-- 用于一对多、多对多关联 -->
    <discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
        <case value=""/>  <!-- 基于某些值的结果映射 -->
    </discriminator>
</resultMap>

<resultMap>元素的type属性表示需要的POJO,id属性是resultMap的唯一标识。子元素<constructor>用于配置构造方法(当POJO未定义无参数的构造方法时使用)。子元素<id>用于表示哪个列是主键。子元素<result>用于表示POJO和数据表普通列的映射关系。子元素<association> 、<collection> 和<discriminator>是用在级联的情况下。关于级联的问题比较复杂,将在7.8节级联那里学习。

7.7.2 使用Map存储结果集

任何select语句可以使用Map存储结果,示例代码如下:

先更改mapping映射文件:

<!-- 查询所有用户信息存到Map中 -->
  <select id="selectAllUserMap"  resultType="map">
    select * from user 
  </select>

再修改Dao接口:

public List<Map<String, Object>> selectAllUserMap();

然后在Controller中调用Dao接口方法:

//查询所有用户信息存到Map中 
    List<Map<String, Object>> lmp = userDao.selectAllUserMap();
    for (Map<String, Object> map : lmp) {
      System.out.println(map);
    }

7.7.3 使用POJO存储结果集

使用POJO存储结果集,可以自动映射,如果有比较复杂的映射或者级联查询,那就需要用到resultMap属性配置映射集合了。具体步骤如下:

创建POJO类:

package com.pojo;
public class MapUser {
  private Integer m_uid;
  private String m_uname;
  private String m_usex;
  public Integer getM_uid() {
    return m_uid;
  } 
  public void setM_uid(Integer m_uid) {
    this.m_uid = m_uid;
  }
  public String getM_uname() {
    return m_uname;
  }
  public void setM_uname(String m_uname) {
    this.m_uname = m_uname;
  }
  public String getM_usex() {
    return m_usex;
  }
  public void setM_usex(String m_usex) {
    this.m_usex = m_usex;
  }
  @Override
  public String toString() {
    return "User [uid=" + m_uid +",uname=" + m_uname + ",usex=" + m_usex +"]";
  }
}

然后配置Mapping文件中的<resultMap>元素:

<!-- 使用自定义结果集类型 -->
<resultMap type="com.pojo.MapUser" id="myResult">
  <!-- property是com.pojo.MapUser类中的属性-->
  <!-- column是查询结果的列名,可以来自不同的表 -->
  <id property="m_uid" column="uid"/>
  <result property="m_uname" column="uname"/>
  <result property="m_usex" column="usex"/>
</resultMap>

再配置<select>元素:

<!-- 引用刚才配置好的resultMap -->
<select id="selectResultMap" resultMap="myResult">
    select * from user
</select>

再去Dao接口中添加接口方法:

public List<MapUser> selectResultMap();

最后Controller中调用方法:

//使用resultMap映射结果集
    List<MapUser> listResultMap = userDao.selectResultMap(); 
    for (MapUser myUser : listResultMap) {
      System.out.println(myUser);
    }

7.8 级联关系

这一小节是本章的重点和难点所在,大家要认真学习哦!

级联关系其实是数据库实体的一个概念,有3种级联关系,分别是一对一级联、一对多级联以及多对多级联。级联的优点是获取数据非常方便,但是过多的级联会增加数据库系统的复杂度,降低系统性能

如果表A中有一个外键引用了表B的主键,A表就是子表,B表就是父表。当查询表A的数据时,通过表A的外键,也将表B的相关记录返回,这就是级联查询。例如,查询一个人的信息时,同时根据外键(身份证号)也将他的身份证信息返回。

7.8.1 一对一级联查询

生活中一对一级联关系是非常常见的,比如我们的身份证系统,一个人对应一个身份证号,一个身份证号只对应一个人。MyBatis如何处理一对一级联查询呢?在MyBatis中,通过<resultMap>元素的子元素<association>处理这种一对一级联关系。在<association>元素中,通常使用以下属性:

property:指定映射到实体类的对象属性

column:指定表中对应的字段(即查询返回的列名)。

javaType:指定映射到实体对象属性的类型。

select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。

下面以个人与身份证之间的关系为例,讲解一对一级联查询的处理过程,读者只需参考该实例即可学会一对一级联查询的MyBatis实现。

1、创建数据库的表idcard 和表person SQL语句如下:

DROP TABLE IF EXISTS `idcard`;
CREATE TABLE `idcard` (
  `id` tinyint(2) NOT NULL AUTO_INCREMENT,
  `code` varchar(18) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO idcard VALUES ('1', '123456789123456789');

DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `id` tinyint(2) NOT NULL,
  `name` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `idcard_id` tinyint(2) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idcard_id` (`idcard_id`),
  CONSTRAINT `idcard_id` FOREIGN KEY (`idcard_id`) REFERENCES `idcard` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO person VALUES ('1', '陈恒', '88', '1');

2、创建ch7项目,com.po包中创建对应的持久化类IdcardPerson,碍于篇幅原因,均省略了setter和getter方法,全部源码见文末Github地址:

package com.po;
/**
 * springtest数据库中idcard表的持久化类
 */
public class Idcard {
  private Integer id;
  private String code;
}
package com.po;
/**
 * springtest数据库中person表的持久化类
 */
public class Person {
  private Integer id;
  private String name;
  private Integer age;
  private Idcard card;//个人身份证关联
}

3、创建映射文件

首先在Mybatis的核心配置文件mybatis-config.xml中打开延时开关代码,目的是提高查询效率。代码如下:

<!-- 在使用MyBatis嵌套查询方式进行关联查询时,使用MyBatis的延迟加载在一定程度可以提高查询效率 -->
  <settings>
    <!-- 打开延迟加载的开关 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 将积极加载改为按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
  </settings>

同样的,在com.mybatis包中创建IDcard表和Person表对应的映射文件。

IdCardMapper.xml中注意命名空间com.dao.IdCardDao,绑定了Dao接口,方便使用SQL语句,当你的namespace绑定接口后,你可以不用写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句。其中的SQL语句也很简单,就是查询idcard表的所有数据,返回类型为Idcard类。

<mapper namespace="com.dao.IdCardDao">
  <select id="selectCodeById" parameterType="Integer" resultType="com.po.Idcard">
    select * from idcard where id=#{id}
  </select>
</mapper>

其对应的Dao接口方法IdCardDao为:

@Repository("idCardDao")
@Mapper
public interface IdCardDao {
  public Idcard selectCodeById(Integer i);
}

PersonMapper.xml文件中以3中方式实现“根据id查询个人信息功能”,下面我们一一解释。

第一种方法:嵌套查询,依次执行两个SQL语句。

<mapper namespace="com.dao.PersonDao">
  <!-- 一对一 根据id查询个人信息:第一种方法(嵌套查询,依次执行两个SQL语句) -->
  <resultMap type="com.po.Person" id="cardAndPerson1">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <!-- 一对一关联查询 -->
    <!-- association定义关联对象的封装规则 
      select:表明当前属性是调用select指定的方法查询出结果
      column:指定将那一列的值传递给这个方法
      整个流程:使用Select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
   -->
    <association property="card" column="idcard_id" javaType="com.po.Idcard"
    select="com.dao.IdCardDao.selectCodeById"/>
   </resultMap>
   <select id="selectPersonById1" parameterType="Integer" resultMap="cardAndPerson1">
     select * from person where id=#{id}
   </select>
  </mapper>

关于第一种方法的个人理解:

先执行 selectPersonById1 的语句,然后将结果idcard_id 的值传给 <association>元素中的<select> 元素。即本表数据由第一个SQL语句查询,被关联的表数据由第二个SQL即<association>元素中的<select> 元素查询,然后整体数据由<resultMap>元素解析并赋值给POJO类Person。

<select id="selectPersonById1"> 这个查询元素接收一个int类型的id值,然后执行这个 select * from person where id=#{id} SQL语句。而后将数据库返回的结果交由 cardAndPerson1 这个resultMap处理。

<resultMap type="com.po.Person" id="cardAndPerson1"> 这个结果集元素返回类型为 com.po.Person 这个POJO类。其中数据库返回的主键列 id 的自动赋值给POJO类 Person 中的属性 id 。其后的 name 和 age 属性皆是如此映射关系。

重点为 <association> 一对一级联元素。它返回的类型为 com.po.Idcard 这个POJO类,映射到 com.po.Person 这个POJO类的 card 属性(读者可以看看Person类的定义)。其中column="idcard_id" 定义要传给其中select="com.dao.IdCardDao.selectCodeById" 这个元素对应的SQL语句的值。

第二种方法:嵌套结果,执行一个SQL语句,本表数据由<resultMap>元素解析,然后被关联的表数据交由<association> 元素解析,所以叫嵌套结果。

<!-- 一对一 根据id查询个人信息:第二种方法(嵌套结果,执行一个SQL语句,由resultMap解析结果) -->
  <resultMap type="com.po.Person" id="cardAndPerson2">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <!-- 一对一关联查询 resultMap解析 Person 类中的 card 属性。
     card 属性的类型是 Idcard,其中card.id 对应查询结果中的 idcard_id 列名
          card.code 对应查询结果中的 code 列名-->
    <association property="card" javaType="com.po.Idcard">
      <id property="id" column="idcard_id"/>
      <result property="code" column="code"/>
    </association>
  </resultMap>
  <select id="selectPersonById2" parameterType="Integer" resultMap="cardAndPerson2">
    select p.*,ic.code 
    from person p, idcard ic 
    where p.idcard_id = ic.id and p.id=#{id}
  </select>

其实<association>元素在这里就相当于一个小的<resultMap>元素,用来解析数据库返回的数据,并赋值给POJO类。

第三种方法:写死SQL语句,数据传给一个自定义的POJO类,其中一个POJO类就是根据SQL结果特制的类。这种方法简洁明了,但是写死了SQL不利于后期程序改动,耦合性太高。

<!-- 一对一 根据id查询个人信息:第三种方法(写死SQL,使用自定义POJO存储结果) -->
  <select id="selectPersonById3" parameterType="Integer" resultType="com.pojo.SelectPersonById">
    select p.*,ic.code 
    from person p, idcard ic 
    where p.idcard_id = ic.id and p.id=#{id}
  </select>

三种方法对应的Dao接口:

@Repository("personDao")
@Mapper
public interface PersonDao {
  public Person selectPersonById1(Integer id);
  public Person selectPersonById2(Integer id);
  public SelectPersonById selectPersonById3(Integer id);
}

测试类:

ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
    OneToOneController oto = (OneToOneController)appCon.getBean("oneToOneController");
    oto.test();

整体结果截图,注意看SQL语句和传入的值,以及POJO类直接对应的关系其实就是表之间的关联关系:

7.8.2 一对多级联查询

在实际生活中一对多级联关系有许多,例如一个用户可以有多个订单,而一个订单只属于一个用户。

下面以用户和订单之间的关系为例,讲解一对多级联查询(实现“根据用户id查询用户及其关联的订单信息”的功能)的处理过程,读者只需参考该实例即可学会一对多级联查询的MyBatis实现。

1、创建数据表

本例子需要两个数据表,一张是前面已经创建了的user表,一张一订单表orders,其中orders表的外键user_id是user表的主键id。

DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
  `id` tinyint(2) NOT NULL AUTO_INCREMENT,
  `ordersn` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
  `user_id` tinyint(2) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO orders VALUES ('1', '999999', '1');
INSERT INTO orders VALUES ('2', '88888', '1');
INSERT INTO orders VALUES ('3', '7777777', '31');
INSERT INTO orders VALUES ('4', '666666666', '31');

2、创建持久化类Orders,对应数据表orders

public class Orders {
  private Integer id;
  private  String ordersn;
  //多对多用到的商品表
  private List<Product> products;
}

user表对应的POJO类MyUser加上这一条属性:

//一对多关联查询,用户关联的订单
  private List<Orders> ordersList;

因为是一对多,所以用的List。

3、创建映射文件UserMapper.xml

还是有三种查询方法,嵌套查询和嵌套结果以及写死SQL方法。

其中<collection> 元素用于解析被关联的表数据,ofType="com.po.Orders" 表示MyUser.ordersList集合中的元素类型, column="uid"表示将uid传递给selectOrdersById。其他的都和一对一查询一样。

<!-- 一对多 根据uid查询用户及其关联的订单信息:第一种方法(嵌套查询) -->
  <resultMap type="com.po.MyUser" id="userAndOrders1">
    <id property="uid" column="uid"/>
    <result property="uname" column="uname"/>
    <result property="usex" column="usex"/>
    <!-- 一对多关联查询,ofType表示集合中的元素类型,将uid传递给selectOrdersById-->
    <collection property="ordersList" ofType="com.po.Orders" column="uid" 
     select="com.dao.OrdersDao.selectOrdersById"/>
  </resultMap>
  <select id="selectUserOrdersById1" parameterType="Integer" resultMap="userAndOrders1">
    select * from user where uid = #{id}
  </select>
  <!-- 一对多 根据uid查询用户及其关联的订单信息:第二种方法(嵌套结果) -->
  <resultMap type="com.po.MyUser" id="userAndOrders2">
    <id property="uid" column="uid"/>
    <result property="uname" column="uname"/>
    <result property="usex" column="usex"/>
    <!-- 一对多关联查询,ofType表示集合中的元素类型 -->
    <collection property="ordersList" ofType="com.po.Orders" >
      <id property="id" column="id"/>
      <result property="ordersn" column="ordersn"/>
    </collection>
  </resultMap>
  <select id="selectUserOrdersById2" parameterType="Integer" resultMap="userAndOrders2">
    select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
  </select>
  <!-- 一对多 写死SQL,根据uid查询用户及其关联的订单信息:第三种方法(使用POJO存储结果) -->
  <select id="selectUserOrdersById3" parameterType="Integer" resultType="com.pojo.SelectUserOrdersById">
    select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
  </select>

其中第三种方法写死SQL,自定义了一个POJO类,但是其对应的Dao接口方法是需要一个List返回对象:

public List<SelectUserOrdersById> selectUserOrdersById3(Integer uid);

其他的没什么,直接上数据和结果截图:

7.8.3 多对多级联查询

其实,MyBatis没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换。例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系。使用一个中间表订单记录表,就可以将多对多级联转换成两个一对多的关系(仅体现在数据库表中,方便SQL查询,Mybatis中不体现)。下面以订单和商品(实现“查询所有订单以及每个订单对应的商品信息”的功能)为例,讲解多对多级联查询。

1、创建数据库表orders_detail product

DROP TABLE IF EXISTS `orders_detail`;
CREATE TABLE `orders_detail` (
  `id` tinyint(2) NOT NULL AUTO_INCREMENT,
  `orders_id` tinyint(2) DEFAULT NULL,
  `product_id` tinyint(2) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `orders_id` (`orders_id`),
  KEY `product_id` (`product_id`),
  CONSTRAINT `orders_id` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`),
  CONSTRAINT `product_id` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO orders_detail VALUES ('1', '1', '1');
INSERT INTO orders_detail VALUES ('2', '1', '2');
INSERT INTO orders_detail VALUES ('3', '2', '1');
INSERT INTO orders_detail VALUES ('4', '2', '2');
INSERT INTO orders_detail VALUES ('5', '3', '1');
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
  `id` tinyint(2) NOT NULL,
  `name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `price` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO product VALUES ('1', '好书', '88');
INSERT INTO product VALUES ('2', '坏书', '8.8');

2、创建持久化类Product和Orders

public class Product {
  private Integer id;
  private String name;
  private Double price;
  //多对多关系中的其中一个一对多
  private List<Orders> orders;
}
public class Orders {
  private Integer id;
  private  String ordersn;
  //多对多关系中的其中另一个一对多
  private List<Product> products;
}

3、创建映射文件

这个多对多和前面的一对多一模一样,看代码注释就行。

<!-- 多对多关联 查询所有订单以及每个订单对应的商品信息(嵌套结果)
     一个SQL查询一切,剩下解析交给resultMap和对应的collection 
     ===用Orders类实现多对多查询===
  -->
  <resultMap type="com.po.Orders" id="allOrdersAndProducts">
    <id property="id" column="id"/>
    <result property="ordersn" column="ordersn"/>
    <!-- 多对多关联 ofType表示Orders.products这个集合中的元素类型Product-->
    <collection property="products" ofType="com.po.Product">
      <id property="id" column="pid"/>
      <result property="name" column="name"/>
      <result property="price" column="price"/>
    </collection>
  </resultMap>
  <select id="selectallOrdersAndProducts" resultMap="allOrdersAndProducts">
    select o.*,p.id as pid,p.name,p.price
    from orders o,orders_detail od,product p
    where od.orders_id = o.id 
    and od.product_id = p.id
  </select>
  
  <!-- 多对多关联 查询所有订单以及每个订单对应的商品信息(嵌套结果)
     一个SQL查询一切,剩下解析交给resultMap和对应的collection -->
  <!-- ===用Product类实现多对多查询=== -->
  <resultMap type="com.po.Product" id="allOrdersAndProducts2">
    <id property="id" column="pid"/>
    <result property="name" column="name"/>
    <result property="price" column="price"/>
    <!-- 多对多关联 ofType表示Orders.orders这个集合中的元素类型Orders-->
    <collection property="orders" ofType="com.po.Orders">
      <id property="id" column="id"/>
      <result property="ordersn" column="ordersn"/>
    </collection>
  </resultMap>
  <select id="selectallOrdersAndProducts2" resultMap="allOrdersAndProducts2">
    select o.*,p.id as pid,p.name,p.price
    from orders o,orders_detail od,product p
    where od.orders_id = o.id 
    and od.product_id = p.id
  </select>

其中这个SQL就是查询所有订单及其对应的订单详情:

对应的Dao方法:

public List<Orders> selectallOrdersAndProducts();

运行截图:

本章知识点讲解完毕,重点就是后面的级联查询,大家有不懂的可以公众号留言,也可以看看公共号的其他相关文章,最近都是有关Mybatis框架知识的,希望对您有所帮助,本系列教程所有源码见下面地址。

本教程所有源码地址:

https://github.com/jiahaoit/java_ssm_course

原文发布于微信公众号 - 浩Coding(gh_c4a2e63d2ca7)

原文发表时间:2019-04-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券