2020 年 3 月 25 日

IT Skills 波林

Polin WEI – 資訊工作者的技術手札

JPA (Java Persistence API) Many-To-Many Relationship 在 Model 上的設置

4 min read
中興文化創意園區

JPA (Java Persistence API) @ManyToMany Relationship 在 Model 上的設置

 

以往要取得物件從屬關連時,常常需要大費週章的寫一堆程式,像下圖: User 透過 user_authority 擁有多個 Authority ( 權力 ) ,而以 Authority ( 權力 ) 角度來看,它的成員也有很多 User。在 JPA (Java Persistence API) 只要在 Model : User 及 Authority 作設定即可。有一點非常重要,就是 JPA 的 @ManyToMany 在 Model 設定時,不可以使用Eclipse 安装 lombok – 讓撰寫 JAVA 程式更加優雅簡潔 中 Lombok 的 @Data 來取代 Getter & Setter,否則會出現錯誤訊息:com.sun.jdi.InvocationException occurred invoking method

jpa ManyToMany

 

Spring Boot 配置 Maria Database 與交易控制 ( Transaction )中,我們已經配置好對於 MariaDB 資料庫的設定,實作 JPA Many-To-Many 之前,先建立上面這三個 Table : user, authority, user_authority 如下 SQL 指令,而這三個 Table 不需要建立 FOREIGN KEY,只要在 JPA 的 Entity: User & Authority 裡設定關連即可。

-- Temporarily disabling referential constraints
SET FOREIGN_KEY_CHECKS=0; 

-- initialize tables
DROP TABLE IF EXISTS `user_authority`;
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `authority`;

CREATE TABLE user (
 id bigint NOT NULL AUTO_INCREMENT,
 username VARCHAR(50) NOT NULL,
 password VARCHAR(100) NOT NULL,
 avatar VARCHAR(100),
 first_name VARCHAR(50) NOT NULL,
 last_name VARCHAR(50) NOT NULL,
 email VARCHAR(50) NOT NULL,
 enabled boolean,
 last_password_reset_date timestamp NOT NULL,
 active_date datetime,
 inactive_date datetime,
 create_date datetime DEFAULT CURRENT_TIMESTAMP,
 update_date datetime,
 create_user bigint,
 update_user bigint,
 CONSTRAINT user_pkey PRIMARY KEY (id),
 CONSTRAINT uk_username UNIQUE KEY(username)
);

CREATE TABLE authority (
 id bigint NOT NULL AUTO_INCREMENT,
 name VARCHAR(50) NOT NULL,
 description VARCHAR(100) NOT NULL,
 create_date datetime DEFAULT CURRENT_TIMESTAMP,
 update_date datetime,
 create_user bigint,
 update_user bigint,    
 CONSTRAINT authority_pkey PRIMARY KEY (id),
 CONSTRAINT uk_authority_name UNIQUE KEY(name)
);

CREATE TABLE user_authority (
 user_id bigint NOT NULL,
 authority_id bigint NOT NULL,
 create_date datetime DEFAULT CURRENT_TIMESTAMP,
 update_date datetime,
 create_user bigint,
 update_user bigint,    
 CONSTRAINT user_authority_pkey PRIMARY KEY (user_id,authority_id)
);


INSERT INTO user (id, username, password, first_name, last_name, email, enabled, last_password_reset_date, create_user) VALUES (1, 'admin', '$2a$10$WesIkZhjATHaeYFHZ7CvDOFhCBsq7VuafI54YCh4mkLAXv8n.v11i', 'pwd', 'admin', 'admin@admin.com', 1, STR_TO_DATE('01-01-2016', 'dd-MM-yyyy'), 1);
INSERT INTO user (id, username, password, first_name, last_name, email, enabled, last_password_reset_date, create_user) VALUES (2, 'user', '$2a$10$uQosgWuU/dQXzD06ULdXBOel59aNV7Sd7GXLIhmcKWVxxpxPbB2NC', 'pwd', 'user', 'enabled@user.com', 1, STR_TO_DATE('01-01-2016','dd-MM-yyyy'), 1);
INSERT INTO user (id, username, password, first_name, last_name, email, enabled, last_password_reset_date, create_user) VALUES (3, 'disabled', '$2a$10$uQosgWuU/dQXzD06ULdXBOel59aNV7Sd7GXLIhmcKWVxxpxPbB2NC', 'pwd', 'disabled', 'disabled@user.com', 0, STR_TO_DATE('01-01-2016','dd-MM-yyyy'), 1);

INSERT INTO authority (id, name, description) VALUES (1, 'ADMINS', '管理者');
INSERT INTO authority (ID, NAME, description) VALUES (2, 'USERS', '一般用戶');

INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1);
INSERT INTO user_authority (user_id, authority_id) VALUES (1, 2);
INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2);
INSERT INTO user_authority (user_id, authority_id) VALUES (3, 2);

-- Enable referential constraints
SET FOREIGN_KEY_CHECKS=1;

 

建立好三個 Table 後,只要建立兩個 Entity: User & Authority。

User.java

@Entity
public class User {

 @Id
 @GeneratedValue(strategy = IDENTITY)
 private Long id;
 
 private String username;
 private String password;
 private String avatar;
 private String first_name;
 private String last_name;
 private String email;
 private Boolean enabled;
 private Timestamp last_password_reset_date;
 private Timestamp active_date;
 private Timestamp inactive_date;
 private Timestamp create_date;
 private Timestamp update_date;
 private Long create_user;
 private Long update_user;
 
 @ManyToMany
 @JoinTable(name = "user_authority", 
  joinColumns = @JoinColumn(name = "user_id"),
  inverseJoinColumns = @JoinColumn(name = "authority_id") )
 private Set<Authority> authorities;
 
 // 後面記得要寫標準的 constructors, getters, and setters
 
}

Entity: User 這裡的 private Set<Authority> authorities; 要用來放入使用者的 Authority ( 權力 ) ,使用者的相關 Authority ( 權力 ) ,是放在 table: user_authority ,所以使用 @JoinTable 來連結,並且用 @JoinColumn(name = "user_id") 來與 Table: User 的 primary key: id  作連結,最後用 inverseJoinColumns = @JoinColumn(name = "authority_id") 反向連結到要取得的 Authority ( 權力 ) 。

Authority.java

@Entity
public class Authority {

 @Id
 @GeneratedValue(strategy = IDENTITY)
 private Long id;   
 private String name;
 private String description;
 private Timestamp create_date;
 private Timestamp update_date;
 private Long create_user;
 private Long update_user;
 
 @ManyToMany(mappedBy = "authorities")
 private Set<User> users ;

 // 後面記得要寫標準的 constructors, getters, and setters
}

Entity: Authority 在這裡只要設定 @ManyToMany(mappedBy = "authorities") ,利用 mappedBy 對映到 Entity: User 裡的 private Set<Authority> authorities; 即可。

最後,可以寫一個簡單的測試,這裡要加入 @Transactional 來產生一個新的 session ,用這新的 session 來另外取得 private Set<Authority> authorities; 裡的資料。

@SpringBootTest
public class BasicTests {
 private final Logger logger = LoggerFactory.getLogger(this.getClass());

 @Autowired
 UserRepository userRepo;
 
 @Test
 @Transactional
 void jpaRepository() {
  User u = userRepo.findByUsername("admin");
  logger.info(u.toString());
  List<Authority> userRoles = new ArrayList<Authority>();
  userRoles.addAll( u.getAuthorities() );
  
  for (Authority authority : userRoles) {
   logger.info(authority.toString());
  }
 }
}

 

若不想用 @Transactional ,也可以修改 User.java 裡 Entity @ManyToMany 的設定,加入 (fetch = FetchType.EAGER) 即可。

@Entity
public class User {

 @Id
 @GeneratedValue(strategy = IDENTITY)
 private Long id;
 
 private String username;
 private String password;
 private String avatar;
 private String first_name;
 private String last_name;
 private String email;
 private Boolean enabled;
 private Timestamp last_password_reset_date;
 private Timestamp active_date;
 private Timestamp inactive_date;
 private Timestamp create_date;
 private Timestamp update_date;
 private Long create_user;
 private Long update_user;
 
 @ManyToMany(fetch = FetchType.EAGER)
 @JoinTable(name = "user_authority",
  joinColumns = @JoinColumn(name = "user_id"),
  inverseJoinColumns = @JoinColumn(name = "authority_id") )
 private Set<Authority> authorities;

        // 後面記得要寫標準的 constructors, getters, and setters
}

 

 

參考: https://www.baeldung.com/jpa-many-to-many

 

Copyright © All rights reserved. | Newsphere by AF themes.