mybatis的多表关联查询
1.一对一
1.1 数据库建表
1.2 实体类与映射文件
1.3测试及相关说明
2.一对多
2.1 数据库建表
2.2 实体类与映射文件
2.3测试及相关说明
3.多对多
3.1 数据库建表
3.2 实体类与映射文件
3.3测试及相关说明
一对一查询
1.1数据库建表
这里建立的是订单与用户的一对一关联查询,因为这里是从订单表中取出其中的某一条订单来,那么对某一条具体的订单而言与用户表的关系就是一对一了,而如果从该订单的用户角度来看,还是一对多的关系,显然哪怕是具体到某一个用户也是可以对应多条订单。
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(32) NOT NULL COMMENT '用户名称',
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
`sex` CHAR(1) DEFAULT NULL COMMENT '性别',
`address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
# 创建账户表,外键为uid,关联用户表的id
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` INT(11) NOT NULL COMMENT '编号',
`uid` INT(11) DEFAULT NULL COMMENT '用户编号',
`money` DOUBLE DEFAULT NULL COMMENT '金额',
PRIMARY KEY (`id`),
KEY `FK_Reference_8` (`uid`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
1.2 编写实体类与映射文件
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String userAddress;
//在第一篇文章的基础上增加Account集合
private List<Account> accounts;
//省略set、get方法
}
//Account实体类
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
//AccountDao
public interface AccountDao {
//查询所有账户,并且带有用户名、地址等信息
List<Account> findAllAccount();
}
#查询账户并且查询出账户的同时要带有User的信息,这里可以使用内连接查询,因为账户一定会对应一个用户,不存在一条账户记录没有用户
#sql语句:
select u.*,a.id as aid,a.uid,a.money from user u,account a where u.id = a.uid
这里如果直接通过Account实体类中维护了User属性去查询会有问题,无法为User属性赋值,因为mybatis不知道User与数据库中表的字段的映射关系
<select id="findAllAccount" resultType="account">
select u.*,a.id as aid ,a.money,a.uid from user u,account a where u.id = a.uid
</select>
@Test
public void account() {
List<Account> accounts = accountDao.findAllAccount();
accounts.forEach(a -> System.out.println(a + " \n " + a.getUser()));
}
查询结果:
Account{id=46, uid=46, money=1000.0}
null
Account{id=45, uid=45, money=1000.0}
null
Account{id=46, uid=46, money=2000.0}
null
所以这里一般有两种解决方法:
创建一个新的实体类AccountUser,继承Account然后维护User中的每一个字段
public class AccountUser extends Account{
private String username;
private String address;
}
在AccountDao.xml的映射文件中将该方法的resultType类型指定为AccountUser,这样就可以满足需求查询到想要的数据了,但是这样的开发效率比较低,所以一般采用第二种方式,通过构造resultMap的方式
<!--定义封装account和user的resultMap-->
<resultMap id="accountUser" type="account">
<!--映射user的数据库字段和实体类的映射-->
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--一对一的关系映射:配置封装user的内容-->
<!--因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。
如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。-->
<association property="user" javaType="user">
<id property="id" column="id"/>
<result property="userAddress" column="address"/>
<result property="sex" column="sex"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
</association>
</resultMap>
#association:用来处理一对一的关系映射,可以代表一个java pojo类,也可以用map来封装查询出的结果
Account{id=1, uid=46, money=1000.0}
User{id=46, username='zxacvb', birthday=Tue May 19 20:39:18 CST 2020, sex='女', userAddress='h州'}
Account{id=2, uid=45, money=1000.0}
User{id=45, username='ghgajhk', birthday=Tue May 19 20:39:18 CST 2020, sex='男', userAddress='大苏打撒'}
Account{id=3, uid=46, money=2000.0}
User{id=46, username='zxacvb', birthday=Tue May 19 20:39:18 CST 2020, sex='女', userAddress='h州'}
这里有一点需要注意,在数据库表的字段中user表的主键名为id,account表的主键名同样也是id,那么这里其中有一方在查询的时候需要取别名,否则id会重复,比如account中某条记录id为10,对应user表中的某条记录id为11,那么这里只会为其中一个id赋值,另一个的id值就是之前的id值,假如先查询了user表,那么两个id都是11,先查询了account表,两个id都是10
<resultMap id="accountUser" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<association property="user" javaType="user">
<id property="id" column="id"/>
<result property="userAddress" column="address"/>
<result property="sex" column="sex"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
</association>
</resultMap>
<select id="findAllAccount" resultMap="accountUser">
select u.*,a.id as id ,a.money,a.uid from user u,account a where u.id = a.uid
</select>
查询结果如下:
Account{id=46, uid=46, money=1000.0}
User{id=46, username=‘zxacvb’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘女’, userAddress=‘h州’}
Account{id=45, uid=45, money=1000.0}
User{id=45, username=‘ghgajhk’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘男’, userAddress=‘大苏打撒’}
显然这样的记录是有问题的
一对多查询
这里从user表出发,某个用户可以对应多个account
2.1数据库建表如1.1中所示
2.2实体类与映射文件
实体类User中添加Account成员变量的集合List accounts,在实体类Account中添加User的成员变量,在1.2中已经完成了,下面来编写UserDao.xml的映射文件。
List<User> findUserWithAccount();
<select id="findUserWithAccount" resultMap="userAccountMap">
select u.*,a.id as aid,a.money,a.uid from user u left outer join account a on u.id = a.uid
</select>
#这里采用左外连接查询,因为一个user可以没有account,比如新注册的用户,所以需要查询所有的用户以及该用户名下的所有account的时候,需要使用左外连接查询,哪怕某用户名下没有account,也是需要查询的
#构建userAccountMap
<resultMap id="userRoleMap" type="user">
<id column="id" property="id"/>
<result property="userAddress" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<result property="username" column="username"/>
<collection property="roles" ofType="role">
<id property="roleId" column="rid"/>
<result property="roleName" column="role_name"/>
<result property="roleDesc" column="role_desc"/>
</collection>
</resultMap>
查询结果:
User{id=46, username=‘zxacvb’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘女’, userAddress=‘h州’, accounts=[Account{id=1, uid=46, money=1000.0}, Account{id=3, uid=46, money=2000.0}]}
User{id=45, username=‘ghgajhk’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘男’, userAddress=‘大苏打撒’, accounts=[Account{id=2, uid=45, money=1000.0}]}
User{id=41, username=‘张三’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘y’, userAddress=‘h州’, accounts=[]}
这里需要特别注意的是在一对多的关联查询中构建resultMap的标签与一对一稍稍有些不同,因为一对一的模型,从oop的角度来看是实体类A中维护了实体类B,实体类B中维护了实体类A,其实就spring中的循环依赖。而一对多是实体类A中维护了实体类B的集合,实体类B中维护了实体类A。
所以在一对一中使用的是association标签,同时在association标签中为实体类的类型赋值的标签是javaType,而在一对多中使用的标签是collection,为collection的元素的类型赋值的标签是ofType,这个在源码中应该也有相应的体现,等分析到源码了再回来补充。
多对多
多对多除了维护外键以外还需要用到一张中间表,这里采用用户和角色的模型,一个用户可以有多个角色,比如即可以是某一学院的老师同时也可以是校长,而某一个角色自然也相应可以有多个用户
3.1数据库建表
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` INT(11) NOT NULL COMMENT '编号',
`role_name` VARCHAR(30) DEFAULT NULL COMMENT '角色名称',
`role_desc` VARCHAR(60) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
# 添加角色数据
INSERT INTO `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) VALUES (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');
# 创建用户角色表,也就是中间表
# uid 和 rid是复合主键,同时也是外键
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`uid` INT(11) NOT NULL COMMENT '用户编号',
`rid` INT(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`uid`,`rid`),
KEY `FK_Reference_10` (`rid`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`rid`) REFERENCES `role` (`id`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
3.2实体类与映射文件
//在User类中新增一个Role的集合属性List<Role> roles;
public class Role {
private Integer roleId;
private String roleName;
private String roleDesc;
private List<User> users;
}
List<Role> findAll();
sql:多对多查询中sql语句要比一对多复杂一些,因为涉及到3张表的操作,首先根据用户表和中间表查询,条件是用户表的id与中间表的uid相同,这样就查询出了所有在用户表中有记录的用户,然后根据角色表的id和中间表的rid查询,这样就查询出了所有在中间表中有记录的角色表,结合这两个条件查询出来的就一定是用户和角色相对应的记录
<select id="findAll" resultMap="roleMap">
SELECT u.*,r.id AS rid,r.`role_desc`,r.`role_name` FROM role r
LEFT OUTER JOIN user_role ur ON r.id = ur.`rid` LEFT OUTER JOIN `user` u ON ur.`uid` = u.id
</select>
<!--定义role表的resultMap-->
<resultMap id="roleMap" type="role">
<id column="rid" property="roleId"/>
<result property="roleName" column="role_name"/>
<result property="roleDesc" column="role_desc"/>
<collection property="users" ofType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result column="address" property="userAddress"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
</collection>
</resultMap>
查询记录:
User{id=41, username=‘张三’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘y’, userAddress=‘h州’, accounts=null, roles=[Role{roleId=1, roleName=‘院长’, roleDesc=‘管理整个学院’, users=null}, Role{roleId=2, roleName=‘总裁’, roleDesc=‘管理整个公司’, users=null}]}
User{id=45, username=‘ghgajhk’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘男’, userAddress=‘大苏打撒’, accounts=null, roles=[Role{roleId=1, roleName=‘院长’, roleDesc=‘管理整个学院’, users=null}]}
User{id=42, username=‘李四i’, birthday=Tue May 19 20:39:18 CST 2020, sex=‘y’, userAddress=‘h州’, accounts=null, roles=[]}
多对多查询所使用的标签与一对多查询是一样的,因为从数据库操作的本质上来看对多对查询本身就是拆分成两次一对多查询,因为这里使用的是左外连接查询所以可以查出roles属性为null的user记录。
从角色表的角度来看,一个角色可以对应多个用户;从用户表的角度来看一个用户可以对应多个角色