MyBatis笔记Day03_Mybatis多表查询

表与表之间的关系有一对一,一对多,以及多对多。而多对多关系通常会被分解为两个一对多关系。
下面将分别学习一对一和一对多,至于多对多,只需明白其分解方法即可。

1、一对一关系查询

一对一关系是表关系中相对简单的一种。也是查询起来最容易实现的一种。首先看一下两张表的结构。
在这里插入图片描述
在这里插入图片描述
     按照现实生活的经验,可以得出,一个账户只能对应一个用户,而一个用户可能有多个账户,当然也可能没有账户。这里账户与用户的关系就是一对一的关系,即一个账户只属于一个用户。

     那么在Mybatis中怎么实现相关的查询呢?首先明确一下需求:查询所户,并获取账户对应的用户信息。

     明确了需求,就可以先编写sql语句了。通过表结构,可以发现,两个表是通过用户的id进行关联的,因此连接的条件是account.uid = user.id。下面是完整的sql语句。因为二者都有名为id的属性,因此,需要将其中一个起别名,这里给账户表的id起了aid的别名。

SELECT u.*, a.id as aid, a.UID, a.MONEY FROM account a, user u WHERE a.UID = u.id

检验一下这条语句,一切正常。那么在Mybatis中要怎么实现呢?这就是下面要解决的问题。
在这里插入图片描述
首先,创建一个Account实体类,并且向其中加入一个User对象。这样实体类中就包含了账户本来的属性和用户的属性。接下来的问题就是如何使用Mybatis将查询到了数据封装到这个实体类对象中去。

package cn.snowing.domain;

import java.io.Serializable;

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                ", user=" + user +
                '}';
    }
}

Mybatis提供了resultMap映射帮助我们对查询到的信息进行封装。

	<resultMap id="accountUserMap" type="account">
        <id property="id" column="aid"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!-- 一对一的关系映射:配置封装use的内容 -->
        <association property="user" column="uid" javaType="User">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
        </association>
    </resultMap>

    可以看到resultMap中的id和type属性,id是唯一确定这个resultMap的标识 符,在其它位置用id的值来使用这个resultMap。type指的是主表的对应的类型,这里的主表是account表。因此此处应该写的是Account实体类的全限定类名,因为我进行了别名的配置,所以直接写了account。

    resultMap的元素,这里有三个类型的元素,分别是:

  • id —id元素对应该表的主键id
  • result —result元素对应其它的属性值
  • association —association对应的就是从表

    这些元素共有的属性有:property和column,其中property对应实体类中的名称,而column对应查询结果中字段的名称或别名。需要注意的是 association中的property对应的是Account实体类中的User对象,column对应两表关联的字段,即account表中的uid。而javaType对应的则是从表user的实体类user,同样的,如果没有进行相关配置的话,这里应该是其全限定类名。

    配置好了resultMap之后,就可以对其进行调用了。这里的resultMap就是上面写的那个accountUserMap,这里的别名aid就是上面column中的aid。

    <select id="findAll" resultMap="accountUserMap">
        SELECT u.*, a.id as aid, a.UID, a.MONEY FROM account a, user u WHERE a.UID = u.id
    </select>

在做完上面的工作后,我们就可以查询到需求所需的信息了,接下来,我们需要编写一个测试类。

package cn.snowing.test;

import cn.snowing.dao.IAccountDao;
import cn.snowing.dao.IUserDao;
import cn.snowing.domain.Account;
import cn.snowing.domain.AccountUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class AccountTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IAccountDao accountDao;

    @Before
    public void init() throws IOException {
        //1.获取主配置文件输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取Dao的代理对象
        accountDao = sqlSession.getMapper(IAccountDao.class);
    }

    @After
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    @Test
    public void findAllTest(){
        List<Account> accounts = accountDao.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
 }

结果被封装到了Account实体类中,实现了功能。
在这里插入图片描述

2、一对多关系查询

    一对多与一对一有许多相通之处。上面提到,一个账户对应一个用户,而一个用户则可能对应多个账户,也可能没有账户。

  • 首先还是定义一下需求:查询所有用户和其名下的所有账户。

    有了需求就可以写SQL语句了,首先要明确的就是主表是user,从表是account,因为有的用户可能没有账户,但是仍然需要其信息。因此,在连接的时候就不能使用inner join(mysql默认连接方式相当表a, b),而要使用左外连接left outer join,保证即使没有账户的用户结果中也包含其信息。连接的条件是user.id = account.uid,注意一下两者的位置,虽然效果是一样的,但是所代表的含义是有区别的。

SELECT u.*, a.id as aid, a.uid, a.money FROM user u LEFT OUTER JOIN account a on u.id = a.UID;

在这里插入图片描述

    下面就是创建User实体类,并在其中加入Account对象,因为是一对多的关系,所以Account用一个List来存放。

package cn.snowing.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {
    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;
    // 1对多关系, 主表包含从表实体集合
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday='" + birthday + '\'' +
                '}';
    }
}

接着就是resultMap的编写。

    <resultMap id="userAccountMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <!-- 配置user对象中accounts集合的映射 -->
        <collection property="accounts" ofType="account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>

    基本上与一对一的配置相同,不同就是association变成了collection。细心的你可能发现了,association并没有column属性,而且没有了javaType,而是多了一个ofType属性。下面看一下两者之间的区别:

  • javaType用来指定对象所属的java数据类型,也就是private List<Post>posts 的ArrayList类型
  • ofType用来指定对象的所属javaBean类,也就是尖括号的泛型private List<Post>posts 中的posts

    想必你已经明白了,在collection中Mybatis通常能自动获取到javaType类型,因此只需要指定其泛型即可。

最后就是使用这个resultMap,与上面的一对一完全相同。

    <select id="findAll" resultMap="userAccountMap">
        SELECT u.*, a.id as aid, a.uid, a.money FROM user u LEFT OUTER JOIN account a on u.id = a.UID;
    </select>

测试类的编写

package cn.snowing.test;

import cn.snowing.dao.IAccountDao;
import cn.snowing.dao.IUserDao;
import cn.snowing.domain.Account;
import cn.snowing.domain.AccountUser;
import cn.snowing.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before
    public void init() throws IOException {
        //1.获取主配置文件输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取Dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    @Test
    public void findAllTest(){
        List<User> users = userDao.findAll();
        for (User user : users) {
            //每个用户的信息
            System.out.println("每个用户的信息:");
            System.out.println(user);
            System.out.println("账户:");
            System.out.println(user.getAccounts());
        }
    }

}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值