Mybatis 多表查询
# 160.Mybatis 多表查询
之前我们都是查询单表,现在我们来讲下查询多表的情况
# 表之间的关系
表与表之间的关系有很多:
- 一对多,
- 多对一
- 一对一
- 多对多
我们举几个生活中的例子:
- 用户和订单就是一对多,订单和用户就是多对一,一个用户可以下多个订单,多个订单属于同一个用户
- 人和身份证号就是一对一,一个人只能有一个身份证号,一个身份证号只能属于一个人
- 老师和学生之间就是多对多,一个学生可以被多个老师教过,一个老师可以交多个学生
特例:如果拿出每一个订单,他都只能属于一个用户,所以 Mybatis 就把多对一看成了一对一。
# 示例 1:用户和账户
我们通过户和账户之间的关系,来学习 Mybatis 如何实现多表查询。
一个用户可以有多个账户,一个账户只能属于一个用户,多个账户也可以属于同一个用户。
步骤:
- 建立两张表:用户表,账户表,让用户表和账户表之间具备一对多的关系,并且在账户表有个外键,关联了用户的 ID。该步骤我们在一开始学习 Mybatis 的时候,已经创建过了,因此该步骤可以省略;
- 建立两个实体类:用户实体类和账户实体类,让用户和账户的实体类能体现出来一对多的关系
- 建立两个配置文件:用户的配置文件,账户的配置文件
- 实现配置:当我们查询用户时,可以同时得到用户下所包含的账户信息;当我们查询账户时,可以同时得到账户的所属用户信息
# 创建 Account 类
package com.peterjxl.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
}
2
3
4
5
6
7
注:请读者自行生成 setter 和 getter 方法
# 创建 IAccountDao 接口
package com.peterjxl.dao;
import com.peterjxl.domain.Account;
import java.util.List;
public interface IAccountDao {
List<Account> findAll();
}
2
3
4
5
6
7
# 创建 IAccountDao.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.peterjxl.dao.IAccountDao">
<select id="findAll" resultType="account">
select * from account
</select>
</mapper>
2
3
4
5
6
7
8
9
10
# 创建测试类 AccountTest
该测试类和我们之前的测试类,几乎一样,可以复制后修改下:
package com.peterjxl.test;
import com.peterjxl.dao.IAccountDao;
import com.peterjxl.domain.Account;
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 SqlSessionFactoryBuilder builder;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@Before
public void init() throws IOException {
// 1. 读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建SqlSessionFactory工厂
builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
// 3. 使用工厂生成SqlSession对象
session = factory.openSession();
// 4. 使用SqlSession创建Dao接口的代理对象
accountDao = session.getMapper(IAccountDao.class);
}
@After
public void destroy() throws IOException {
// 6. 释放资源
session.close();
in.close();
}
@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for (Account account : accounts){
System.out.println(account);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
此时我们运行下测试方法,应该是正常的;
# SQL 准备
此时,我们就可以开始写多表查询的代码了。
首先,我们想要的效果是:查询所有账户,并获取到当前账户的所属用户信息(用户名和住址)。我们先写 SQL:
SELECT a.*, u.username, u.address
FROM account a, user u
WHERE u.id = a.UID
2
3
查询结果如下:
ID UID MONEY username address
1 46 1000 小七 北京
2 45 1000 赵六 北京
3 46 2000 小七 北京
2
3
4
# 实现方式 1:通过写 Account 的子类实现
由于我们需要 Account 表的信息,并且还要用户名和住址,此时有一种方式就是,通过写一个 Account 类的子类,里面加上这两个成员变量,然后查询数据库并返回。
package com.peterjxl.domain;
public class AccountUser extends Account{
private String username;
private String address;
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;
}
@Override
public String toString() {
return super.toString() + " AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
接口 IAccountDao
中新增方法:
/**
* 查询所有账户,并带有用户名称和地址信息
*/
List<AccountUser> findAccountUser();
2
3
4
然后在 IUserDao.xml 中配置:
<!-- 查询所有账户,同时包含用户名和地址信息 -->
<select id="findAccountUser" resultType="accountUser">
SELECT a.*, u.username, u.address
FROM account a, user u
WHERE u.id = a.UID
</select>
2
3
4
5
6
我们新建一个测试方法:
@Test
public void testFindAccountUser(){
List<AccountUser> accounts = accountDao.findAccountUser();
for (AccountUser account : accounts){
System.out.println(account);
}
}
2
3
4
5
6
7
运行结果:
Account{id=1, uid=46, money=1000.0} AccountUser{username='小七', address='北京'}
Account{id=2, uid=45, money=1000.0} AccountUser{username='赵六', address='北京'}
Account{id=3, uid=46, money=2000.0} AccountUser{username='小七', address='北京'}
2
3
可以看到能正常查询出来的。
为了实现多表查询,还得写一个子类,这太不方便,几乎不用,我们可以用其他方式来实现多表查询。
# 实现方式 2:建立实体类之间的关系
如何在类中体现类之间的关系呢?我们可以在 Account 中新建一个成员变量 user,因为 account 是 user 的从表
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
2
3
4
5
6
7
注:自行生成 getter 和 setter 。
当我们在查询 account 的时候,我们希望将 user 的信息封装到 user 中;此时我们需要定义一个封装 account 和 user 的 resultMap,我们在 IAccountDao.xml 里新建一个:
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 建立一对一的关系映射,配置封装user的内容 -->
<association property="user" column="uid" javaType="user">
<id property="userId" column="id"/>
<result property="userName" column="username"/>
<result property="userAddress" column="address"/>
</association>
</resultMap>
2
3
4
5
6
7
8
9
10
11
我们用 association 标签,表明一对一的映射,property 表明查询结果封装到 account 的哪个属性中, column 属性表面两表之间用什么字段关联,javatype 表明实体类对应的类名。然后标签里写要封装的 user 属性
我们修改 findAll 标签中,查询的 SQL:
<select id="findAll" resultMap="accountUserMap">
SELECT a.*, u.username, u.address
FROM account a, user u
WHERE u.id = a.UID
</select>
2
3
4
5
我们在测试方法中,加上打印 user 属性的语句:
@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for (Account account : accounts){
System.out.print(account);
System.out.println(account.getUser());
}
}
2
3
4
5
6
7
8
执行结果:
Account{id=1, uid=46, money=1000.0}User{userId=1, userName='小七', userBirthday=null, userSex='null', userAddress='北京'}
Account{id=2, uid=45, money=1000.0}User{userId=2, userName='赵六', userBirthday=null, userSex='null', userAddress='北京'}
Account{id=3, uid=46, money=2000.0}User{userId=3, userName='小七', userBirthday=null, userSex='null', userAddress='北京'}
2
3
可以看到能正常封装 userName 和 userAddress 属性
# 一对多查询
例如,我们想要在查询所有用户的时候,同时查询到用户下所有账户的信息;我们先定义 SQL
select u.*, a.ID aid, a.UID, a.MONEY
from user u LEFT JOIN account a
on u.id = a.UID
2
3
我们需要在 User 类中添加一对多的映射,主表实体类应该包含从表实体类的集合引用;我们在 User 类中,新建一个成员变量 accounts
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
private List<Account> accounts;
}
2
3
4
5
6
7
8
注:自行生成 getter 和 setter
<resultMap id="userAccountMap" type="user">
<id property="userId" column="id"/>
<result property="userName" column="userName"/>
<result property="userAddress" column="address"/>
<result property="userSex" column="sex"/>
<result property="userBirthday" column="birthday"/>
<!-- 配置user对象中account集合的映射 -->
<collection property="accounts" ofType="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
2
3
4
5
6
7
8
9
10
11
12
13
我们用 collection 属性表示查询一个集合出来,ofType
表示集合中元素的类型;然后里面写 account 表的列
然后我们将 IUserDao.xml 的 findAll 标签,修改 resultMap:
<select id="findAll" resultMap="userAccountMap">
select u.*, a.ID aid, a.UID, a.MONEY
from user u LEFT JOIN account a
on u.id = a.UID
</select>
2
3
4
5
修改 MybatisTest
中的测试方法,增加打印 accounts 的语句:
@Test
public void helloMybatis() throws Exception{
List<User> users = userDao.findAll();
for(User user : users){
System.out.print(user);
System.out.println(user.getAccounts());
}
}
2
3
4
5
6
7
8
运行结果:可以看到 45 和 46 的用户,能成功查询出来 Accout 信息,其他用户则没有
# 源码
本文所有代码已上传到了 GitHub (opens new window) 和 Gitee (opens new window) 上,并且创建了分支 demo14,读者可以通过切换分支来查看本文的示例代码。