SSM框架之MyBatis3专题3:关联

发布时间:2019-09-20 07:34:44编辑:auto阅读(1614)

    1 关联查询

    • 当查询内容涉及具有关联关系的多个表时,就需要使用关联关系查询。根据表与表之间的关联关系的不同,关联查询分为四种:
      1、一对一关联查询;
      2、一对多关联查询;
      3、多对一关联查询;
      4、多对多关联查询;
    • 由于日常工作中最常见的关联关系是一对多、多对一与多对多,所以这里就不专门只讲解一对一关联查询了,其解决方案与多对一解决方案是相同的。

      1.1 一对多关联查询

    • 这里的一对多关联查询是指,在查询一方对象的时候,同时将其所关联的多方对象也都查询出来。
    • 下面以国家Country与部长Minister间的一对多关系进行演示。

      1.1.1 定义实体

    • 在定义实体时,若定义的是双向关联,即双方的属性中均有对方对象作为域属性出现,那么它们在定义各自的toString()方法时需要注意,只让某一方可以输出另一方即可,不要让双方的toString()方法均可输出对方。这样会形成递归调用,程序出错。

      1.1.2 定义数据库表

      SSM框架之MyBatis3专题3:关联关系查询
      SSM框架之MyBatis3专题3:关联关系查询

      1.1.3 定义Dao层接口

      public interface ICountryDao {
      Country selectCountryById(int cid);
      }

      1.1.4 定义测试类

      public class Mytest {
      private SqlSession session;
      private ICountryDao dao;    
      @Before
      public void setUp() {
          session = MyBatisUtils.getSqlSession();
          dao = session.getMapper(ICountryDao.class);
      }   
      @After
      public void tearDown() {
          if(session != null) {
              session.close();
          }
      }   
      @Test
      public void test01() {
          Country country = dao.selectCountryById(1);
          System.out.println(country);
      }
      }

      1.1.5 定义映射文件

      1、多表连接查询方式

      <mapper namespace="com.eason.mybatis.dao.ICountryDao">
      <resultMap type="Country" id="countryMapper">
          <id column="cid" property="cid"/>
          <result column="cname" property="cname"/>
          <!-- 关联属性的映射文件 -->
          <collection property="ministers" ofType="Minister">
              <id column="mid" property="mid"/>
              <result column="mname" property="mname"/>
          </collection>
      </resultMap>
      <!-- 多表连接查询 -->
      <select id="selectCountryById" resultMap="countryMapper">
          select cid, cname, mid, mname from t_country, t_minister where cid=#{xxx} and cid=countryId
      </select>
      </mapper>
    • 注意,此时即使字段名与属性名相同,在<resultMap/>中也写出它们的映射关系。因为框架是依据这个<resultMap/>封装对象的。
    • 另外,在映射文件中使用<collection/>标签体现出两个实体对象间的关联关系。其两个属性的意义为:
    • property:指定关联属性,即Country类中的集合属性;
    • ofType:集合属性的泛型类型;

    2、多表单独查询方式

    • 多表连接查询方式是将多张表进行拼接,连为一张表后进行查询。其查询的本质是一张表。而多表单独查询方式是多张表各自查询各自的相关内容,需要多张表的联合数据了,则将主表的查询结果联合其他表的查询结果,封装为一个对象。
    • 当然,这多个查询是可以跨越多个映射文件的。即是可以跨越多个namespace的。在使用其他namespace的查询时,添加上其所在的namespace即可。
      <mapper namespace="com.eason.mybatis.dao.ICountryDao">
      <select id="selectMinisterByCountry" resultType="Minister">
      select mid, mname from t_minister where countryId=#{ooo}
      </select>
      <resultMap type="Country" id="countryMapper">
          <id column="cid" property="cid"/>
          <result column="cname" property="cname"/>
          <!-- 关联属性的映射文件 -->
          <!-- 集合的数据来自于指定的select查询 -->
          <collection property="ministers" ofType="Minister" select="selectMinisterByCountry" column="cid"></collection>
      </resultMap>
      <!-- 多表连接查询 -->
      <select id="selectCountryById" resultMap="countryMapper">
          select cid, cname from t_country where cid=#{xxx}
      </select>
      </mapper>
    • 关联属性<collection/>的数据来自于一个查询<selectMinisterByCountry/>。而该查询<selectMinisterByCountry/>的动态参数countryId=#{ooo}的值来自于查询<selectCountryById/>的查询结果字段cid。

      1.2 多对一关联查询

    • 这里的多对一关联查询是指,在查询多方对象的时候,同时将其所关联的一方对象也查询出来。
    • 由于在查询多方对象时也是一个一个查询,所以多对一关联查询,其实就是一对一关联查询。即一对一关联查询的实现方式与多对一的实现方式是相同的。
    • 下面以部长Minister与国家Country间的多对一关联进行演示。

      1.2.1 定义实体

      public class Minister {
      private Integer mid;
      private String mname;
      private Country country;
      //setter and getter
      //toString
      }
      public class Country {
      private Integer cid;
      private String cname;
      //setter and getter()
      //toString
      }

      1.2.2 定义数据库表

      SSM框架之MyBatis3专题3:关联关系查询
      SSM框架之MyBatis3专题3:关联关系查询

      1.2.3 定义Dao层接口

      public interface ICountryDao {
      Minister selectMinisterById(int mid);
      }

      1.2.4 定义测试类

      @Test
      public void test02() {
          Minister minister = dao.selectMinisterById(2);
          System.out.println(minister);
      }

      1.2.5 定义映射文件

      1、多表连接查询方式:

      <mapper namespace="com.eason.mybatis.dao.ICountryDao">
      <resultMap type="Minister" id="ministerMapper">
          <id column="mid" property="mid"/>
          <result column="mname" property="mname"/>
          <association property="country" javaType="Country">
              <id column="cid" property="cid"/>
              <result column="cname" property="cname"/>
          </association>
      </resultMap>
      <select id="selectMinisterById" resultMap="ministerMapper">
          select mid, mname, cid, cname from t_minister,t_country where mid=#{xxx} and countryId = cid 
      </select>
      </mapper>
    • 注意:在映射文件中使用<association/>标签体现出两个实体对象间的关联关系。
    • property:指定关联属性,即Minister类中的country属性。
    • javaType:关联属性的类型。
      2、多表单独查询方式
      <mapper namespace="com.eason.mybatis.dao.ICountryDao">
      <select id="selectCountryById" resultType="Country">
          select * from t_country where cid=#{ooo} 
      </select>
      <resultMap type="Minister" id="ministerMapper">
          <id column="mid" property="mid"/>
          <result column="mname" property="mname"/>
          <association property="country" javaType="Country" select="selectCountryById" column="countryId"></association>
      </resultMap>
      <select id="selectMinisterById" resultMap="ministerMapper">
          select mid, mname, countryId from t_minister where mid=#{xxx}
      </select>
      </mapper>

      1.3 自关联查询

    • 所谓自关联是指,自己即充当一方,又充当多方,是1:n或者n:1的变型。例如,对于新闻栏目NewsColumn,可以充当一方,即父栏目,也可以充当多方,即子栏目。而反映到DB表中,只有一张表,这张表中具有一个外键,用于表示该栏目的父栏目。一级栏目没有父栏目,所以可以将其外键值是为0,而子栏目具有外键值。
    • 为了便于理解,将自关联分为两种情况来讲解,一种是当做1:n讲解,即当前类作为一方,其包含多方的集合域属性。一种是当做n:1讲解,即当前类作为多方,其包含一方的域属性。
    • 下面以新闻栏目为例进行讲解。由于Column是DBMS中的关键字,为了避免误解,将新闻栏目实体类定义为NewsLabel。

      1.3.1 自关联的DB表

      SSM框架之MyBatis3专题3:关联关系查询

      1.3.2 以一对多方式处理

    • 以一对多方式处理,即一方可以看到多方。该处理方式的应用场景比较多,例如在页面上点击父栏目,显示出其子栏目。再如,将鼠标定位在窗口中的某菜单项上会显示其所有子菜单项等。
      1、查询指定栏目的所有子孙栏目:
    • 根据指定的id,仅查询出其所有子栏目。当然,包括其所有辈分的孙子栏目。即,给出的查询id实际为父栏目id。
    • 定义实体类:
      public class NewsLabel {
      private Integer id;
      private String name;
      //关联属性,指定子栏目,即多方
      private Set<NewsLabel> children;
      //getter and setter
      //toString()
      }
    • 定义Dao接口:
      public interface INewsLabelDao {
      List<NewsLabel> selectChidrenByParentId(int pid);
      }
    • 定义mapper映射:这里通过select语句的递归调用实现查询所有下级栏目的功能。查询结果的集合数据<collection/>来自于递归调用的selectChidrenByParentId查询。与第一次进行该查询不同的是,第一次的pid动态参数值来自于调用方法传递来的实参,而<collection/>中查询语句的pid动态参数数值来自于上一次的查询结果的id值。
      <mapper namespace="com.eason.mybatis.dao.INewsLabelDao">
      <!-- 形成递归,因为查询结果再次调用了selectChidrenByParentId查询 -->
      <resultMap type="NewsLabel" id="newsLabelMapper">
          <id column="id" property="id"/>
          <result column="name" property="name"/>
          <collection property="children" ofType="NewsLabel" 
                      select="selectChidrenByParentId"
                      column="id"></collection>
      </resultMap>
      <!-- 根据pid查询其子栏目 -->
      <select id="selectChidrenByParentId" resultMap="newsLabelMapper">
          select id, name from t_newslabel where pid = #{xxx}
      </select>
      </mapper>
    • 定义测试类:
      public class Mytest {
      private SqlSession session;
      private INewsLabelDao dao;
      @Before
      public void setUp() {
          session = MyBatisUtils.getSqlSession();
          dao = session.getMapper(INewsLabelDao.class);
      }
      @After
      public void tearDown() {
          if(session != null) {
              session.close();
          }
      }   
      @Test
      public void test02() {
          List<NewsLabel> children = dao.selectChidrenByParentId(1);
          for(NewsLabel newsLabel : children) {
              System.out.println(newsLabel);
          }
      }
      }

      2、查询指定栏目以及所有子孙栏目

    • 这里的查询结果,即要包含指定id的当前栏目,还要包含其所有辈分的孙子栏目。即给出的id实际为当前要查询的栏目的id。
    • 修改Dao接口:
      public interface INewsLabelDao {
      NewsLabel selectNewsLabelById(int id);
      }
    • 修改mapper映射:

      <mapper namespace="com.eason.mybatis.dao.INewsLabelDao">
      <select id="selectNewsLabelByParentId" resultMap="newsLabelMapper">
          select id, name from t_newslabel where pid = #{ooo}
      </select>
      <resultMap type="NewsLabel" id="newsLabelMapper">
          <id column="id" property="id"/>
          <result column="name" property="name"/>
          <collection property="children" ofType="NewsLabel" 
                      select="selectNewsLabelByParentId"
                      column="id"></collection>
      </resultMap>
      
      <select id="selectNewsLabelById" resultMap="newsLabelMapper">
          select id, name from t_newslabel where id = #{xxx}
      </select>
      </mapper>
    • 修改测试类:
      @Test
      public void test02() {
          NewsLabel newsLabel = dao.selectNewsLabelById(1);
          System.out.println(newsLabel);
      }

      1.3.3 以多对一方式处理

    • 以多对一方式处理,即多方可以看到一方。该处理方式的应用功能场景,例如在网页上显示当前页面的站内位置。
    • 定义实体类:

      public class NewsLabel {
      private Integer id;
      private String name;
      private NewsLabel parent;
      
      //setter and getter()
      //toString
      }
    • 定义mapper映射:
      <mapper namespace="com.eason.mybatis.dao.INewsLabelDao">
      <resultMap type="NewsLabel" id="newslabelMapper">
          <id column="id" property="id"/>
          <result column="name" property="name"/>
          <association property="parent"
                      javaType="NewsLabel"
                      select="selectParentByParentId"
                      column="pid">
          </association>
      </resultMap>
      <select id="selectParentByParentId" resultMap="newslabelMapper">
          select id,name,pid from t_newslabel where id=#{xxx}
      </select>
      </mapper>
    • 定义测试类:
      @Test
      public void test03() {
          NewsLabel newsLabel = dao.selectParentByParentId(3);
          System.out.println(newsLabel);
      }

      1.4 多对多关联查询

    • 什么是多对多关联关系?一个学生可以选择多门课程,而一门课程可以由多个学生选。这就是典型的多对多关联关系。所以,所谓多对多关系,其实是由两个互反的一对多关系组成。一般情况下,多对多关系都会通过一个中间表来建立,例如选课表。

      1.4.1 定义实体

    • 在定义双向关联(双方均可看到对方的关联关系)的实体的toString()方法时,只会让一方的toString()方法中可以输出对方,不要让双方均可输出对方。否则将会出现的递归现象,程序会报错。
      public class Student {
      private Integer sid;
      private String sname;
      private Set<Course> courses;
      //setter and getter()
      //toString()
      }
      public class Course {
      private Integer cid;
      private String cname;
      private Set<Student> students;
      //setter and getter()
      //toString()
      }

      1.4.2 定义数据库表

      SSM框架之MyBatis3专题3:关联关系查询
      SSM框架之MyBatis3专题3:关联关系查询

      1.4.3 定义Dao层接口

      public interface IStudentDao {
      Student selectStudentById(int id);
      }

      1.4.4 定义mapper映射

    • 多对多关联关系也是通过映射文件<resultMap/>的<collection/>体现的。但是,需要注意的是SQL语句中是对三张表的连接查询。
      <mapper namespace="com.eason.mybatis.dao.IStudentDao">
      <resultMap type="Student" id="studentMapper">
          <id column="sid" property="sid"/>
          <result column="sname" property="sname"/>
          <collection property="courses" ofType="Course">
              <id column="cid" property="cid"/>
              <result column="cname" property="cname"/>
          </collection>
      </resultMap>
      <select id="selectStudentById" resultMap="studentMapper">
          select sid, sname, cid, cname 
          from t_student, t_middle, t_course 
          where sid = studentId and cid = courseId and sid = #{xxx}
      </select>
      </mapper>

      1.4.5 定义测试类

      @Test
      public void test05() {
          Student student = dao.selectStudentById(2);
          System.out.println(student);
      }

      2 延迟加载

    • MyBatis中的延迟加载,也称之为懒加载,是指在进行关联查询时,按照设置规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。
    • 需要注意的是,MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的。

      2.1 关联对象加载时机

    • MyBatis根据对关联对象查询的select语句的执行时期,分为三种类型:直接加载、侵入式延迟加载和深度延迟加载。
      1、直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询。
      2、侵入式延迟:执行完对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的select查询。即对关联对象的查询执行,侵入到了主加载对象的详情访问中。也可以这样理解:将关联对象的详情侵入到了主加载对象的详情中,即将关联对象的详情作为主加载对象的详情的一部分出现。
      3、深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。
    • 需要注意的是,延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能够是多表连接所进行的select查询。因为,多表连接查询,其实质是对一张表的查询,对由多个表连接后形成的一张表的查询。会一次性将多张表的所有信息查询出来。
    • MyBatis中对于延迟加载设置,可以应用到一对一、一对多、多对多的所有关联关系查询中。
    • 下面以一对多关联关系查询为例,讲解MyBatis中的延迟加载应用。

      2.2 直接加载

    • 修改主配置文件:在主配置文件的<properties/>与<typeAliases/>标签之间,添加<setting/>标签,用于完成全局参数设置。
          <!-- 注册属性文件 -->
          <properties resource="jdbc.properties"></properties>
          <!-- 全局参数设置 -->
          <settings>
              <setting name="lazyLoadingEnabled" value="false"/>
          </settings>
          <!-- 注册类的别名 -->
          <typeAliases>
              <package name="com.eason.mybatis.beans"/>
          </typeAliases>
    • 在MyBatis帮助文档中Ctrl+F查询关键字“lazy”,则可查询出延迟加载的相关参数名称以及取值。
      SSM框架之MyBatis3专题3:关联关系查询
    • 全局属性lazyLoadingEnabled的值只要设置为false,那么,对于关联对象的查询,将采用直接加载。即在查询过主加载对象后,会马上查询关联对象。(对于标签的书写位置,是由约束文件进行规定好的,不能随便写。在<configuration/>标签上点击F2,可查看的顺序以及数量要求。)
    • 标签数量上要求说明用到符号为:
      1、?:表示子标签可以没有,若有的话,最多只能有一个,即小于等于1;
      2、*:表示子标签可以没有,可以有多个,即大于等于0;
      3、+:表示子标签最少要有一个,即大于等于1;
      4、没有符号:表示有且只能够有一个,即等于1;

      2.3 深度延迟加载

    • 修改主配置文件的<settrings/>,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading关闭(设置为false)。
      <!-- 全局参数设置 -->
      <settings>
          <!-- 延迟加载总开关 -->
          <setting name="lazyLoadingEnabled" value="true"/>
          <!-- 侵入式延迟加载开关 -->
          <setting name="aggressiveLazyLoading" value="false"/>
      </settings>

      2.4 侵入式延迟加载

    • 修改主配置文件的<settings/>,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading也开启(设置为true,默认为true)。
      <!-- 全局参数设置 -->
      <settings>
          <!-- 延迟加载总开关 -->
          <setting name="lazyLoadingEnabled" value="true"/>
          <!-- 侵入式延迟加载开关 -->
          <setting name="aggressiveLazyLoading" value="true"/>
      </settings>
    • 需要注意的是,该延迟策略也是一种延迟加载,需要在延迟加载开关lazyLoadingEnabled开启时才会其作用。若lazyLoadingEnabled为false,则aggressiveLazyLoading无论取何值,均不会起作用。

关键字