• MyBatis关联查询


    在关系型数据库中,我们经常要处理一对多,多对一和多对多的关系。

    关联查询分为:

    • 一对一关联查询
    • 一对多关联查询
    • 多对一关联查询
    • 多对多关联查询
    • 自关联查询、

    一、一对多关联查询

    一对多关联查询是指,在查询一对象的时候,同时将其所关联的多放对象也都查询出来。

    例:国家Country与部长Minister间的一对多关系

    1.定义实体

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

    Country:

    public class Country {
        
        private Integer cid;
        private String cname;
        //关联属性
        private Set<Minister> ministers;
    }
    

    Minister:

    public class Minister {
        private Integer mid;
        private String mname;
    }
    

    2.定义数据库表

     
    country.PNG
     
    minister.PNG

    3.定义dao接口

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

    4.测试类:

    public class MyTest {
        
        private ICountryDao dao;
        private SqlSession session;
                
        @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(2);
            System.out.println(country);
        }   
    }
    

    5.映射文件

    方式一:多表连接查询方式:

    <mapper namespace="com.hcx.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 country,minster
            where countryId=cid and cid=#{cid}
        </select>
    
    </mapper>
    

    注意,此时即使字段名与属性名相同,在<resultMap/>中也要写出他们的映射关系。因为框架是依据<resultMap/>封装对象的。

    在映射文件中使用<collection/>标签体现出两个实体对象间的关联关系。其两个属性的意义为:

    • property:指定关联属性,即Country类中的集合属性
    • ofType:集合属性的泛型类型

    方式二:多表单独查询方式:

    多表连接查询方式是将多张表进行连接,连为一张表后进行查询。其查询的本质是一张表。而多表单独查询方式是多张表各自查询各自的相关内容,需要多张表的联合数据,则将主表的查询结果联合其他表的查询结果,封装为一个对象。

    多个查询时可以跨越多个映射文件的,即是可以跨越多个namespace的。在使用其它namespace的查询时,添加上其所在的namespace即可。

    <mapper namespace="com.hcx.dao.ICountryDao">
    
        <select id="selectMinisterByCountry" resultType="Minister">
            select mid,mname from minister where countryId=#{cid}
        </select>
        
        <resultMap type="Country" id="countryMapper">
            <id column="cid" property="cid"/>
            <result column="cname" property="cname"/>
            <collection property="ministers" 
                        ofType="Minister"
                        select="selectMinisterByCountry"
                        column="cid"/>                              
        </resultMap>
     
        <select id="selectCountryById" resultMap="CountryMapper">
            select cid,cname
            from country
            where cid=#{cid}
        </select>
    
    </mapper>
    
     
    一对多单独查询.PNG

    手动sql语句过程:

    select cid,cname from country where cid=2
    
     
    1.PNG
    select mid,mname from minister where countryId=2
    
     
    2.PNG

    关联属性<collection/>的数据来自于另一个查询<selectMinisterByCountry/>。而该查询<selectMinisterByCountry>的动态参数countryId=#{cid}的值来自于查询<selectCountryById/>的查询结果字段cid。

    二、多对一关联查询

    这里的多对一关联查询是指,在查询多方对象的时候,同时将其所关联的一方对象也查询出来。

    由于查询多方对象时也是一个一个查询,所以多对一关联查询,其实就是一对一关联查询。即一对一关联查询的实现方式与多对一的实现方式是相同的。

    例:部长Minister与国家Country间的多对一关系

    1.实体类:

    Minister:

    public class Minister {
        private Integer mid;
        private String mname;
        //关联属性
        private Country country;
    }
    

    Country:

    public class Country {      
        private Integer cid;
        private String cname;       
    }
    

    2.dao接口:

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

    3.测试类:

    public class MyTest {
        
        private IMinisterDao dao;
        private SqlSession session;
        
        
        @Before
        public void before(){
            session = MyBatisUtils.getSqlSession();
            dao = session.getMapper(IMinisterDao.class);
        }
        
        @After
        public void tearDown(){
            if(session!=null){
                session.close();
            }
        }
        
        @Test
        public void test01(){           
            Minister minister = dao.selectMinisterById(2);
            System.out.println(minister);
        }
    
    }
    

    映射文件:

    方式一:多表连接查询方式:

    <mapper namespace="com.hcx.dao.IMinisterDao">
    
        <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 minister,country
            where countryId=cid and mid=#{mid}
        </select>
        
    </mapper>
    

    注意,在映射文件中使用<association/>标签体现出两个实体对象间的关联关系。

    • property:指定关联属性,即Minister类中的country属性
    • javaType:关联属性的类型

    方式二:多表单独查询方式:

    <mapper namespace="com.hcx.dao.IMinisterDao">
        
        <select id="selectCountryById" resultType="Country">
            select cid,cname from country where cid=#{cid}
        </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"/>
        </resultMap>
        
        <select id="selectMinisterById" resultMap="ministerMapper">
            select mid,mname,countryId from minister where mid=#{mid}
        </select>
    </mapper>
    
     
    多对一单独查询.PNG

    手动写sql过程:

    select mid,mname,countryId from minister where mid=2
    
     
    11.PNG
    select cid,cname from country where cid=1
    
     
    22.PNG

    三、自关联查询

    所谓自关联查询是指,自己即充当一方,又充当多方,是1:n或n:1的变型。例如,对于新闻栏目NewsLabel,可以充当一方,即父栏目,也可以充当多方,即子栏目。而反映到DB表中,只有一张表,这张表中具有一个外键,用于表示该栏目的父栏目。一级栏目没有父栏目,所以可以将其外键值设为0,而子栏目则具有外键值。

    将自关联分为两种情况。一种是当作1:n,即当前类作为一方,其包含多方的集合域属性。一种是当作n:1,即当前类作为多方,其包含一方的域属性。

    1.自关联的DB表

     
    自关联DB表.PNG

    手动查询:

     
    自关联手动1.PNG
     
    自关联手动2.PNG
     
    自关联手动3.PNG

    2.以一对多方式处理

    以一对多方式处理,即一方可以看到多方。该处理方式的应用场景比较多。例如页面点击父栏目,显示出其子栏目。将鼠标定位在窗口中的某菜单项上会显示其所有子菜单项等。

    查询指定栏目的所有子孙栏目

    根据指定的id,仅查询出其所有子栏目。包括其所有辈分的孙子栏目。即给出的插叙id实际为父栏目id

    a.实体类

    //新闻栏目:当前的新闻栏目被看作是一方,即父栏目
    public class NewsLabel {
        private Integer id;
        private String name; //栏目名称
        private Set<NewsLabel> children;
    }
    

    b.dao接口

    public interface INewsLabelDao {    
        List<NewsLabel> selectChildrenByParent(int pid);
    }
    

    c.mapper映射

    通过select语句的递归调用实现查询所有下级栏目的功能。查询结果的集合数据<collection/>来自于递归调用的selectChildrenByParentId查询。与第一次进行该查询不同的是,第一次的pid动态参数值来自于调用方法传递来的实参,而<collection/>中查询语句的pid动态参数值来自于上一次的查询结果的id值

    <mapper namespace="com.hcx.dao.INewsLabelDao">
        
        <resultMap type="NewsLabel" id="newslabelMapper">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <collection property="children"
                        ofType="NewsLabel"
                        select="selectChildrenByParent"
                        column="id"/>
        </resultMap>
        
        <select id="selectChildrenByParent" resultMap="newsLabelMapper">
            select id,name from newslabel where pid=#{pid}
        </select>
        
    </mapper>
    

    d.测试类

    public class MyTest {
        
        private INewsLabelDao dao;
        private SqlSession session;
                
        @Before
        public void before(){
            session = MyBatisUtils.getSqlSession();
            dao = session.getMapper(INewsLabelDao.class);
        }
        
        @After
        public void tearDown(){
            if(session!=null){
                session.close();
            }
        }
        
        @Test
        public void test01(){       
            List<NewsLabel> children = dao.selectChildrenByParent(1);
            for (NewsLabel newsLabel : children) {
                System.out.println(newsLabel);
            }
        }   
    }
    

    查询指定栏目及其所有子孙栏目

    查询结果既要包含指定id的当前栏目,还包含其所有辈分的孙子栏目。即给出的id实际为当前要查询的栏目的id。

    a.dao接口:

    public interface INewsLabelDao {    
        //List<NewsLabel> selectChildrenByParent(int pid);
        NewsLabel selectNewsLabelById(int id);
    }
    

    b.mapper映射文件

    <mapper namespace="com.hcx.dao.INewsLabelDao">
    
        <select id="selectNewslabelByParent" resultMap="newslabelMapper">
            select id,name from newslabel where pid=#{id}
        </select>
        
        <resultMap type="NewsLabel" id="newslabelMapper">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <collection property="children"
                        ofType="NewsLabel"
                        select="selectNewslabelByParent"
                        column="id"/>
        </resultMap>
        
        <select id="selectNewslabelById" resultMap="newsLabelMapper">
            select id,name from newslabel where id=#{id}
        </select>
            
    </mapper>
    

    c.测试类

    @Test
    public void test02(){
        NewsLabel newslabel = dao.selectNewsLabelById(1);
        System.out.println(newslabel);
    }
    

    3.以多对一方式处理

    多对一方式处理,即多方可以看到一方。该处理方式的应用场景,例如在网页上显示当前页面的站内位置

    a.实体类

    //新闻栏目:当前的新闻栏目被看作是多方,即子栏目
    public class NewsLabel {
        private Integer id;
        private String name; //栏目名称
        private NewsLabel parent;
    }
    

    b.dao接口

    public interface INewsLabelDao {    
        //List<NewsLabel> selectChildrenByParent(int pid);
        NewsLabel selectNewsLabelById(int id);
    }
    

    c.mapper映射:

    <mapper namespace="com.hcx.dao.INewsLabelDao">
        
        <!-- <select id="selectNewsLabelById" resultMap="newslabelMapper">
            select id,name,pid from newslabel where id=#{pid}
        </select> -->
        
        <resultMap type="NewsLabel" id="newslabelMapper">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <association property="parent"
                         javaType="NewsLabel"
                         select="selectNewsLabelById"
                         column="pid"/>
        </resultMap>
        
        <select id="selectNewsLabelById" resultMap="newslabelMapper">
            select id,name,pid from newslabel where id=#{id}
        </select>
            
    </mapper>
    

    d.测试类

    @Test
    public void test02(){
        NewsLabel newslabel = dao.selectNewsLabelById(1);
        System.out.println(newslabel);
    }
    

    四、多对多关联查询

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

    1.定义实体:

    在定义双向关联(双方均可看到对方的关联关系)的实体的toString方法时,只让一方的toString方法中输出对方,不要让双方均可输出对方。否则将会出现输出时的递归现象。

    Student:

    public class Student {
        
        private Integer sid;
        private String sname;
        private Set<Course> courses;
    }
    

    Course:

    public class Course {
        
        private Integer cid;
        private String cname;
        private Set<Student> students;
    }
    

    2.数据库表:

     
    多对多表.PNG

    3.dao接口

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

    4.mapper映射

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

    <mapper namespace="com.hcx.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 student,middle,course
            where sid=studentId and cid=courseId and sid=#{sid}
        </select>
            
    </mapper>
    

    5.测试类:

    public class MyTest {
        
        private IStudentDao dao;
        private SqlSession session;
        
        
        @Before
        public void before(){
            session = MyBatisUtils.getSqlSession();
            dao = session.getMapper(IStudentDao.class);
        }
        
        @After
        public void tearDown(){
            if(session!=null){
                session.close();
            }
        }
        
        @Test
        public void test01(){
            Student student = dao.selectStudentById(1);
            System.out.println(student);
        }
    
    }
      

    自关联查询

    自关联查询就是自己充当多方,同时也充当一方,即多和一都在同一张表中,一般这样的表其实可以看做是一个树形结构,在数据库表中有一个外键,该外键表示当前数据的父节点。下面以公司职位为例创建一张自关联的表。

    建表语句

    CREATE TABLE `t_employee` (
      `id` int(11) NOT NULL,
      `name` varchar(45) DEFAULT NULL,
      `job` varchar(20) DEFAULT NULL,
      `mgr` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    id是主键
    name是员工姓名
    job是工作岗位
    mgr是上级领导的id
    这里面mgr可以看做是外键引用的是上级领导的id,为了方便操作,这里先不建立外键关系。

    初始化数据:

    INSERT INTO `t_employee` VALUES (1001,'king','董事长',NULL),(1002,'jack','技术总监',1001),(1003,'paul','财务总监',1001),(1004,'tom','销售总监',1001),(1005,'tomas','技术一部经理',1002),(1006,'linda','技术二部经理',1002),(1007,'lucy','会计',1003),(1008,'lily','出纳',1003),(1009,'terry','销售一部经理',1004),(1010,'emma','销售二部经理',1004),(1011,'may','软件工程师',1005),(1012,'bella','软件工程师',1005),(1013,'kelly','软件工程师',1006);
    

    一对多关联查询

    创建javabean
    当前类是一方,里面添加List属性用来表示多的一方

    public class Employee {
    
        private int id;
    
        private String name;
    
        private String job;
    
        //表示多的一方,即当前员工的所有下属
        private List<Employee> children;
    
        //省略getter、setter和toString
    }
    

    创建dao

    public interface EmployeeDao {
    
        List<Employee> selectChildrenByPid(int mgr);
    }
    

    创建mapper
    创建EmployeeMapper.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.monkey1024.dao.EmployeeDao">
    
        <!--形成递归查询-->
        <resultMap id="childrenMap" type="Employee">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <collection property="children" ofType="Employee" select="selectChildrenByPid" column="id"/>
        </resultMap>
    
        <select id="selectChildrenByPid" resultMap="childrenMap">
            SELECT id, name ,job
            FROM t_employee
            WHERE mgr=#{pid}
        </select>
    </mapper>
    

    在上面的sql语句中,实际上形成了递归查询,resultMap中的collection里面,select属性表示会继续执行selectChildrenByPid这个sql语句,column表示将id作为属性传入sql中,此处是pid。有一点需要注意的是column中的id要跟sql语句中的id一致,因为本次查询出的id会作为条件pid再次传入sql中执行。

    定义测试类

    
    import org.apache.ibatis.session.SqlSession;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.util.List;
    
    public class EmployeeTest01 {
    
        private SqlSession sqlSession;
    
        private EmployeeDao employeeDao;
    
        /**
         * 测试时先执行该方法创建EmployeeDao对象
         */
        @Before
        public void initStudentDao(){
            sqlSession = MyBatisUtil.getSqlSession();
            //通过该方法可以获取TeamDao的对象
            employeeDao = sqlSession.getMapper(EmployeeDao.class);
        }
    
        /**
         * 执行完成后需要关闭sqlSession
         */
        @After
        public void closeSession() {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    
        @Test
        public void selectChildrenByPid(){
            List<Employee> employeeList = employeeDao.selectChildrenByPid(1002);
    
            employeeList.forEach((e)->{
                System.out.println(e);
            });
        }
    }
    

    查询结果包含指定员工和其下属员工
    上面的执行结果中可以看到会将id是1002这位员工的所有下属打印出来,但是不包含1002这位员工的信息,如果想要查询的结果包含1002这位员工的信息需要在mapper中添加下面内容:

    <select id="selectEmployeeByPid" resultMap="childrenMap">
        SELECT id, name ,job
        FROM t_employee
        WHERE id=#{id}
    </select>
    

    上面sql就是根据id查询员工数据,但是这里面的resultMap指定的是childrenMap,所以它会继续进行递归查询将其下属也全部查询出来,这样子在查询结果中就包含了当前员工的信息了。

    在dao中添加下面方法

    List<Employee> selectEmployeeByPid(int mgr);
    

    添加测试方法

    @Test
    public void selectEmployeeByPid(){
        List<Employee> employeeList = employeeDao.selectEmployeeByPid(1002);
    
        employeeList.forEach((e)->{
            System.out.println(e);
        });
    }
  • 相关阅读:
    【回顾整理】HTML+CSS个的两个实战项目
    【转】tkinter实现的文本编辑器
    迟到的tkinter---学校选课刷屏器
    调用有道翻译API
    【leetCode】3Sum Closest
    【LeedCode】3Sum
    【LeedCode】String to integer(atoi)
    【LeetCode】Reverse digits of an integer
    前端工程师必备PS技能
    【LeetCode】Add Two Numbers
  • 原文地址:https://www.cnblogs.com/wangdayexinyue/p/11668425.html
Copyright © 2020-2023  润新知