1 关联查询

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

    1.1 一对多关联查询

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

    1.1.1 定义实体

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

    1.1.2 定义数据库表

    SSM框架之MyBatis3专题3:关联关系查询_第1张图片
    SSM框架之MyBatis3专题3:关联关系查询_第2张图片

    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、多表连接查询方式

    
    
        
        
        
        
            
            
        
    
    
    
    
  • 注意,此时即使字段名与属性名相同,在中也写出它们的映射关系。因为框架是依据这个封装对象的。
  • 另外,在映射文件中使用标签体现出两个实体对象间的关联关系。其两个属性的意义为:
  • property:指定关联属性,即Country类中的集合属性;
  • ofType:集合属性的泛型类型;

2、多表单独查询方式

  • 多表连接查询方式是将多张表进行拼接,连为一张表后进行查询。其查询的本质是一张表。而多表单独查询方式是多张表各自查询各自的相关内容,需要多张表的联合数据了,则将主表的查询结果联合其他表的查询结果,封装为一个对象。
  • 当然,这多个查询是可以跨越多个映射文件的。即是可以跨越多个namespace的。在使用其他namespace的查询时,添加上其所在的namespace即可。
    
    
    
        
        
        
        
        
    
    
    
    
  • 关联属性的数据来自于一个查询。而该查询的动态参数countryId=#{ooo}的值来自于查询的查询结果字段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:关联关系查询_第3张图片
    SSM框架之MyBatis3专题3:关联关系查询_第4张图片

    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、多表连接查询方式:

    
    
        
        
        
            
            
        
    
    
    
  • 注意:在映射文件中使用标签体现出两个实体对象间的关联关系。
  • property:指定关联属性,即Minister类中的country属性。
  • javaType:关联属性的类型。
    2、多表单独查询方式
    
    
    
        
        
        
    
    
    

    1.3 自关联查询

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

    1.3.1 自关联的DB表

    SSM框架之MyBatis3专题3:关联关系查询_第5张图片

    1.3.2 以一对多方式处理

  • 以一对多方式处理,即一方可以看到多方。该处理方式的应用场景比较多,例如在页面上点击父栏目,显示出其子栏目。再如,将鼠标定位在窗口中的某菜单项上会显示其所有子菜单项等。
    1、查询指定栏目的所有子孙栏目:
  • 根据指定的id,仅查询出其所有子栏目。当然,包括其所有辈分的孙子栏目。即,给出的查询id实际为父栏目id。
  • 定义实体类:
    public class NewsLabel {
    private Integer id;
    private String name;
    //关联属性,指定子栏目,即多方
    private Set children;
    //getter and setter
    //toString()
    }
  • 定义Dao接口:
    public interface INewsLabelDao {
    List selectChidrenByParentId(int pid);
    }
  • 定义mapper映射:这里通过select语句的递归调用实现查询所有下级栏目的功能。查询结果的集合数据来自于递归调用的selectChidrenByParentId查询。与第一次进行该查询不同的是,第一次的pid动态参数值来自于调用方法传递来的实参,而中查询语句的pid动态参数数值来自于上一次的查询结果的id值。
    
    
    
        
        
        
    
    
    
    
  • 定义测试类:
    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 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映射:

    
    
    
        
        
        
    
    
    
    
  • 修改测试类:
    @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映射:
    
    
        
        
        
        
    
    
    
  • 定义测试类:
    @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 courses;
    //setter and getter()
    //toString()
    }
    public class Course {
    private Integer cid;
    private String cname;
    private Set students;
    //setter and getter()
    //toString()
    }

    1.4.2 定义数据库表

    SSM框架之MyBatis3专题3:关联关系查询_第6张图片
    SSM框架之MyBatis3专题3:关联关系查询_第7张图片

    1.4.3 定义Dao层接口

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

    1.4.4 定义mapper映射

  • 多对多关联关系也是通过映射文件体现的。但是,需要注意的是SQL语句中是对三张表的连接查询。
    
    
        
        
        
            
            
        
    
    
    

    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 直接加载

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

    2.3 深度延迟加载

  • 修改主配置文件的,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading关闭(设置为false)。
    
    
        
        
        
        
    

    2.4 侵入式延迟加载

  • 修改主配置文件的,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading也开启(设置为true,默认为true)。
    
    
        
        
        
        
    
  • 需要注意的是,该延迟策略也是一种延迟加载,需要在延迟加载开关lazyLoadingEnabled开启时才会其作用。若lazyLoadingEnabled为false,则aggressiveLazyLoading无论取何值,均不会起作用。