一、前言
爲了避免浪費時間進行不必要的閱讀,這裏先對項目進行簡單的介紹。在實際應用場景中,每個用戶都有對應的角色,而每個角色又有對應的一些角色。因爲一個用戶可以有多個角色,一個角色也可以被多個用戶所擁有,角色和權限的關係也同理,這裏主要利用多對多的映射關係將他們聯繫起來,對他們進行管理。
主要實現的功能:
- 添加用戶、角色和權限
- 刪除用戶、角色和權限
- 給用戶添加角色、給角色添加權限
- 根據用戶名稱查詢用戶擁有的權限
二、項目環境
Java版本:jdk1.8.0_181
IDE:IntelliJ IDEA 2019.1.3
數據庫:postgresql 9.5
測試工具:postman
ps:數據庫類型不同不要緊,在創建的時候勾選不一樣的數據庫驅動的就行。
三、項目文件結構
項目創建的時候,需要勾選Web中的Spring Web Starter和SQL中Spring Data JPA、PostgreSQL Driver(如果使用的是mysql數據庫,則勾選MySQL Driver),IDEA會自動幫我們在Maven的配置文件中添加相關的依賴。
以下是本項目的目錄結構:
四、項目代碼
數據庫連接配置
- application.yml
spring:
datasource:
driver-class-name: org.postgresql.Driver
username: postgres
password: 123456
url: jdbc:postgresql://localhost:5432/postgres
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false
1.Entity層
Entity層爲數據庫實體層,一般一個實體類對應數據庫中的一張數據表,類中的屬性與數據表中的字段一 一對應。默認情況下,類名即爲數據表的表名,屬性名則是對應字段名,字段類型也與變量的類型相對應。
本層註解簡單解釋:
- @Entity
該註解用於表明這個類是一個實體類,會給他生成一張對應的數據表。 - @Table(name = “table_name”)
該註解主要用於修改表名,name的值就是修改的數據表的名稱。 - @Id
該註解用於聲明主鍵,標在哪個屬性上面對應的哪個字段就是主鍵 - @GeneratedValue(strategy = GenerationType.IDENTITY)
該註解的strategy屬性主要用於設置主鍵的增長方式,IDENTITY表示主鍵由數據庫自己生成,從1開始單調遞增。 - @Column(name = “column_name”)
該註解的name屬性用於更改數據表的列名,如果不想用默認的就用這個屬性改吧 - @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
這個註解得上是本項目得核心了,它聲明瞭實體之間的多對多關係,使兩張數據表關聯關聯起來,一般是通過生成一張映射表來實現這種映射關係。關於上面的cascade屬性和fetch屬性,有興趣的讀者可以查資料瞭解。 - @JoinTable
這個註解是配套@ManyToMany使用的,一般在多對多關係的維護端標註,用於生成上面提到的映射表。一般該註解常用三個屬性:name屬性表示生成的數據表的名稱,joinColumns屬性表示關係維護端的主鍵,inverseJoinColumns則表示關係被維護端的主鍵。關於嵌套在裏面的@JoinColumn註解,在這裏主要用於配置映射表的外鍵,一般有兩個屬性:name用於配置外鍵在映射表中的名稱,referencedColumnName 用於表明外鍵在原表中的字段名稱。 - @JsonBackReference
關於這個註解,建議先去掉試試然後再加上,對比一下效果。它主要可以使標註屬性避免被json序列化,進而避免多對多關係的查詢中出現死循環的情況。但是加上了這注解後,就不能進行反向查詢了(也就是說不能利用權限名查詢擁有這個權限的角色了)
注意:以下代碼都省略了要導入的包,getter和setter方法。需要導入相關包可以用快捷鍵Alt+Insert,用快捷鍵Alt+Insert然後選擇Getter and Setter可以快速生成相關方法。
- User.java
@Entity
@Table(name = "user_tabel")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Integer userId;
@Column(name = "user_name")
private String userName;
//關鍵點
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
name = "user_role", //name是表名
//joinColumns設置的是entity中屬性到關係表的映射名稱,name是映射表中的字段名
joinColumns = {@JoinColumn(name = "user_id")},
//inverseJoinColumns,name是關係實體Role的id在關係表中的名稱
inverseJoinColumns = {@JoinColumn(name = "role_id")}
)
private List<Role> roles;
//省略了getter和setter方法
}
- Role.java
@Entity
@Table(name = "role_table")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Integer roleId;
@Column(name = "role_name")
private String roleName;
//作爲被維護端,只需要設置mappedBy屬性,其值與User中對應List類型變量名相同
//@JsonBackReference可以避免屬性被json序列化,出現死循環
@JsonBackReference
@ManyToMany(mappedBy = "roles")
private List<User> users;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
name = "role_auth", //name是表名
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns =@JoinColumn(name = "auth_id")
)
private List<Authority> authorities;
//省略了getter和setter方法
}
- Authority.java
@Entity
@Table(name = "auth_table")
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "auth_id")
private Integer authorityId;
@Column(name = "auth_name")
private String authorityName;
@JsonBackReference
@ManyToMany(mappedBy = "authorities")
private List<Role> roles;
//省略了getter和setter方法
}
2.dao層
dao層是數據持久層,也被稱爲mapper層。主要負責訪問數據庫,向數據庫發送SQL語句,完成基礎的增刪查改任務。主要通過定義繼承JpaRepository類的接口來實現,<>中填寫的是實體類的名稱和該實體主鍵的變量類型。
在接口中聲明的方法不用我們去實現,只要滿足命名規則JpaRepository類會自動幫我們生成相應的sql語句。
詳情見:官方文檔
- UserRepository.java
public interface UserRepository extends JpaRepository<User, Integer> {
public List<User> findAllByUserName(String userName);
public void deleteByUserName(String userName);
}
- RoleRepository.java
public interface RoleRepository extends JpaRepository<Role, Integer> {
public List<Role> findAllByRoleName(String roleName);
public void deleteByRoleName(String roleName);
}
- AuthorityRepository.java
public interface AuthorityRepository extends JpaRepository<Authority, Integer> {
public List<Authority> findAllByAuthorityName(String authorityName);
public void deleteByAuthorityName(String authorityName);
}
3.service層
service層是業務邏輯層,主要通過調用dao層的接口,接收dao層返回的數據,完成項目的基本功能設計。由於本項目的service層是在後面才加的,所以有些應該在本層實現的功能寫在了controller層orz。
踩到的坑
- 涉及到兩張表以上的更新或者刪除操作,爲了保證數據庫的一致性,需要添加 @Transactional事務註解,否則程序會拋出異常。(關於事務的詳情,如果不熟悉的話,強烈建議去弄懂。)
- 如果要執行刪除操作,需要先把它的List先清空,也就相當於把映射表中的關係清除。否則會拋出org.hibernate.exception.ConstraintViolationException異常。(我這裏用到了多種清除方式:如果刪除維護端數據,只是把維護端的List清空就行;如果刪除被維護端的數據,則把用戶(維護端)的List中要移除的角色(被維護端)都remove掉,不知道我是不是想多了)
- EntityService.java
@Service
public class EntityService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Transactional
public void deleteUser(String userName) {
List<User> users = userRepository.findAllByUserName(userName);
//如果刪除維護端數據,只是把維護端的List清空
for(User user : users) {
user.getRoles().clear();
userRepository.save(user); //執行save()之後纔會保存到數據庫中
}
userRepository.deleteByUserName(userName);
}
@Transactional
public void deleteRole(String roleName) {
List<Role> roles = roleRepository.findAllByRoleName(roleName);
List<User> users = userRepository.findAll();
for (User user : users) {
List<Role> userRole = user.getRoles();
for (Role role : roles) {
if (userRole.contains(role)) {
userRole.remove(role);
}
role.getAuthorities().clear();
roleRepository.save(role);
}
userRepository.save(user);
}
roleRepository.deleteByRoleName(roleName);
}
@Transactional
public void deleteAuthority(String authName) {
List<Authority> authorities = authorityRepository.findAllByAuthorityName(authName);
List<Role> roles = roleRepository.findAll();
//如果刪除被維護端的數據,則把用戶(維護端)的List中要移除的角色(被維護端)都remove掉
for (Role role : roles) {
List<Authority> roleAuthoritis = role.getAuthorities();
for (Authority authority : authorities) {
if (roleAuthoritis.contains(authority)) {
roleAuthoritis.remove(authority);
}
}
roleRepository.save(role);
}
authorityRepository.deleteByAuthorityName(authName);
}
}
4.controller層
controller層是控制層,其功能爲請求和響應控制,負責前後端交互,接受前端請求,調用service層,接收service層返回的數據,最後返回具體的頁面和數據到客戶端。
本層註解簡單解釋:
-
@RestController
Spring4之後新加入的註解,相當於@Controller + @ResponseBody。
@Controller 將當前修飾的類注入SpringBoot IOC容器,使得從該類所在的項目跑起來的過程中,這個類就被實例化。當然也有語義化的作用,即代表該類是充當Controller的作用
@ResponseBody 它的作用簡單來說說就是指該類中所有的API接口返回的數據,甭管你對應的方法返回Map或是其他Object,它會以Json字符串的形式返回給客戶端,根據嘗試,如果返回的是String類型,則仍然是String。 -
@RequestMapping("/user")
該註解用來處理請求地址的映射,可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作爲父路徑。 -
@Autowired
養成看源代碼的好習慣,在IDEA中按住Ctrl鍵點擊該註解,可以查看該註解的解析。我理解了一下,大概就是調用這個類的構造方法對這個類進行實例化操作。 -
@RequestParam(value = “userName”)
該註解可以獲取請求報文中的數據(數據一般以鍵值對方式傳輸),把然後把獲取到的數據複製給方法的參數中。例如上面就是獲取名爲"userName"的數據值。
再簡單介紹一下,增加用戶、角色和權限的操作。一般我們添加的時候,是先添加權限,再添加角色,最後添加角色(可以聯想一下,是不是先有權限才能給角色分配呀)。有些人會對如何關聯用戶和角色、角色和權限有疑惑(包括一開始的自己),在實體類中存在一個List對象,只要在其中添加對應的對象映射表中就會創建好映射關係。(可以看看下面添加的代碼,然後自己做實驗觀察現象)
只要這個不是你的第一個spring boot程序,相信你都看得懂。如果感覺功能不夠,讀者還可以自行添加。
- EntityController
@RestController
@RequestMapping("/user")
public class EntityController {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Autowired
private EntityService entityService;
/*
用戶部分的增刪查改
*/
@RequestMapping("/finduser")
public List<User> findByName(@RequestParam(value = "userName") String userName) {
return userRepository.findAllByUserName(userName);
}
@RequestMapping("/findalluser")
public List<User> findAllUser() {
return userRepository.findAll();
}
@RequestMapping("/adduser")
public List<User> addUser(@RequestParam(value = "userName") String userName,
@RequestParam(value = "roleName") String roleName) {
User user = new User();
Role role = roleRepository.findAllByRoleName(roleName).get(0);
user.setUserName(userName);
user.setRoles(new ArrayList<>());
user.getRoles().add(role);//給用戶設置權限
userRepository.save(user);
return userRepository.findAll();
}
/*
給用戶添加角色
*/
@RequestMapping("/adduserrole")
public List<User> addUserRole(@RequestParam(value = "userName") String userName,
@RequestParam(value = "roleName") String roleName) {
User user = userRepository.findAllByUserName(userName).get(0);
Role role = roleRepository.findAllByRoleName(roleName).get(0);
if (user.getRoles() == null) {
user.setRoles(new ArrayList<>());
}
user.getRoles().add(role);//給用戶設置權限
userRepository.save(user);
return userRepository.findAll();
}
@RequestMapping("/deleteuser")
public List<User> deleteUser(
@RequestParam(value = "userName") String userName) {
entityService.deleteUser(userName);
return userRepository.findAll();
}
/*
查詢用戶權限
*/
@RequestMapping("/getauth")
public Set<Authority> getAuthority(
@RequestParam(value = "userName") String userName) {
Set<Authority> authoritieSet = new HashSet<>();
User user = userRepository.findAllByUserName(userName).get(0);
for(Role role : user.getRoles()){
for(Authority authority : role.getAuthorities()) {
authoritieSet.add(authority);
}
}
return authoritieSet;
}
/*
角色部分的增刪查改
*/
@RequestMapping("/findallrole")
public List<Role> findAllRole() {
return roleRepository.findAll();
}
@RequestMapping("/addrole")
public List<Role> addRole(
@RequestParam(value = "roleName") String roleName,
@RequestParam(value = "authName") String authName) {
Role role = new Role();
Authority authority = authorityRepository.findAllByAuthorityName(authName).get(0);
role.setRoleName(roleName);
role.setAuthorities(new ArrayList<>());
role.getAuthorities().add(authority);
roleRepository.save(role);
return roleRepository.findAll();
}
/*
給角色添加權限
*/
@RequestMapping("/addroleauth")
public List<Role> addRoleAuth(
@RequestParam(value = "roleName") String roleName,
@RequestParam(value = "authName") String authName) {
Role role = roleRepository.findAllByRoleName(roleName).get(0);
Authority authority = authorityRepository.findAllByAuthorityName(authName).get(0);
if (role.getAuthorities() == null) {
role.setAuthorities(new ArrayList<>());
}
role.getAuthorities().add(authority);
roleRepository.save(role);
return roleRepository.findAll();
}
@RequestMapping("/deleterole")
public List<Role> deleteRole(
@RequestParam(value = "roleName") String roleName) {
entityService.deleteRole(roleName);
return roleRepository.findAll();
}
/*
權限部分的增刪查改
*/
@RequestMapping("/findallauth")
public List<Authority> findAllAuthority() {
return authorityRepository.findAll();
}
@RequestMapping("/addauth")
public List<Authority> addAuthority(
@RequestParam(value = "authName" ) String authName) {
Authority authority = new Authority();
authority.setAuthorityName(authName);
authorityRepository.save(authority);
return authorityRepository.findAll();
}
@RequestMapping("/deleteauth")
public List<Authority> deletAuthority(
@RequestParam(value = "authName") String authName) {
entityService.deleteAuthority(authName);
return authorityRepository.findAll();
}
}
五、運行效果
寫得函數有點多,這裏挑選一部分來演示吧。
-
數據表
在程序運行之後,它會自動爲我們在數據庫中創建5張表,其中包括3個實體對應的數據表以及2張映射表。
-
查詢操作
由於先前已經進行了一些實驗,數據表中已經有了少量的數據,所以我們就現在演示查詢吧。
首先按照上文說的添加順序,先是權限的查詢。
接着是角色的查詢:
接着是用戶查詢:
最後,我們通過用戶名來查詢他擁有的權限。
-
增加角色操作
添加權限的操作很常規不做演示,添加用戶的操作和添加角色的操作差不多可以借鑑。
六、參考資料
- https://www.cnblogs.com/hhhshct/p/9492741.html
- https://liuyanzhao.com/7913.html
- https://blog.csdn.net/lidai352710967/article/details/83509821
- https://blog.csdn.net/H_Shun/article/details/78013017
- https://blog.csdn.net/Just_learn_more/article/details/90665009
- https://www.sojson.com/blog/295.html
- https://www.jianshu.com/p/6bbb5748ac83