前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java EE之SSM框架整合开发 -- (7) MyBatis映射器

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

作者头像
浩Coding
发布2019-07-03 15:07:35
2.5K0
发布2019-07-03 15:07:35
举报
文章被收录于专栏:浩Coding

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

前言

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

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

代码语言:javascript
复制
<!-- 查询所有用户信息存到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等数据库的表格可以采用自动递增的字段作为主键。自动回填示例如下:

代码语言:javascript
复制
<!-- 添加一个用户,成功后将主键值回填给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>元素来自定义生成主键。具体配置示例代码如下:

代码语言:javascript
复制
<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)传递一个基本数据参数,如:

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

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

代码语言:javascript
复制
<!-- 查询陈姓男性用户信息 -->
<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传递多个参数,如:

代码语言:javascript
复制
package com.pojo;
public class SeletUserParam {
private String u_name;
private String u_sex;
//此处省略setter和getter方法
}
代码语言:javascript
复制
<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的核心配置文件的情况不多

代码语言:javascript
复制
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语句,其示例代码如下:

代码语言:javascript
复制
<!-- 根据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接口通过键值对传递多个参数。

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

代码语言:javascript
复制
public List<MyUser> selectAllUser(Map<String, Object> param);

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

代码语言:javascript
复制
<!-- 查询陈姓男性用户信息 -->
  <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代码为:

代码语言:javascript
复制
//查询多个用户
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方法修改为:

代码语言:javascript
复制
public List<MyUser> selectAllUser(SeletUserParam param);

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

代码语言:javascript
复制
<!-- 查询陈姓男性用户信息 -->
  <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的“查询多个用户”代码片段修改:

代码语言:javascript
复制
//查询多个用户
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等数据库的表格可以采用自动递增的字段作为主键。有时可能需要使用这个刚刚产生的主键,用以关联其他业务

映射文件写法:

代码语言:javascript
复制
<!-- 添加一个用户,成功后将主键值回填给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类中的调用:

代码语言:javascript
复制
//添加一个用户
    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配置:

代码语言:javascript
复制
<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,实际情况比这个复杂:

代码语言:javascript
复制
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>元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。

代码语言:javascript
复制
<!-- 修改一个用户 -->
  <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语句引用它,比如反复使用的列名。

代码语言:javascript
复制
<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>元素结构

代码语言:javascript
复制
<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映射文件:

代码语言:javascript
复制
<!-- 查询所有用户信息存到Map中 -->
  <select id="selectAllUserMap"  resultType="map">
    select * from user 
  </select>

再修改Dao接口:

代码语言:javascript
复制
public List<Map<String, Object>> selectAllUserMap();

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

代码语言:javascript
复制
//查询所有用户信息存到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类:

代码语言:javascript
复制
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>元素:

代码语言:javascript
复制
<!-- 使用自定义结果集类型 -->
<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>元素:

代码语言:javascript
复制
<!-- 引用刚才配置好的resultMap -->
<select id="selectResultMap" resultMap="myResult">
    select * from user
</select>

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

代码语言:javascript
复制
public List<MapUser> selectResultMap();

最后Controller中调用方法:

代码语言:javascript
复制
//使用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语句如下:

代码语言:javascript
复制
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地址:

代码语言:javascript
复制
package com.po;
/**
 * springtest数据库中idcard表的持久化类
 */
public class Idcard {
  private Integer id;
  private String code;
}
代码语言:javascript
复制
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中打开延时开关代码,目的是提高查询效率。代码如下:

代码语言:javascript
复制
<!-- 在使用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类。

代码语言:javascript
复制
<mapper namespace="com.dao.IdCardDao">
  <select id="selectCodeById" parameterType="Integer" resultType="com.po.Idcard">
    select * from idcard where id=#{id}
  </select>
</mapper>

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

代码语言:javascript
复制
@Repository("idCardDao")
@Mapper
public interface IdCardDao {
  public Idcard selectCodeById(Integer i);
}

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

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

代码语言:javascript
复制
<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> 元素解析,所以叫嵌套结果。

代码语言:javascript
复制
<!-- 一对一 根据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不利于后期程序改动,耦合性太高。

代码语言:javascript
复制
<!-- 一对一 根据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接口:

代码语言:javascript
复制
@Repository("personDao")
@Mapper
public interface PersonDao {
  public Person selectPersonById1(Integer id);
  public Person selectPersonById2(Integer id);
  public SelectPersonById selectPersonById3(Integer id);
}

测试类:

代码语言:javascript
复制
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。

代码语言:javascript
复制
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

代码语言:javascript
复制
public class Orders {
  private Integer id;
  private  String ordersn;
  //多对多用到的商品表
  private List<Product> products;
}

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

代码语言:javascript
复制
//一对多关联查询,用户关联的订单
  private List<Orders> ordersList;

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

3、创建映射文件UserMapper.xml

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

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

代码语言:javascript
复制
<!-- 一对多 根据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返回对象:

代码语言:javascript
复制
public List<SelectUserOrdersById> selectUserOrdersById3(Integer uid);

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

7.8.3 多对多级联查询

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

1、创建数据库表orders_detail product

代码语言:javascript
复制
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');
代码语言:javascript
复制
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

代码语言:javascript
复制
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、创建映射文件

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

代码语言:javascript
复制
<!-- 多对多关联 查询所有订单以及每个订单对应的商品信息(嵌套结果)
     一个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方法:

代码语言:javascript
复制
public List<Orders> selectallOrdersAndProducts();

运行截图:

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

本教程所有源码地址:

https://github.com/jiahaoit/java_ssm_course

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 浩Coding 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档