個人上兩篇博客總結了一下不使用外鍵的優缺點mysql
可是我還沒試過,今天嘗試了一下,用難一點的多對多關係實驗spring
一:工具:sql
springboot數據庫
mybatisspringboot
mysqlmybatis
二:材料:app
五張表:ide
user--用戶表工具
role--角色表ui
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(),爲何,所有打印出來會死循環,好好想一想爲何