1.mybatis中的多表查询
表之间的关系有几种:
一对多
多对一
一对一
多对多
举例:
一个用户可以下多个订单 : 一对多
多个订单属于同一个用户 : 多对一
一个人只能有一张身份证 : 一对一
多个学生可以被多个老师教过 : 多对多
多个老师可以教多个学生
特例:
如果拿出每一个订单,他都属于每一个用户
所以Mybatis就把多对一看成了一对一。
mybatis中的多表查询:
示例:用户和账户
一个用户可以有多个账户
一个账户只能属于一个用户(多个账户也可以属于同一个用户)
步骤:
1.建立两张表:用户表,账户表
让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
2.建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出来一对多的关系
3.建立两个配置文件
用户的配置文件
账户的配置文件
4.实现配置
当我们查询用户时,可以同时得到用户下所包含的账户信息
当我们查询账户时,可以同时得到账户的所属用户信息。
1.1 查询所有账户,并且带有所属用户的用户名称和地址信息
1.1.1 代码实现1: 不常用的方式
* sql:select a.*, u.username, u.address from user u, account a where u.id = a.uid
```
* 1.Account表对应的实体类
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
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 +
'}';
}
}
* 2.查询得到的所属用户的用户名称和地址信息封装成到:AccountUser类
public class AccountUser extends Account {
//AccountUser继承Account只是为了能打印出Account表的信息
//将查询得到的username和address封装到这个类中
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 + '\'' +
'}';
}
}
3. IAccountDao
List<AccountUser> findAllAccount();
4. AccountTest
/**
* 测试查询所有账户,并且带有所属用户的用户名称和地址信息
* select a.*, u.username, u.address from user u, account a where u.id = a.uid
* @return
*/
@Test
public void testFindAllAccountUser(){
List<AccountUser> accountUsers = accountDao.findAllAccount();
for (AccountUser accountUser : accountUsers) {
System.out.println(accountUser);
}
}
5.IAccountDao.xml
<!--查询所有账户,并且带有用户名称和地址信息-->
<select id="findAllAccount" resultType="com.itheima.domain.AccountUser">
select a.*, u.username, u.address from user u, account a where u.id = a.uid
</select>
```
1.1.2 代码实现2: 常用的方式
* 从表实体应该包含一个主表实体的对象引用
使用resultMap在IAccountDao.xml定义专门的resultMap用于映射一对一查询结果
我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。
```
* 1.Account表对应的实体类
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
* 2.IAccountDao
List<AccountUser> findAllAccount();
* 3.IAccountDao.xml
<resultMap id="accountUserMap" type="com.itheima.domain.Account">
<!--主键字段的对应-->
<!--这里account的id列在下面的sql中起了别名,所以这里也用别名-->
<id property="id" column="aid"></id>
<!--非主键字段的对应-->
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系映射:配置封装user表中的信息-->
<!--javaType指明信息封装到哪个对象-->
<association property="user" column="uid" javaType="com.itheima.domain.User">
<!--上面的column属性可写可不写 但是在延迟或者立即加载那里需要写上 因为要用他做下一次查询-->
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
<!--查询所有方法-->
<select id="findAll" resultMap="accountUserMap">
<!--因为这里的结果:user表中出现了id列,account表中也有id列,所以我们为其中一个id列起别名a.id as aid-->
select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id = a.uid;
</select>
```
1.2 一对多
需求:查询所有用户,同时获取用户下所有账户的信息
1.SQL语句:不能用内连接,要用左外连接(将LEFT OUTER JOIN左边的数据都查询出来)。
内连接:必行
SELECT * FROM USER u, account a WHERE u.id = a.uid
左外连接:
SELECT u.*, a.`ID` AS aid, a.`MONEY`, a.`UID` FROM USER u LEFT OUTER JOIN account a ON u.id = a.`UID`
或者右连接也行:
SELECT a.`ID` AS aid, a.`MONEY`, a.`UID`, u.* FROM account a RIGHT OUTER JOIN USER u ON u.id = a.`UID`
2. User实体类
//一对多关系映射:每个用户可能有多个账户
//主表实体应该包含从表实体的集合引用(因为是一对多)
private List<Account> accounts;
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.itheima.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<!--配置 user对象中account集合的映射-->
<collection property="accounts" ofType="com.itheima.domain.Account" column="id">
<!--上面的column属性可写可不写 但是在延迟或者立即加载那里需要写上 因为要用他做下一次查询-->
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</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>
1.3 多对多
SELECT u.*, r.id AS rid, r.role_name, r.role_desc FROM USER u LEFT OUTER JOIN user_role ur ON u.id = ur.uid LEFT OUTER JOIN role r ON r.id = ur.rid
2 延迟加载与立即加载
问题:在一对多情况中,当我们有一个用户,他有100个账户。
在查询用户:的时候,要不要把关联的账户查出老?
在查询账户的时候,要不要把关联的用户查出来?
1.查询用户:要不要把关联的账户查出老?
* 我们要做的功能是查询用户(user),但是由于我们用户里面关联了一个账户集合(accounts),
且这个用户有100个账户,我们一查用户就会把这100个账户也查出来。显然这对内存是一个非常大的
开销。
所以我们如果用不到这100个账户信息时,是完全不应该将它查询出来的。
既然没有查出来,拿到用的时候又该怎么办呢?
* 最好的解决办法:延迟加载
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询。
---------------------延迟加载
2.查询账户:要不要把关联的用户查出来?
通常情况下,在查询账户时,账户的所属用户信息,应该是随着账户查询时一起查询出来。
---------------------立即加载
3.延迟加载
在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
4.立即加载
不管用不用,只要一调用方法,马上发起查询加载。
5.在对应的四种表关系中:
一对多,多对多
关联的对象是多:延迟加载
多对一,一对一
关联的对象是一:立即加载
2.1 延迟加载案例需求
需求:
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可
满足要求,当需要时再去查询关联的用户(User)信息。
nyvatis第三天实现的多表操作时,我们使用了resultMap来实现一对一,一对多多关系的操作。主要
是通过association,collection实现一对一及一对多映射。而且association,collection具备延迟
功能。
注意:
既然是延迟加载,那么我们查询出来的结果就应该是查询账户(Account)的信息,再按需查询
用户(User)的信息。
sql:select * from account
而不是:select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id = a.uid;
2.1.1映射文件写法
<mapper namespace="com.itheima.Dao.IAccountDao">
<!--定义封装account和user的resultMap-->
<!--配置查询结果的列名和实体类的属性名的对应关系-->
<resultMap id="accountUserMap" type="com.itheima.domain.Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" column="uid" javaType="User"
select="com.itheima.Dao.IUserDao.findById">
</association>
</resultMap>
<select id="findAll" resultMap="accountUserMap">
select * from account
</select>
</mapper>这个是账户映射文件中
<select id="findById" parameterType="INT" resultType="User">
select * from user where id = #{id};
</select>这个是用户映射文件中
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.itheima.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<!--配置 user对象中account集合的映射-->
<!--注意:findAccountByUid需要的参数是user中的id-->
<collection property="accounts" ofType="account" select="com.itheima.Dao.IAccountDao.findAccountByUid" column="id">
</collection>
</resultMap>这个是用户映射文件中
<!--查询所有方法-->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM USER
</select>这个是用户映射文件中
<!--根据用户id查询账户列表-->
<select id="findAccountByUid" resultType="account">
<!--
注意这里我们没有parameterType但是仍然写了#{uid}
所以这个uid不是来自parameterType而是来自IUserDao.xml中调用该映射是穿过来的参数column="id",
而且这个是user中的id,对应着account中的uid字段。
-->
select * from account where uid = #{uid}
</select>这个是账户映射文件中
2.1.2全局配置文件写法
3 注解开发
sqlmap中的mappers配置
如果只有一个接口使用注解的话这么写没有问题
但是如果涉及多个接口使用注解的话这么写就要重复写多个 所以改成如下
注意:只要你使用了注解开发,同时你的配置文件下包含了对应的IUserDao.xml,就会报错。
他发现你用了注解,又用了IUserDao.xml,mybatis不知道该以谁为准。
如果一个dao用注解,那么这个dao就不会用IUserDao.xml
所以我们要么把IUserDao.xml移到不相关的配置文件,或者删掉。
即我们使用注解开发时,不能在同一个dao文件下使用IUserDao.xml。
3.2 java实体类的属性和表中字段不对应
1. 在Sql语句中起别名
显然不仅仅调用findAll()方法时要起别名,调用findByiD()方法也要起别名。
因为起别名只作用在一个SQL,一个方法上,不太好。
2. @Results注解
@Select("select * from user")
@Results(value={
@Result(id=true,column="id",property = "userId"),
@Result(column="username",property = "userName"),
@Result(column="sex",property = "userSex"),
@Result(column="address",property = "userAddress"),
@Result(column="birthday",property = "userBirthday"),
}, id="userMap")
List<User> findAll();
@Results 注解是一个 @Result 注解数组,
@Result注解在这里用到3个值:
id:默认值是false。 是不是主键,id列
column :数据库表的列名
property : 实体类的属性名
@Results :
id : 为 @Results指定一个唯一id值
3. 在其他方法处也可以引用这个注解:
注意:@ResultMap()的value是一个数组:
所以正确的写法:@ResultMap(value={"userMap"})
如果只有一个属性value,可以省略
如果value只有一个值:{}可以省略
@ResultMap("userMap")
3.3 多对一查询
积极加载
* Account实体类:
```
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
```
* IAccountDao:
```
@Select("select * from account")
@Results(id="accountMap", value={
@Result(id=true, column="id", property="id"),
@Result(column="uid", property="uid"),
@Result(column="money", property="money"),
@Result(property="user", column="uid", one=@One(select="com.itheima.dao.IUserDao.findById", fetchType= FetchType.EAGER)),
})
List<Account> findAll();
```
one=@One(select="com.itheima.dao.IUserDao.findById", fetchType= FetchType.EAGER):
one属性:是@Result中的属性,表示一对一关系,值是@One注解
@One注解:两个属性:
select:用于指定我们要调用的select映射信息
fetchType:一对一关系中,你所指定的加载方式(LAZY,EAGER,DEFAULT)
3.4 一对多查询