MyBatis(五) 关联映射|一对一、一对多、多对一、多对多关联CRUD的案例实现|关联查询|连接查询|子查询

十一、关联映射

11.1 实体关系

实体——数据实体,实体关系指的就是数据与数据之间的关系

例如:用户和角色、房屋和楼栋、订单和商品

实体关系分为以下四种:

一对一关联

  • 人和身份证、学生和学生证、用户基本信息和详情

一个场景:

用户信息(30个字段,用户数量也很多)

用户登录:根据用户名查询用户信息,当用户表中数据量很大且字段很多时会严重影响查询速度。

那么我可以分为两张表。

①用户基本信息表(5个字段):

​ 用户id,账号,密码,姓名,最后登陆时间

②用户详情表(20+个字段):

​ 详情id,…

用户登录的时候我只需要查询用户基本信息表就可以了,效率会大大提升。

那我在查看用户详情的时候是不是需要获取到用户基本信息表中的用户id,所以这两张表要有一个关联,这也属于一对一关联。

  • 主键关联(用户表主键和详情表主键相同时,表示是匹配的数据)

①用户基本信息表(5个字段):

用户id,账号,密码,姓名,最后登陆时间

​ 1 zhangsan …

​ 2 lisi …

​ 3 wangwu …

②用户详情表(20+个字段):

详情id,手机,…

​ 1 1303030303…

​ 3 1313131313…

​ 2 1323232323…

此时,当用户id详情id相等时,即表示这两个记录是匹配的。

  • 唯一外键关联

①用户基本信息表(5个字段):

用户id,账号,密码,姓名,最后登陆时间

​ 1 zhangsan …

​ 2 lisi …

​ 3 wangwu …

②用户详情表(20+个字段):

详情id,手机,… uid(外键,唯一)

​ 1 1303030303… 2

​ 2 1313131313… 3

​ 3 1323232323… 1

此时,用户id详情id各是各的,其相等与否没有任何关系。

我在用户详情表中再加一个外键叫做uid(用户id的意思)。通过uid用户id是否相等,来匹配数据。

但是这样会有一个问题,如果两个用户详情记录的uid都为2,就表示‘lisi’这个用户有两个详情信息了,这样不合理。所以,我在把uid设置为外键的同时,还要把它设置为唯一键。

这就叫唯一外键关联。

一对多关联、多对一关联

实例:

  • 一对多:班级和学生、类别和商品、楼栋和房屋
  • 多对一:学生和班级、商品和类别

(一对多和多对一只是一个角度的区别)

数据表关系:

  • 在”多“的一端添加外键和”一“的一端进行关联

多对多关联

实例:用户和角色、角色和权限、房屋和业主、学生和社团、订单和商品

数据表关系:建立第三张关系表,添加两个外键分别与两张表主键进行关联

用户(user_id) 用户角色表(uid,rid) 角色(role_id)

11.2 创建项目,部署MyBatis框架

11.2.1 WEB项目的搭建

创建web项目(基于maven)

  • 新建一个Maven项目

  • 如果你是一个web项目,设置打包方式为war

        <groupId>com.wyl</groupId>
        <artifactId>mybatis-demo2</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>	//添加这一句
    
  • 在src.main目录下新建一个webapp文件夹。

  • 在webapp目录下新建一个WEB-INF文件夹。

    • 在WEB-INF目录下新建一个web.xml配置文件,根据本笔记2.3的方法可建立此模板,模板内容如下
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
    
    </web-app>
    
  • 然后刷新一下Maven

  • 在pom.xml中添加web的依赖支持

        <!-- servlet依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- jsp依赖 -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
  • 作为一个WEB项目,要放到服务器运行。

    • 点击右上角Add Configuration

    在这里插入图片描述

    • 找到Tomcat,点击local,创建本地Tomcat

      在这里插入图片描述

    • 选择一个你已经配置好的Tomcat

    在这里插入图片描述

    • 然后点一下右下角的Fix -> war exploded,把项目部署进来

      在这里插入图片描述

    • 自己可以改一下项目的名字

在这里插入图片描述

  • 这样一来,整个WEB项目的搭建,就结束了。
11.2.2 部署MyBatis框架

上面的笔记已经讲过了,此处大致做一遍,具体的原理理论翻看上面

  • 添加依赖(mybatis和mysql )
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!-- mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
  • 配置文件

    在src->main->resources目录中新建一个xml文件mybatis-config.xml(利用自定义模板创建)

    • 在里面要进行数据库连接信息的配置

      • 在同级目录下新建一个配置文件jdbc.properties

        driver=com.mysql.jdbc.Driver
        url=jdbc:mysql://localhost:3306/mybatis_study?characterEncoding=utf-8
        username=root
        password=root
        
      • 在主配置文件mybatis-config.xml中引入

            <properties resource="jdbc.properties"></properties>
            <environments default="mysql">
                <environment id="mysql">
                    <transactionManager type="JDBC"></transactionManager>
                    <dataSource type="POOLED">
                        <property name="driver" value="${driver}"/>
                        <property name="url" value="${url}"/>
                        <property name="username" value="${username}"/>
                        <property name="password" value="${password}"/>
                    </dataSource>
                </environment>
            </environments>
        

        到此,数据库的连接配置已经结束了。

  • 帮助类

    • 在src->main->java目录下新建一个包com.wyl.utils,在包中新建一个类MyBatisUtil.java

      public class MyBatisUtil {
      
          private static SqlSessionFactory factory;
          private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();
      
          static {
              try {
                  InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
                  factory = new SqlSessionFactoryBuilder().build(is);
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
          //由于factory跟缓存相关,有可能会被人使用,所以这里给factory提供一个get方法(不写也可以)
          public static SqlSessionFactory getSqlSessionFactory(){
              return factory;
          }
          //获取SqlSession这个是比较重要的
          //给个true,表示自动事务提交;不给参数或者给个false,表示手动事务提交
          public static SqlSession getSqlSession(boolean isAutoCommit){
              SqlSession sqlSession = local.get();
              if (sqlSession == null){
                  sqlSession = factory.openSession(isAutoCommit);
                  local.set(sqlSession);
              }
              return sqlSession;
          }
          //对上面的方法进行一个封装,不传参默认是手动提交
          public static SqlSession getSqlSession(){
              return getSqlSession(false);
          }
          //对于查询操作,不需要事务管理,只需要获取DAO
          public static <T extends Object>T getMapper(Class<T> c){
              SqlSession sqlSession = getSqlSession(true);
              return sqlSession.getMapper(c);
          }
      }
      

      对于工具类的封装,最好要熟练,要知道每一个步骤为什么这样写。

11.3 一对一关联

实例:用户和详情

11.3.1 创建数据表
-- 用户信息表
create table users(
	user_id int primary key auto_increment,
    user_name varchar(20) not null unique,
    user_pwd varchar(20) not null,
    user_realname varchar(20) not null,
    user_img varchar(100) not null	--用户头像,用varchar保存图片路径
);

-- 用户详情表
create table details(
	detail_id int primary key auto_increment,
    user_addr varchar(50) not null,
    user_tel char(11),
    user_desc varchar(200),		-- 用户的个性签名
    uid int not null unique,	-- unique表示唯一键
    --constraint FK_USER foreign key(uid) references users(user_id)	
    		-- 表示建立物理关联,你在删除的时候,比如你在删除用户的时候,详情信息没删,你是删不掉的
    --现在在企业中,即使uid要与user_id关联起来,我们也不会给它创建外键。这个uid的字段是有的,但是我们不		会去建立关联。现在的物理关联都是由业务逻辑去做,都是逻辑关联。
);
11.3.2 创建实体类
  • 为了便于创建实体类,引入lombok依赖
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
  • 在src->main->java目录下新建包com.wyl.pojo

  • 新建一个User类User.java

    • 实体类中属性的个数与类型尽量要与表中的保持一致。
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class User {
        private int userId;
        private String userName;
        private String userPwd;
        private String userRealName;
        private String userImg;
    }
    
    • 使用lombok提供的方法

      lombok是一个工具包,可以帮助我们去简化实体类中的例如get、set,ToString这些方法,一律使用注解就可以完成。

      @Data: 声明它为一个实体类,之后,它的get、set方法你就不用手动去写了。

      @NoArgsConstructor:这样写之后,这个类就有无参构造器了。

      @AllArgsConstructor:这个类就有全参构造器了。

      @ToString:这个类就重写了ToSTring方法了。

  • 新建一个Detail类Detail.java

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Detail {
        private int detailId;
        private String userAddr;
        private String userTel;
        private String userDesc;
        private int userId;
    }
    
11.3.3 添加操作
  • 在src->main->java目录下新建包com.wyl.dao

  • 在dao目录下新建UserDAO接口

    • 实现添加用户操作
        public int insertUser(User user);
    
  • 在src->main->resources目录下新建mappers目录

    • 在mappers目录下新建UserMapper.xml

    而UserMapper是要实现UserDAO的,所以不要忘记将包名放到namespace

    <mapper namespace="com.wyl.dao.UserDAO">
    
    </mapper>
    
    • 配置insert操作
    <mapper namespace="com.wyl.dao.UserDAO">
        <insert id="insertUser">
            insert into users(user_name,user_pwd,user_realname,user_img)
            values(#{userName},#{userPwd},#{userRealName},#{userImg})
        </insert>
    </mapper>
    

    insert into时,是不需要插入user_id的,因为它是自增的

    values括号中的内容,是通过#{ }来取的

    • 但是还有个问题,在添加用户之后,在对这个用户的相应的详情信息进行操作时,要获取到对应的user_id,所以这个insert操作还需要设置对user_id的主键回填,以便于实现详情相关操作时能够获取到对应的userId。

      <mapper namespace="com.wyl.dao.UserDAO">
          <insert id="insertUser"  useGeneratedKeys="true" keyProperty="userId">
              insert into users(user_name,user_pwd,user_realname,user_img)
              values(#{userName},#{userPwd},#{userRealName},#{userImg})
          </insert>
      </mapper>
      

      到此,添加用户就做好了。

  • 单元测试

    想要进行单元测试,首先要导入junit依赖

            <!-- junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    

    怎么做单元测试,把你的光标放在被测试类的上面,alt+insert或者鼠标右键->generate,然后点Test

在这里插入图片描述

​ 我们使用JUnit4,然后把下面想要做测试的方法勾选上,OK

在这里插入图片描述

​ 单元测试类就创建好了。它被放在src->test->java->com.wyl.dao下,类名叫UserDAOTest。

11.3.4 插入操作

插入操作,或者说是用户注册的实现。要实现向用户基本信息User表中插入记录的同时,还要向用户详情信息Detail表中相对应地插入记录。

  • 在测试类中,你想要实现testInsertUser()方法,或者说是用户注册

    • 首先你要获取到UserDAO对象

      通过MyBatisUtil.getMapper(UserDAO.class)(这个是我们刚才自己写好的帮助类中的方法)

      然后执行userDao.insertUser(user)方法(先new一个User对象user)

          @Test
          public void testInsertUser() {
              User user = new User(0,"zhangsan","123123","张三","1.jpg");
              UserDAO userDAO = MyBatisUtil.getMapper(UserDAO.class);
              int i = userDAO.insertUser(user);
              System.out.println(i);
          }
      
    • 运行测试方法,发现报出异常

      org.apache.ibatis.binding.BindingException: Type interface com.wyl.dao.UserDAO is not known to the MapperRegistry.

      说明我的mapper没找到,UserDAO没找到。

      为什么呢?我的UserMapper.xml中定义好了namespace=“com.wyl.dao.UserDAO”

      但是不要忘了,还要在主配置文件里添加

          <mappers>
              <mapper resource="mappers/UserMapper.xml"></mapper>
          </mappers>
      

      运行成功,返回值1。向user表中插入了一条记录。

    • 想实现用户注册,还需要实现对详情表信息的插入,如何处理

      • 首先new一个Detail对象(先将userId设为0)
      User user = new User(0,"lisi","456456","李四","2.jpg");
      Detail detail = new Detail(0,"河南省新乡市","13030303300","我的个性签名",0);
      
      • 由于在mapper文件中标签设置了主键回填,所以可以通过user.getUserId()获取到插入的用户信息的userId。再通过detail.setUserId去设置Detail对象的userId。

        于是,Detail就获取到了与User所关联的userId了

      int i = userDAO.insertUser(user);   //插入了一条user
      detail.setUserId(user.getUserId());	//此时我的detail中就获得了userId了
      
      • 接下来,要实现对Detail的记录的插入操作,与User的插入操作类似

        • 第一步:创建DetaiDAO接口,声明插入方法

          public interface DetaiDAO {
              public int insertDetail(Detail detail);
          }
          
        • 第二步:新建mapper文件DetailMapper.xml

          <mapper namespace="com.wyl.dao.DetaiDAO">
              <insert id="insertDetail">
                  insert into details(user_addr,user_tel,user_desc,user_img,uid)
                  values (#{userAddr},#{userTel},#{userDesc},{userImg},#{userId})
              </insert>
          </mapper>
          

          这里就没必要主键回填了,因为要它的主键没啥用

        • 第三步:在主配置文件中加入

          <mappers>
              <mapper resource="mappers/UserMapper.xml"></mapper>
              <mapper resource="mappers/DetailMapper.xml"></mapper>
          </mappers>
          
      • 处理好这些配置后,就可以在测试类里面实现了

            @Test
            public void testInsertUser() {
                User user = new User(0,"lisi","456456","李四","2.jpg");
                Detail detail = new Detail(0,"河南省新乡市","13030303300","我的个性签名",0);
        
                UserDAO userDAO = MyBatisUtil.getMapper(UserDAO.class);
                int i = userDAO.insertUser(user);   //插入了一条user
                System.out.println(i);
                detail.setUserId(user.getUserId());	//此时我的detail中就获得了userId了
        
                DetaiDAO detailDAO = MyBatisUtil.getMapper(DetaiDAO.class);
                int j = detailDAO.insertDetail(detail);	//插入了一条detail
        
                System.out.println(j);
            }
        
      • 结果展示:

      在这里插入图片描述
      在这里插入图片描述

  • 但是还有一个问题,上面的代码,如果 i 返回值为1,则表示user插入成功;如果 j 返回值为1,则表示detail插入成功。但是,如果user插入成功,但detail插入失败,就有问题了。我处理失败问题之后再次插入,它会告诉我重复插入记录了,因为刚才user已经插入成功进去了。

    • 也就是说这两个插入操作应该属于同一个事务。这两个操作,有一个失败,整个注册操作就失败。
    • 通过手动提交来实现
        @Test
        public void testInsertUser() {
            //用户注册,提交了基本信息和详情到Servlet,Servlet接收注册信息封装到User和Detail对象中
            User user = new User(0,"lisi","456456","李四","2.jpg");
            Detail detail = new Detail(0,"河南省新乡市","13030303300","我的个性签名",0);
    
            SqlSession sqlSession = MyBatisUtil.getSqlSession();
            try{
                UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
                int i = userDAO.insertUser(user);   //插入了一条user,OK。那么detail该怎么插入
                System.out.println(i);
                detail.setUserId(user.getUserId());
                DetaiDAO detailDAO = sqlSession.getMapper(DetaiDAO.class);
                int j = detailDAO.insertDetail(detail);
                System.out.println(j);
                sqlSession.commit();    //手动提交
            } catch (Exception e){
                e.printStackTrace();
                sqlSession.rollback();  //失败回滚
            }
        }
    
11.3.5 关联查询

关联操作最难的不是增删改,最难的是查询。当我查询用户信息的时候,请把详情信息一并查出来。查询是个重点。

  • 我先完成对用户表user的单表查询(按照用户名username查询)

    • 首先,在UserDAO接口中添加方法
        public User queryUser(String username);
    
    • 然后,到UserMapper中进行的配置
        <resultMap id="userMap" type="User">
            <id column="user_id" property="userId"/>
            <result column="user_name" property="userName"/>
            <result column="user_pwd" property="userPwd"/>
            <result column="user_realname" property="userRealName"/>
            <result column="user_img" property="userImg"/>
        </resultMap>
        <select id="queryUser" resultMap="userMap">
            select user_id,user_name,user_pwd,user_realname,user_img
            from users where user_name=#{username}
        </select>
    

    ​ type可以直接写作User的原因是我在主配置文件mybatis-config.xml中进行了设置

        <typeAliases>
            <typeAlias type="com.wyl.pojo.User" alias="User"></typeAlias>
            <typeAlias type="com.wyl.pojo.Detail" alias="Detail"></typeAlias>
        </typeAliases>
    
    • 最后写测试类
        @Test
        public void testQueryUser(){
            UserDAO userDAO = MyBatisUtil.getMapper(UserDAO.class);
            User user = userDAO.queryUser("lisi");
            System.out.println(user);
        }
    
    • 成功执行
    User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg)
    
    Process finished with exit code 0
    
  • 关联查询(我不仅要查用户,在查用户的同时,我还要求把与之对应的详情也查出来)

现在,如果我做查询。首先,实体要有所变化。刚才的那个查询的类型是User,即使我把最终要求的结果给你全部查询出来,你User类型能装么?

所以,在查询一个对象的时候,如果想关联地查询出与之匹配的信息,我们有一种操作是这样的:

直接在实体类中private定义一个Detail对象。

这样一来,我们的User中有没有用户基本信息?有。

里面能不能放我们的Detail对象,也就是详情?可以。

实体

User.java
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class User {
        private int userId;
        private String userName;
        private String userPwd;
        private String userRealName;
        private String userImg;

        private Detail detail;	//加入Detail
    }

Detail.java
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Detail {		//不用做修改
        private int detailId;
        private String userAddr;
        private String userTel;
        private String userDesc;
        private int userId;
    }

当然,这样修改之后,你的User再去insert添加操作的时候,要多添加一个detail属性,写成null就行了。

    public void testInsertUser() {
        User user = new User(0,"zhaoliu","111111","赵六","3.jpg",null);
        ...
    }

映射文件

如果我映射文件不改,还是原来的

<select id="queryUser" resultMap="userMap">
    select user_id,user_name,user_pwd,user_realname,user_img
    from users where user_name=#{username}
</select>

那么你还是只能查询出用户的基本信息。

我们有两种实施方案。一种叫连接查询,一种叫子查询

1.第一种方案(连接查询):

先学习一下SQL语句怎么写:

select *		-- select * 不太好,一会儿把所有的字段写上去就行了
from users u
INNER JOIN details d
on u.user_id=d.uid
where u.user_name="lisi"

运行结果:

在这里插入图片描述

只修改SQL语句,不修改其他的:

<resultMap id="userMap" type="User">
    <id column="user_id" property="userId"/>
    <result column="user_name" property="userName"/>
    <result column="user_pwd" property="userPwd"/>
    <result column="user_realname" property="userRealName"/>
    <result column="user_img" property="userImg"/>
</resultMap>
<select id="queryUser" resultMap="userMap">
    select user_id,user_name,user_pwd,user_realname,user_img,detail_id,user_addr,user_tel,user_desc
    from users u
    INNER JOIN details d
    on u.user_id=d.uid
    where u.user_name=#{username}
</select>

只修改SQL语句,其他的不作改动。这样去执行查询,也可以执行,只是查询的结果有点问题:

User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg, detail=null)

Process finished with exit code 0

为什么?

​ 因为我的中,设置的字段还是原来的那几个字段,而后面的字段没有装进去。

​ 直接在里添加配置即可

<result column="detail_id" property="detail.detailId"/>
//detail_id是User实体类中detail属性的一个属性,detailId是detail中的一个属性,所以是这样写

​ 只添加这一个,看看效果:

User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg, detail=Detail(detailId=1, userAddr=null, userTel=null, userDesc=null, userId=0))

Process finished with exit code 0

​ 我们会发现,不再是null了,而是真正放入了一个detail对象。只不过里面的字段除了detailId外都是null。

​ 那么同理再把其他字段也都添加进去吧,完成。

<result column="detail_id" property="detail.detailId"/>
<result column="user_addr" property="detail.userAddr"/>
<result column="user_tel" property="detail.userTel"/>
<result column="user_desc" property="detail.userDesc"/>

2.第二种方案(子查询):

先学习一下SQL语句怎么写:

-- 第一次查询,查询用户信息
select * from users where user_name='lisi';
-- 第二次查询,根据第一次查询出的用户id=6,查询详情信息
select * from details where uid=6;

没有做连接,而只是执行了两个基本查询。

mapper中的SQL语句:

    <resultMap id="userMap" type="User">
        <id column="user_id" property="userId"/>
        <result column="user_name" property="userName"/>
        <result column="user_pwd" property="userPwd"/>
        <result column="user_realname" property="userRealName"/>
        <result column="user_img" property="userImg"/>
    </resultMap>
    <select id="queryUser" resultMap="userMap">
        select user_id,user_name,user_pwd,user_realname,user_img
        from users
        where user_name=#{username}
    </select>

​ 只是一个最简单的单表查询。

​ 但是,当我查询出user以后,我还要做第二次查询,第二次查询是去查询detail表。

​ 所以,在detailDAO中应该有一个操作。

    public Detail queryDetailByUid(int uid);

​ 同时,在detail的映射文件DetailMapper中,做相应配置:

    <resultMap id="detailMap" type="Detail">
        <id column="detail_id" property="detailId"/>
        <result column="user_addr" property="userAddr"/>
        <result column="user_tel" property="userTel"/>
        <result column="user_desc" property="userDesc"/>
    </resultMap>
    <select id="queryDetailByUid" resultMap="detailMap">
        select detail_id,user_addr,user_tel,user_desc
        from details
        where uid=#{uid}
    </select>

​ 这样,detail的单表查询也完成了。

但是,我现在想要在我查User的时候,顺带把Detail查出来,查出来之后,放到我的User里面的Detail属性里。怎么放,通过resuletMap的去放,只不过detail和其他字段相比的不同是,detail是一个对象。想放对象怎么办,这里我们用到一个标签,通过这个标签,调用子查询,关联查询一个对象。

<association property="detail"/>

而这个“detail”是从哪来的?这里要用到一个属性叫select。

<association property="detail" select="com.wyl.dao.DetaiDAO.queryDetailByUid"/>

但是我这个queryDetailByUid需不需要传入一个uid进去?需要。所以我又写了一个属性column。

<association property="detail" select="com.wyl.dao.DetaiDAO.queryDetailByUid" column="user_id"/>

理解:我刚刚做了第一次查询。我第一次查询出来的字段user_id的结果,赋值给我的detail,我的detail再根据select的内容作第二次查询。

我的User里面就有detail了:

User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg, detail=Detail(detailId=1, userAddr=河南省新乡市, userTel=13030303300, userDesc=我的个性签名, userId=0))

Process finished with exit code 0

11.4 一对多关联

案例:班级(1)和学生(n)

增删改不是重点。重点是查。

11.4.1 创建数据表

一对多关联,就是要在“多”的一端添加外键。

-- 创建班级信息表
create table classes(
	cid int primary key auto_increment,
    cname varchar(30) not null unique,
    cdesc varchar(100)
);

-- 创建学生信息表
create table students(
	sid char(5) primary key,
    sname varchar(20) not null,
    sage int not null,
    scid int not null	-- 不加唯一键,意味着多个学生能属于同一个班级
);
11.4.2 创建实体类
Student.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
    private String stuId;   //学号
    private String stuName;
    private int stuAge;
    private int stuCid;     //学生所在班级的id
}

Clazz.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
    private int classId;
    private String className;
    private String classDesc;
}
11.4.3 关联查询

当查询一个班级的时候,要关联查询出这个班级下的所有学生

所以班级的实体类,应当重新构造一下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
    private int classId;
    private String className;
    private String classDesc;
    
    private List<Student> stus;	//存储当前班级下所有的学生信息
}

由于增删改不是重点,所以我们先手动地在数据表中添加一些记录:

classes表:

在这里插入图片描述

students表:

在这里插入图片描述

连接查询

SQL语句写法:

-- 根据班级编号查询班级信息,同时查询这个班级下所有的学生信息

-- 连接查询
select *
from classes c INNER JOIN students s
ON c.cid=s.scid
where c.cid=1

查询结果(查询出1班所有学生信息):

在这里插入图片描述

新建DAO,配置mapper:

Class.java
public interface ClassDAO {
    //根据班级编号,查询班级信息
    public Clazz queryClass(int classId);
}
ClassMapper.xml
    <resultMap id="classMap" type="Clazz">
        <id column="cid" property="classId"/>
        <result column="cname" property="className"/>
        <result column="cdesc" property="classDesc"/>
        <!--CLazz对象的stus属性是一个List集合,需要使用collection标签-->
        <!--collection标签的ofType属性声明集合中元素的类型-->
        <collection property="stus" ofType="Student">
            <result column="sid" property="stuId"/>
            <result column="sname" property="stuName"/>
            <result column="sage" property="stuAge"/>
        </collection>
    </resultMap>
    <select id="queryClass" resultMap="classMap">
        select cid,cname,cdesc,sid,sname,sage
        from classes c
        INNER JOIN students s
        ON c.cid = s.scid
        where c.cid = #{classId}
    </select>

一定不要忘了把mapper文件进行注册,注册到主配置文件mybatis-config中!!

创建测试类:

    @Test
    public void testQueryClass() {
        ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);
        Clazz clazz = classDAO.queryClass(1);
        System.out.println(clazz);
    }

运行结果:

Clazz(classId=1, className=Java1班, classDesc=666, stus=[Student(stuId=10001, stuName=Tom, stuAge=21, stuCid=0), Student(stuId=10003, stuName=HanMeimei, stuAge=19, stuCid=0), Student(stuId=10004, stuName=Lily, stuAge=18, stuCid=0)])

Process finished with exit code 0

子查询

再多新建一个StudentDAO

public interface StudentDAO {
    public List<Student> listStudentsByCid(int cid);
}

新建一个StudentMapper

    <resultMap id="studentMap" type="Student">
        <id column="sid" property="stuId"/>
        <result column="sname" property="stuName"/>
        <result column="sage" property="stuAge"/>
        <result column="scid" property="stuCid"/>
    </resultMap>
    <select id="listStudentsByCid" resultMap="studentMap">
        select sid,sname,sage,scid from students where scid=#{cid}
    </select>

对ClassMapper进行修改:

        <resultMap id="classMap" type="Clazz">
            <id column="cid" property="classId"/>
            <result column="cname" property="className"/>
            <result column="cdesc" property="classDesc"/>
            <collection property="stus" select="com.wyl.dao.StudentDAO.listStudentsByCid" column="cid"/>
        </resultMap>
        <select id="queryClass" resultMap="classMap">
            select cid,cname,cdesc
            from classes
            where cid = #{classId}
        </select>

运行成功。

11.5 多对一关联

案例:学生(n)和班级(1)

当查询一个学生的时候,关联查询这个学生所在的班级信息

11.5.1 创建实体类

(与一对多的实体类略有不同)

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
    private String stuId;   //学号
    private String stuName;
    private int stuAge;
    //private int stuCid;     //学生所在班级的id
    private Clazz clazz;    //学生所在班级
}

要在学生实体类下面添加一个Clazz类的对象属性,存放学生所在的班级对象。此处,由于班级对象里面包含的就有班级id,所以原来的stuCid可以删去。

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
    private int classId;
    private String className;
    private String classDesc;
    //private List<Student> stus;
}

此时,班级实体类中的“学生列表”对象属性就可以删去了,留着没什么意义了。

11.5.2 关联查询

连接查询

  • 此时,StudentDAO接口中实现的方法就不再是根据班级id查出班级下所有学生了,而是根据学生id查询出一条关联其班级信息的一条记录

    public interface StudentDAO {
        //public List<Student> listStudentsByCid(int cid);
        public Student queryStudentBySid(String sid);
    }
    
  • StudentMapper中对SQL语句作对应修改,映射配置

    -- 根据学号查询学生信息,同时关联查询到学生所属的班级信息
    -- 1.连接查询
    select *
    from students s INNER JOIN classes c
    ON s.scid=c.cid
    where s.sid='10001'
    
        <resultMap id="studentMap" type="Student">
            <id column="sid" property="stuId"/>
            <result column="sname" property="stuName"/>
            <result column="sage" property="stuAge"/>
            <result column="cid" property="clazz.classId"/>
            <result column="cname" property="clazz.className"/>
            <result column="cdesc" property="clazz.classDesc"/>
            
        </resultMap>
        <select id="queryStudentBySid" resultMap="studentMap">
            select sid,sname,sage,scid,cid,cname,cdesc
            from students s INNER JOIN classes c
            ON s.scid=c.cid
            where s.sid=#{sid}
        </select>
    
  • 测试类

        @Test
        public void testQueryStudentBySid() {
            StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class);
            Student student = studentDAO.queryStudentBySid("10001");
            System.out.println(student);
        }
    
  • 运行结果

    Student(stuId=10001, stuName=Tom, stuAge=21, clazz=Clazz(classId=1, className=Java1班, classDesc=666))
    
    Process finished with exit code 0
    

子查询

  • 映射文件

    • StudentMapper
        <resultMap id="studentMap" type="Student">
            <id column="sid" property="stuId"/>
            <result column="sname" property="stuName"/>
            <result column="sage" property="stuAge"/>
            <association property="clazz" select="com.wyl.dao.ClassDAO.queryClass" column="scid"/>
        </resultMap>
        <select id="queryStudentBySid" resultMap="studentMap">
            select sid,sname,sage,scid from students where sid=#{sid}
        </select>
    
    • ClassMapper
        <resultMap id="classMap" type="Clazz">
            <id column="cid" property="classId"/>
            <result column="cname" property="className"/>
            <result column="cdesc" property="classDesc"/>
        </resultMap>
        <select id="queryClass" resultMap="classMap">
            select cid,cname,cdesc from classes where cid=#{cid}
        </select>
    
  • 测试类运行结果

    Student(stuId=10001, stuName=Tom, stuAge=21, clazz=Clazz(classId=1, className=Java1班, classDesc=666))
    
    Process finished with exit code 0
    

子查询的思路就是,先做一个简单的单表查询,把学生信息查出来,但是我这个学生信息里面想包含一个Clazz类型的clazz属性,怎么获取它?再对classes表做一个单表查询,把clazz查出来。最后想办法把这两次查询联系起来就可以。

联系起来的方法就是resultMap里面的设置,如果你的这个对象属性是一个对象(单个),则使用标签,如果这个对象属性是一个集合(多个),则使用标签。

11.6 多对多关联

案例:学生(m)和课程(n)

11.6.1 创建数据表

应该有三张表。两张表分别存放两者的基本信息,还有一张表专门存放两表间记录的关联信息。(此处叫做选课表)

-- 学生信息表(如上)
-- 课程信息表
create table courses(
	course_id int primary key auto_increment,
    course_name varchar(50) not null
);
-- 选课信息表/成绩表(学号、课程号、成绩)
create table grades(
	sid char(5) not null,
    cid int not null,
    score int not null
);

先向新表中插入几条记录:

在这里插入图片描述

在这里插入图片描述

score为-1,表示该学生选了课但是还没有考试。不能记为0,因为有可能考试成绩为0分。

11.6.2 关联查询

查询学生时,同时查询出学生选择的课程

  • 创建实体类
Student实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
    private String stuId;   //学号
    private String stuName;
    private int stuAge;
    private List<Course> courses;   //学生选择的课程
}

Course实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Course {
    private int courseId;
    private String courseName;
}
  • 这一角度的具体操作不讲了,参考下面的另一个角度的实现过程即可

根据课程编号,查询课程时,同时查询选择了这门课程的学生

  • 创建实体类
Student实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
    private String stuId;   //学号
    private String stuName;
    private int stuAge;
}

Course实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Course {
    private int courseId;
    private String courseName;
    private List<Student> students; 
}

两种不同的业务需求,其实体类的实现略有不同。

如果两个操作都想完成,那么两边同时写List<>就可以了。但是不建议这样做,因为你包含我、我又包含你、你又包含我,很容易造成错误。所以一般根据业务需求,二选一去写就行了。

  • 创建DAO接口
public interface CourseDAO {
    public Course queryCourseById(int courseId);
}
  • 映射文件

连接查询

-- 连接查询
select *
from courses c INNER JOIN grades g INNER JOIN students s
ON c.course_id=g.cid and g.sid=s.sid
where c.course_id=1;
CourseMapper.xml
<mapper namespace="com.wyl.dao.CourseDAO">
    <resultMap id="courseMap" type="Course">
        <id column="course_id" property="courseId"/>
        <result column="course_name" property="courseName"/>
        <collection property="students" ofType="Student">
            <result column="sid" property="stuId"/>
            <result column="sname" property="stuName"/>
            <result column="sage" property="stuAge"/>
        </collection>
    </resultMap>
    <select id="queryCourseById" resultMap="courseMap">
        select course_id,course_name,s.sid,sname,sage
        from courses c INNER JOIN grades g INNER JOIN students s
        ON c.course_id=g.cid and g.sid=s.sid
        where c.course_id=#{courseId}
    </select>
</mapper>

子查询

CourseMapper.xml
<mapper namespace="com.wyl.dao.CourseDAO">
    <resultMap id="courseMap" type="Course">
        <id column="course_id" property="courseId"/>
        <result column="course_name" property="courseName"/>
    </resultMap>
    <select id="queryCourseById" resultMap="courseMap">
        select course_id,course_name
        from courses
        where course_id=#{courseId}
    </select>
</mapper>

这样写之后,能查是能查,只不过course中的students属性内容为null:

Course(courseId=1, courseName=Java, students=null)

Process finished with exit code 0

那我怎样获取到students的内容并将它放到resultMap里面呢。

执行第二次查询。

首先在StudentDAO中添加一个“根据课程id查询学生”的方法:

public interface StudentDAO {
    //public List<Student> listStudentsByCid(int cid);
    public Student queryStudentBySid(String sid);
    public List<Student> queryStudentsByCourseId(int courseId);	//添加这个
}

然后在StudentMapper中实现这个操作

在实现这个操作的时候,它不是一个简单的单表查询,而是也需要进行一个关联查询:

select s.sid,sname,sage from students s INNER JOIN grades g
ON s.sid=g.sid
where g.cid=1
StudentMapper.xml
	<resultMap id="studentMap2" type="Student">
        <id column="sid" property="stuId"/>
        <result column="sname" property="stuName"/>
        <result column="sage" property="stuAge"/>
        <association property="clazz" select="com.wyl.dao.ClassDAO.queryClass" column="scid"/>
    </resultMap>
    <select id="queryStudentsByCourseId" resultMap="studentMap2">
        select s.sid,sname,sage from students s INNER JOIN grades g
        ON s.sid=g.sid
        where g.cid=#{courseId}
    </select>

CourseMapper.xml
<mapper namespace="com.wyl.dao.CourseDAO">
    <resultMap id="courseMap" type="Course">
        <id column="course_id" property="courseId"/>
        <result column="course_name" property="courseName"/>
        <collection property="students" select="com.wyl.dao.StudentDAO.queryStudentsByCourseId" column="course_id"/>
    </resultMap>
    <select id="queryCourseById" resultMap="courseMap">
        select course_id,course_name
        from courses
        where course_id=#{courseId}
    </select>
</mapper>
  • 测试类
    @Test
    public void testQueryClass() {
        ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);
        Clazz clazz = classDAO.queryClass(1);
        System.out.println(clazz);
    }
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋秋秋叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值