【MyBatis】mybatis多对一、一对多、多对多

1. 表结构与关系分析

1. 数据表创建
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userid` int(11) NOT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

INSERT INTO `account` VALUES ('1', '1', '1000');
INSERT INTO `account` VALUES ('2', '1', '2000');
INSERT INTO `account` VALUES ('3', '2', '2000');
INSERT INTO `account` VALUES ('4', '3', '30');

DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `role` VALUES ('1', '狗头军师');
INSERT INTO `role` VALUES ('2', '千年咸鱼');
INSERT INTO `role` VALUES ('3', '吃瓜群众');

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

INSERT INTO `user` VALUES ('1', '张三', '18');
INSERT INTO `user` VALUES ('2', '李四', '21');
INSERT INTO `user` VALUES ('3', '张待遇', '30');
INSERT INTO `user` VALUES ('4', '张哈哈', '12');
INSERT INTO `user` VALUES ('5', '张大炮', '30');

DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userid` int(11) NOT NULL,
  `roleid` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `user_role` VALUES ('3', '1', '3');
INSERT INTO `user_role` VALUES ('4', '2', '1');
INSERT INTO `user_role` VALUES ('5', '2', '2');
INSERT INTO `user_role` VALUES ('6', '3', '1');
INSERT INTO `user_role` VALUES ('7', '5', '2');
1.2 表关联关系
  • 一个用户User拥有多个账户Account,属于一对多关系
  • 一个账户Account只属于一个User,属于多对一关系
  • 一个用户User拥有多个角色Role
  • 一个角色Role拥有多个用户User
  • 用户User与Role是多对多关系
  • User_Role是User表与Role表的中间表,等同于2个一对多

2. 多对一

2.1 实体间建立关系
  • 在从表Account实体类中添加主表User类型的属性
package com.domain;
public class Account {
    private  Integer id;
    private Double money;
    private  User user;  //添加主表User类型的属性

    public Integer getId() {return id;}
    public void setId(Integer id) { this.id = id;}
    public Double getMoney() {return money;}
    public void setMoney(Double money) { this.money = money;
    public User getUser() { return user; }
    public void setUser(User user) { this.user = user;}

    @Override
    public String toString() {
        return "Account{" + "id=" + id +", money=" + money +'}' ;
    }
}
2.2 方式一:先查从表再根据外键id查主表
  • 在IUserDao.xml中添加findById方法
<!-- 根据主键查询User -->
 <select id="findById" parameterType="int" resultType="user" >
     select * from user where id=#{id}
 </select>
  • 在IAccountDao.xml中使用association获取主表信息
<!-- 自定义映射规则 -->
<resultMap id="accountMap" type="account">
    <!-- 根据外键id查询主表(一方)的信息,封装进从表(多方)的user属性中 -->
    <!-- association表示当前实体类引用的主表实体映射规则 -->
    <!-- property表示关联主表的实体属性名称(Account类中的user属性) -->
    <!-- select属性表示调用的查询方法 -->
    <!-- column表示传递给查询方法的参数列的名称 -->
    <!-- select属性的值,如果是不同映射文件的必须包含前缀包名,而且不能用别名,如果是同在一个映射文件的则可以直接使用方法名调用-->
    <association  property="user" column="userid"  select="com.dao.IUserDao.findById"></association>
</resultMap>

<!-- 1.该方法会先查询所有从表Account所有记录,然后通过accountMap的规则封装数据 -->
<!-- 
	 2.自动遍历Account查询结果,把每条记录的外键列userid的值传递给
	 IUserDao中的findById方法,把所对应的User信息查询出来,逐一封装到
	 Account的user属性中 
-->
<select id="findAll" resultMap="accountMap">
    select * from account;
</select>

2.3 方式二:直接外连接连表一次性查询

  • 只需修改IAccountDao.xml文件
<resultMap id="accountMap" type="account">
    <!-- 注意:使用方式二主表和从表每列和每个属性都必须声明,没有声明的列属性会变为NULL -->
    <id property="id" column="aid" ></id>
    <result property="money" column="money"></result>

    <!-- javaType表示从外连接查询的结果列中选择部分列映射成User -->
    <association property="user" column="userid" javaType="user">
        <!-- 注意:使用方式二主表和从表每列和每个属性都必须声明,没有声明的列属性会变为NULL -->
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="age" column="age"></result>
    </association>
</resultMap>

<select id="findAll" resultMap="accountMap">
    <!-- 使用外连接一次性把所有Acount信息和所对应的User都查出来 -->
    select u.*,a.id as aid,a.money from account a left join user u on u.id=a.userid;
</select>
  • 2.4 测试
List<Account> accountList = accountDao.findAll();
for (Account account : accountList) {
    System.out.println(account);
    //调用主表属性
    System.out.println(account.getUser());
}

3. 一对多

3.1 实体间建立关系
  • 在主表实体类User中添加从表Account实体类集合
package com.domain;
import java.util.ArrayList;
import java.util.List;

public class User {
    private Integer id;
    private String username;
    private Integer age;

    //一个User可以有多个Account
    //主表(一方)实体类引用多个从表(多方)实体类
    private List<Account> accountList=new ArrayList<Account>();

    public Integer getId() {return id; }
    public void setId(Integer id) {this.id = id;}
    public Integer getAge() {return age;}
    public void setAge(Integer age) {this.age = age;}
    public String getUsername() {return username;}
    public void setUsername(String username) {this.username = username;}
    public List<Account> getAccountList() {return accountList;}
    public void setAccountList(List<Account> accountList) {this.accountList = accountList;}

    @Override
    public String toString() {
        return "User{" +"id=" + id +", username='" + username + '\'' +", age=" + age +'}';
    }
}
3.2 方式一:先查主表再查从表
  • 在IAccountDao.xml中添加根据外键userid查询Account
<!-- 根据外键值查询 -->
<!-- 
	该方法是给主表封装从表集合用得,这里Account不需要设置user属性,所以只用了resultType,避免了主表和从表都查User信息,浪费表情
-->
<select id="findByUserid" resultType="account">
    select * from account where userid=#{userid};
</select>
  • 在IUserDao.xml中的collection使用column与select属性
<resultMap id="userMap" type="com.domain.User">
    <!-- 用方法一必须声明主键,否则会被collection的column覆盖掉,主键值会变NULL -->
    <id property="id" column="id"></id>

	<!-- 根据主键id查询从表(多方)的信息,封装进主表(一方)的accountList属性中 -->
    <!-- collection 表示当前实体类引用的从表实体集合的映射规则 -->
    <!-- property表示关联从表的实体属集合性名称(User类中的accountList属性) -->
    <!-- select属性表示调用的查询方法 -->
    <!-- column表示传递给查询方法的参数列的名称 -->
    <!-- select属性的值,如果是不同映射文件的必须包含前缀包名,而且不能用别名,如果是同在一个映射文件的则可以直接使用方法名调用-->
    <collection property="accountList"   column="id"  select="com.dao.IAccountDao.findByUserid"></collection>


<!-- 1.该方法会先查询所有主表User所有记录,然后通过userMap的规则封装数据 -->
<!-- 
	 2.自动遍历User查询结果,把每条记录的主键列id的值传递给
	 IAccountDao的findByUserid方法,把该记录所拥有的Account查询出来并封装进,逐一封装到User的accountList属性中
-->
<select id="findAll" resultMap="userMap">
    select * from user;
</select>
</resultMap>
3.3 方式二:直接外连接连表一次性查询
  • 只需修改IUserDao.xml文件
<resultMap id="userMap" type="com.domain.User">
    <!-- 注意:使用方式二主表和从表每列和每个属性都必须声明,没有声明的列属性会变为NULL -->
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="age" column="age"></result>
    
    <!-- ofType表示从外连接查询的结果列中选择部分列映射成Account -->
    <collection property="accountList" ofType="com.domain.Account">
        <!-- 注意:使用方式二主表和从表每列和每个属性都必须声明,没有声明的列属性会变为NULL -->
        <id property="id" column="aid"></id>
        <result property="money" column="money"></result>
    </collection>
</resultMap>


<select id="findAll" resultMap="userMap" >
    <!-- 使用外连接一次性把所有User信息和每个User所拥有的Account都查出来 -->
    select u.*,a.id as aid,a.money from user u 
    left join account a on  a.userid=u.id;
</select>
3.4 测试
List<User> list = userDao.findAll();
 for (User u : list) {
     System.out.println(u);
      for (Account a : u.getAccountList()) {
          System.out.println(a);
     }
     System.out.println("=========================================");
 }

4.多对多

4.1 概述
  • 多对多就是两个一对多,只是多了一个中间表,查询时只需内连接一下中间表即可,其他没有变化
4.3 Role对User的一对多
4.3.1 在Role实体类添加属性建立与User的关联关系
  • 在主表实体类Role中添加从表User实体类集合
package com.domain;

import java.util.ArrayList;
import java.util.List;

public class Role {
    private Integer id;
    private String rolename;

    //一个角色可以属于多个角色
    private List<User> userList=new ArrayList<User>();

    public Integer getId() {return id;}
    public void setId(Integer id) {this.id = id;}
    public String getRolename() {return rolename;}
    public void setRolename(String rolename) {this.rolename = rolename;}
    public List<User> getUserList() {return userList;}
    public void setUserList(List<User> userList) {this.userList = userList;}

    @Override
    public String toString() {
        return "Role{" +"id=" + id + ", rolename='" + rolename + '\'' + '}';
    }
}

4.3.2 方式一:先查主表再查从表
  • 在IUserDao.xml中添加根据外键roleid查询Role
<!-- 根据外键值查询 -->
<!--
    该方法是给主表封装从表集合用得,这里User不需要设置roleList属性,所以只用了resultType,避免了主表和从表都查User信息,浪费表情
-->
<select id="findByRoleid" resultType="user">
    select u.* from user u join user_role ur on ur.userid=u.id where ur.roleid=#{roleid}
</select>
  • 在IRoleDao.xml中的collection使用column与select属性
<resultMap id="roleMap" type="role">
   <!-- 用方法一必须声明主键,否则会被collection的column覆盖掉,主键值会变NULL -->
   <id property="id" column="id"></id>

   <!-- 根据主键id查询从表(多方)的信息,封装进主表(一方)的userList属性中 -->
   <!-- collection 表示当前实体类引用的从表实体集合的映射规则 -->
   <!-- property表示关联从表的实体属集合性名称(Role类中的userList属性) -->
   <!-- select属性表示调用的查询方法 -->
   <!-- column表示传递给查询方法的参数列的名称 -->
   <!-- select属性的值,如果是不同映射文件的必须包含前缀包名,而且不能用别名,如果是同在一个映射文件的则可以直接使用方法名调用-->
   <collection property="userList"   column="id"  select="com.dao.IUserDao.findByRoleid"></collection>
</resultMap>

<!-- 1.该方法会先查询所有主表Role所有记录,然后通过roleMap的规则封装数据 -->
<!--
    2.自动遍历Role查询结果,把每条记录的主键列id的值传递给
    IUserDao的findByRoleid方法,把该记录所拥有的Role查询出来并封装进,逐一封装到Role的roleList属性中
-->
<select id="findAll" resultMap="roleMap">
   select * from role
</select>
3.3 方式二:直接外连接连表一次性查询
  • 只需修改IRoleDao.xml文件
<resultMap id="roleMap" type="role">
   <!-- 注意:使用方式二主表和从表每列和每个属性都必须声明,没有声明的列属性会变为NULL -->
   <id property="id" column="id"></id>
   <result property="rolename" column="rolename"></result>

   <!-- ofType表示从外连接查询的结果列中选择部分列映射成User -->
   <collection property="userList" ofType="com.domain.User">
       <!-- 注意:使用方式二主表和从表每列和每个属性都必须声明,没有声明的列属性会变为NULL -->
       <id property="id" column="uid"></id>
       <result property="username" column="username"></result>
       <result property="age" column="age"></result>
   </collection>
</resultMap>

<select id="findAll" resultMap="roleMap">
   <!-- 使用外连接一次性把所有角色及其关联的用户查询出来 -->

   <!-- 推荐方式right join:无论涉及到多少个表的关联,只需在最后那里right join主表即可 -->
   select r.*,u.id as uid,u.username,u.age from user u
   join user_role ur on ur.userid=u.id
   right join role r on r.id=ur.roleid

   <!-- 不推荐left join:多表连接时主表左外连接后会被后面的连接与条件过滤掉,那些主表有值从表NULL行的记录就不见了,所以要一直left join -->
   select r.*,u.id as uid,u.username,u.age from role r
   left join user_role ur on ur.roleid=r.id
   left join user u on u.id=ur.userid

   <!-- 临时表通用方式:把多表查询结果放到临时表,最后主表再跟临时表外连接(left join与right join都可以) -->
   select r.*,t.id as uid,t.username,t.age from role r
   left join
   (
       select u.*,ur.roleid from user u
       join user_role ur on ur.userid=u.id
   ) t
   on t.roleid=r.id
</select>
4.4 User对Role的一对多
  • 跟Role对User的一对多一样,只是反过来而已,这里不再阐述

5.总结

5.1 方式一与方式二区别
  • 方式一是1+N次查询,方式二是1次查询
  • 方式 一sql语句简单好理解,方式二稍复杂且容易忘掉外连接导致数据丢失
  • 方式一只需要手动映射部分列,方式二必须主表从表的列都要手动映射(当然代码生成器或其他方式都可以解决这烦恼)
  • 方式一可用于懒加载,方式二不可以
  • 明确用到关联表信息就用方式二,不确定是否用到关联信息就用方式一
5.2 注意事项
  • <collection>的select属性要跟column配合组成了方式一,select是执行的dao方法,column是方法参数传递主键值,但这个column属性覆盖了主键列属性,变为NULL(蜜汁疑惑),所以需要手动显式声明<id>标签

  • <collection>的属性ofType属性组成了方式二,主表和从表的列属性映射都要手动设置,没有映射的属性值为NULL

  • 多表外连接时建议使用右外连接,参考3.3的3种外连接方式说明

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值