不使用外键,尝试多对多查询
我的上两篇博客总结了一下不使用外键的优缺点
但是我还没试过,今天尝试了一下,用难一点的多对多关系实验
一:工具:
springboot
mybatis
mysql
二:材料:
五张表:
user--用户表
role--角色表
permission--权限表
user-role表
permission-role表
其中,user-role表和permission-role表是意义上的中间表,就是没有外键的,其他三张是基本表
sql语句:
1 /* 2 Navicat MySQL Data Transfer 3 4 Source Server : root 5 Source Server Version : 50549 6 Source Host : localhost:3306 7 Source Database : shiro 8 9 Target Server Type : MYSQL 10 Target Server Version : 50549 11 File Encoding : 65001 12 13 Date: 2018-05-30 14:42:06 14 */ 15 16 SET FOREIGN_KEY_CHECKS=0; 17 18 -- ---------------------------- 19 -- Table structure for permission 20 -- ---------------------------- 21 DROP TABLE IF EXISTS `permission`; 22 CREATE TABLE `permission` ( 23 `pid` int(11) NOT NULL AUTO_INCREMENT, 24 `name` varchar(255) NOT NULL DEFAULT '', 25 `url` varchar(255) DEFAULT '', 26 PRIMARY KEY (`pid`) 27 ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; 28 29 -- ---------------------------- 30 -- Records of permission 31 -- ---------------------------- 32 INSERT INTO `permission` VALUES ('1', 'add', ''); 33 INSERT INTO `permission` VALUES ('2', 'delete', ''); 34 INSERT INTO `permission` VALUES ('3', 'edit', ''); 35 INSERT INTO `permission` VALUES ('4', 'query', ''); 36 37 -- ---------------------------- 38 -- Table structure for permission_role 39 -- ---------------------------- 40 DROP TABLE IF EXISTS `permission_role`; 41 CREATE TABLE `permission_role` ( 42 `rid` int(11) NOT NULL, 43 `pid` int(11) NOT NULL, 44 KEY `idx_rid` (`rid`), 45 KEY `idx_pid` (`pid`) 46 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 47 48 -- ---------------------------- 49 -- Records of permission_role 50 -- ---------------------------- 51 INSERT INTO `permission_role` VALUES ('1', '1'); 52 INSERT INTO `permission_role` VALUES ('1', '2'); 53 INSERT INTO `permission_role` VALUES ('1', '3'); 54 INSERT INTO `permission_role` VALUES ('1', '4'); 55 INSERT INTO `permission_role` VALUES ('2', '1'); 56 INSERT INTO `permission_role` VALUES ('2', '4'); 57 58 -- ---------------------------- 59 -- Table structure for role 60 -- ---------------------------- 61 DROP TABLE IF EXISTS `role`; 62 CREATE TABLE `role` ( 63 `rid` int(11) NOT NULL AUTO_INCREMENT, 64 `rname` varchar(255) NOT NULL DEFAULT '', 65 PRIMARY KEY (`rid`) 66 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 67 68 -- ---------------------------- 69 -- Records of role 70 -- ---------------------------- 71 INSERT INTO `role` VALUES ('1', 'admin'); 72 INSERT INTO `role` VALUES ('2', 'customer'); 73 74 -- ---------------------------- 75 -- Table structure for user 76 -- ---------------------------- 77 DROP TABLE IF EXISTS `user`; 78 CREATE TABLE `user` ( 79 `uid` int(11) NOT NULL AUTO_INCREMENT, 80 `username` varchar(255) NOT NULL DEFAULT '', 81 `password` varchar(255) NOT NULL DEFAULT '', 82 PRIMARY KEY (`uid`) 83 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 84 85 -- ---------------------------- 86 -- Records of user 87 -- ---------------------------- 88 INSERT INTO `user` VALUES ('1', 'admin', '123'); 89 INSERT INTO `user` VALUES ('2', 'demo', '123'); 90 91 -- ---------------------------- 92 -- Table structure for user_role 93 -- ---------------------------- 94 DROP TABLE IF EXISTS `user_role`; 95 CREATE TABLE `user_role` ( 96 `uid` int(11) NOT NULL, 97 `rid` int(11) NOT NULL, 98 KEY `idx_uid` (`uid`), 99 KEY `idx_rid` (`rid`) 100 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 101 102 -- ---------------------------- 103 -- Records of user_role 104 -- ---------------------------- 105 INSERT INTO `user_role` VALUES ('1', '1'); 106 INSERT INTO `user_role` VALUES ('2', '2');
这里补充一下,两张中间表我既没有设外键,也没有设置主键
因为我觉得,用外键时,他们都是联合主键,现在我把外键去了,主键给谁都不对,如果另设一个属性作为主键我觉得也没必要
这里大家有建议欢迎提出,我也是第一次这样做,或许我觉得没必要的地方还是有必要的。
三:直接使用mybatis逆向工程生成bean和mapper
四:如何表示多对多关系?
我的上两篇博客中提到了,如果放弃外键,那么需要自己手动维护表与表之间的关联关系。
比如说,User实体类中应该有一个roles的集合,而Role实体类里也应该有一个users的集合。
但在数据库表中,user表和role表并没有相应的属性。因为多对多要通过中间表关联嘛。可惜,中间表user-role和这两表没有任何联系。至少现在是这样的。
那么,怎么搞?
要roles集合,我就给你呗!
在User实体类中加上下面这个不就行了,联系不就有了,找的时候set进去不ok了嘛
private Set<Role> roles = new HashSet<>();
这样真的好吗?
不好吧!上面已经说过,所有的bean和mapper都是mybatis逆向工程自动生成。等哪天,我数据库表一变,比如某些表加个其他属性,我又重新逆向工程生成全部bean和mapper,我又之前忘记了在这张表加了个什么,那张表加了什么,东西很难回去了,这就麻烦了。
所以,建议:
创建包装类,包装实体类,再在包装类增加其他属性
1 public class UserVo extends User { 2 3 private Set<Role> roles = new HashSet<>(); 4 5 public Set<Role> getRoles() { 6 return roles; 7 } 8 9 public void setRoles(Set<Role> roles) { 10 this.roles = roles; 11 } 12 }
这样,不管实体类怎么变化,包装类就继承你,什么也不用另外变化
同理,其他也这样弄
-----------------------------------------分割线---------------------------------------------
等等,这里,我先捋一下关系
user和role多对多
permission和role多对多
意思是说:
user里要有roles集合;
role里要有users集合,role里还要有permissions集合;
permission里要有roles集合;
关键的地方来了,我在User实体类里不能放 Set<Role> roles = new HashSet<>()
为什么?
我是要在roles里取到permissions或者users的,那样写代码大概是 user.getRoles().......getPermissions()
这是逼我在Role实体类里加一个 private Set<Permission> permissions= new HashSet<>() 啊,这不是上面刚说的不能这样写的吗?
怎么办?
我能放一个实体类作为泛型的类型,为什么不能放一个包装类作为泛型的类型,两者本质没有任何区别!
User实体类的包装类:
1 public class UserVo extends User { 2 3 private Set<RoleVo> roleVos = new HashSet<>(); 4 5 public UserVo() { 6 } 7 8 public Set<RoleVo> getRoleVos() { 9 return roleVos; 10 } 11 12 public void setRoleVos(Set<RoleVo> roleVos) { 13 this.roleVos = roleVos; 14 } 15 16 @Override 17 public String toString() { 18 return "UserVO{" + 19 "uid=" + super.getUid() + 20 ", username=" + super.getUsername() + 21 ", password=" + super.getPassword() + 22 ", roleVos.size=" + roleVos.size() + 23 '}'; 24 } 25 }
Role实体类的包装类
1 public class RoleVo extends Role { 2 3 private Set<PermissionVo> permissionVos = new HashSet<>(); 4 5 private Set<UserVo> userVos = new HashSet<>(); 6 7 public Set<PermissionVo> getPermissionVos() { 8 return permissionVos; 9 } 10 11 public void setPermissionVos(Set<PermissionVo> permissionVos) { 12 this.permissionVos = permissionVos; 13 } 14 15 public Set<UserVo> getUserVos() { 16 return userVos; 17 } 18 19 public void setUserVos(Set<UserVo> userVos) { 20 this.userVos = userVos; 21 } 22 23 @Override 24 public String toString() { 25 return "RoleVo{" + 26 "rid=" + super.getRid() + 27 ", rname=" + super.getRname() + 28 ", permissionVos=" + permissionVos + 29 ", userVos.size=" + userVos.size() + 30 '}'; 31 } 32 }
Permission实体类的包装类
1 public class PermissionVo extends Permission { 2 3 private Set<RoleVo> roleVos = new HashSet<>(); 4 5 public Set<RoleVo> getRoleVos() { 6 return roleVos; 7 } 8 9 public void setRoleVos(Set<RoleVo> roleVos) { 10 this.roleVos = roleVos; 11 } 12 13 @Override 14 public String toString() { 15 return "PermissionVo{" + 16 "pid=" + super.getPid() + 17 ", pname=" + super.getName() + 18 ", url=" + super.getUrl() + 19 ", roleVos.size=" + roleVos.size() + 20 '}'; 21 } 22 }
五:实现需求:
查询一个用户,并把当前用户拥有的角色和角色所具有的权限一并查出
1 @Test 2 public void fun() { 3 // 创建三个包装类 4 UserVo userVo = new UserVo(); 5 RoleVo roleVo = null; 6 PermissionVo permissionVo = null; 7 8 // 根据用户uid查询用户 9 User user = userMapper.selectByPrimaryKey(1); 10 11 // 装配user属性-- id, username, password,还有一个roleVos在下面 12 BeanUtils.copyProperties(user, userVo); 13 14 // 通过user的 uid 查询中间表 user-role表 15 UserRoleExample userRoleExample = new UserRoleExample(); 16 userRoleExample.createCriteria().andUidEqualTo(user.getUid()); 17 // 得到该用户的角色集合 18 List<UserRole> userRoles = userRoleMapper.selectByExample(userRoleExample); 19 // 遍历角色集合 20 for (UserRole userRole : userRoles) { 21 // 通过角色rid, 查询role表中对应的每一个角色 22 Role role = roleMapper.selectByPrimaryKey(userRole.getRid()); 23 24 // 装配roleVo属性 -- rid, rname,还有一个permissionVos在下面 25 roleVo = new RoleVo(); 26 BeanUtils.copyProperties(role, roleVo); 27 28 // 又通过每一个role的rid,查询中间表 permission_role 表 29 PermissionRoleExample permissionRoleExample = new PermissionRoleExample(); 30 permissionRoleExample.createCriteria().andRidEqualTo(role.getRid()); 31 // 得到该角色具有的权限集合 32 List<PermissionRole> permissionRoles = 33 permissionRoleMapper.selectByExample(permissionRoleExample); 34 // 遍历权限集合 35 for (PermissionRole permissionRole : permissionRoles) { 36 // 通过permission的pid,查询 permission表 对应的权限 37 Permission permission = permissionMapper.selectByPrimaryKey(permissionRole.getPid()); 38 39 // 装配permissionVo属性 -- pid, name, url, roleVos 40 permissionVo = new PermissionVo(); 41 BeanUtils.copyProperties(permission, permissionVo); 42 permissionVo.getRoleVos().add(roleVo); 43 44 // 装配roleVo里的permissionVos 45 roleVo.getPermissionVos().add(permissionVo); 46 } 47 // 装配userVo属性roleVos 48 userVo.getRoleVos().add(roleVo); 49 } 50 System.out.println(userVo); 51 System.out.println(roleVo); 52 System.out.println(permissionVo); 53 }
结果:
1 UserVO{ 2 uid=1, 3 username=admin, 4 password=123, 5 roleVos.size=1 6 } 7 8 RoleVo{ 9 rid=1, 10 rname=admin, 11 permissionVos=[ 12 PermissionVo{pid=4, pname=query, url=, roleVos.size=1}, 13 PermissionVo{pid=2, pname=delete, url=, roleVos.size=1}, 14 PermissionVo{pid=1, pname=add, url=, roleVos.size=1}, 15 PermissionVo{pid=3, pname=edit, url=, roleVos.size=1} 16 ], 17 userVos.size=0 18 } 19 20 PermissionVo{ 21 pid=4, 22 pname=query, 23 url=, 24 roleVos.size=1 25 }
小小总结一下:
1. 别看上面实现的代码挺多的,去掉注释也就30行左右,只要不绕进去,逻辑还是不难的(废话)
2.善用包装类
3.注意看三个包装类的toString方法,有些我是只给打印长度的,比如roleVos.size(),为什么,全部打印出来会死循环,好好想想为什么