表与表之间的关系有一对一,一对多,以及多对多。而多对多关系通常会被分解为两个一对多关系。
下面将分别学习一对一和一对多,至于多对多,只需明白其分解方法即可。
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());
}
}
}