Korea_sleep_Tech_springBoot/src/main/java/com/example/korea_sleepTech_springboot at main · seoyoonchoi/Korea_sleep_Tech_springB
Contribute to seoyoonchoi/Korea_sleep_Tech_springBoot development by creating an account on GitHub.
github.com
실제 코드를 더 확인하고 싶다면 위 링크를 통해서 확인할 수 있다. 이 글에서는 간략하게 코드의 중요한 부분만 발췌하였다.
1. Entity란?
Entity는 객체 지향적으로 표현된 데이터베이스의 테이블이다.
JPA(Java Persistence API)에서는 Entity를 통해 자바 객체와 RDB 테이블 간 매핑을 자동으로 관리하게 된다.
즉, 데이터베이스를 직접 건드리지 않아도 엔티티 클래스를 통해 다음과 같은 작업을 할 수 있게 된다:
- 테이블 생성 / 수정
- CRUD 처리 (저장, 조회, 삭제 등)
- 관계 맵핑 (1:1, 1:N, N:1, N:M)
com.example.korea_sleepTech_springboot
├── entity
│ ├── User.java
│ ├── Role.java
│ ├── UserRole.java
│ ├── UserRoleId.java
│ └── RoleChangeLog.java
권한 인증을 위해 설계하는 entity는 이렇게 5개의 file 이 있으며 각각의 entity의 역할에 대해서 아래에서 계속 서술하겠다.
2. User 엔티티 — 인증 시스템의 핵심
User 엔티티는 Spring Security와 연결되어 있어 단순한 사용자 정보만을 담는 구조가 아니다.
이 엔티티는 UserDetails 인터페이스를 구현함으로써 Spring Security가 인식 가능한 사용자 객체가 된다.
주요 필드 및 기능
- id, email, password, createdAt, updatedAt: 기본적인 사용자 정보
- Set<Role> roles: 사용자 권한. 다대다(@ManyToMany) 관계로 여러 Role을 가질 수 있음
- getAuthorities(): 권한을 Spring Security 형식에 맞게 변환하여 반환
- getUsername(): 이메일을 username으로 사용하도록 지정
- 기타 계정 상태 관련 메서드: 계정 만료, 잠금 여부 등을 반환 (true로 고정)
설계에서의 포인트
- 생성자는 @Builder를 사용해 명확하게 생성 관리
- @NoArgsConstructor(access = AccessLevel.PROTECTED)로 외부에서 무분별한 객체 생성을 방지
- 즉시 로딩(EAGER) 설정을 통해 인증 후 권한까지 함께 조회되도록 최적화
// 다대다 관계 매핑
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;
//사용자 권한을 반환하는 메서드
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleName()))
.collect(Collectors.toSet());
}
// 계정 만료 여부(true: 만료되지 않음)
@Override public boolean isAccountNonExpired() { return true; }
// 계정 잠김 여부(true: 잠기지 않음)
@Override public boolean isAccountNonLocked() { return true;}
// 인증 정보 만료 여부
@Override public boolean isCredentialsNonExpired() {return true; }
// 계정 활성화 여부
@Override public boolean isEnabled() { return true;}
}
3. Role 엔티티 — 권한 도메인
Role은 시스템 내에서 부여될 수 있는 권한을 의미한다.
예: ROLE_USER, ROLE_ADMIN 등
- id, roleName: 기본 정보
- roleName은 유일해야 하므로 unique = true로 설정
- @Builder 사용으로 객체 생성을 유연하게 처리
이처럼 역할은 유저와의 관계를 통해 인증 흐름을 완성시킨다.
@Entity
@Table(name = "roles")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
// 기본 생성자를 protected로 생성
// : JPA에서 필요로 함
@Getter
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id") // DB 컬럼 이름과 매핑
private Long id;
@Column(name = "role_name", nullable = false, unique = true)
private String roleName;
@Builder
public Role(String roleName) {
this.roleName = roleName;
}
}
4. UserRole & UserRoleId — 다대다 관계의 세분화
JPA에서는 @ManyToMany만으로도 다대다 관계 매핑이 가능하다.
@ManyToMany
@JoinTable(name = "user_roles", ...)
이 방식은 **중간 테이블(user_roles)**을 자동으로 생성하고 관리해주기 때문에, 단순한 유저-역할 연결에는 충분하다.
그러나 현실에서는 이 중간 테이블에 다음과 같은 추가적인 정보가 필요한 경우가 많다:
- 역할 부여 일시 (assigned_at)
- 현재 역할이 유효한지 여부 (is_active)
- 부여한 관리자 ID (assigned_by)
- 변경 사유 등…
이런 경우엔 user_roles 테이블을 독립적인 엔티티로 분리하여 관리해야 한다.
그렇게 하면 단순 연결 이상으로, 변경 이력, 권한 활성화/비활성화 상태, 부여자 추적 등이 가능하다
복합 키 클래스: UserRoleId
중간 테이블은 일반적으로 두 개의 외래 키를 복합 키로 사용한다.
JPA에서는 @Embeddable을 통해 이런 복합 키 클래스를 별도로 정의해야 한다.
@Embeddable // 복합 키로 사용할 수 있도록 지정
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
public class UserRoleId implements Serializable {
private Long userId;
private Long roleId;
}
- Serializable 필수
- equals()와 hashCode()를 재정의해야 하므로 @EqualsAndHashCode 사용
- 두 외래 키(userId, roleId)를 조합해 하나의 복합 키로 구성
중간 엔티티: UserRole
@Entity
@Table(name = "user_roles") // 중간 테이블을 명시적으로 엔티티화
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserRole {
@EmbeddedId
private UserRoleId id; // 복합 키
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("userId")
@JoinColumn(name = "user_id")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("roleId")
@JoinColumn(name = "role_id")
private Role role;
// 예: 역할 부여 일시, 활성 상태, 부여자 정보 등 추가 가능
// private LocalDateTime assignedAt;
// private Boolean isActive;
// private Long assignedBy;
@Builder
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
this.id = new UserRoleId(user.getId(), role.getId());
}
}
주요 어노테이션 설명
- @EmbeddedId: 복합 키 클래스 사용
- @MapsId: 외래 키가 복합 키의 일부임을 명시
- @ManyToOne(fetch = FetchType.LAZY): 중간 테이블에서도 지연 로딩 전략 사용 가능
분리해야 하는 이유
// 단순 연결이면 이렇게 충분하지만,
@ManyToMany
@JoinTable(name = "user_roles", ...)
Set<Role> roles;
// 아래처럼 추가 정보가 필요한 경우, 독립된 엔티티가 필요하다.
@Entity
@Table(name = "user_roles")
class UserRole {
@EmbeddedId
private UserRoleId id;
private LocalDateTime assignedAt;
private boolean isActive;
private Long assignedBy;
}
5. RoleChangeLog - 역할 변경 로그 기록
이 엔티티는 관리자나 시스템에 의해 유저의 역할이 변경된 내역을 추적하기 위한 용도다.
이런 로그는 보안 감사를 위한 중요한 근거 자료가 된다.
- userId, email: 변경 대상
- prevRoles, newRoles: 변경 전후의 역할 (TEXT 타입 → @Lob 사용)
- changedBy, changeType, changeReason: 누가 어떤 이유로 변경했는지 기록
- changedAt: @PrePersist를 통해 자동 기록
이처럼 로그를 남김으로써 시스템의 투명성과 변경 이력 관리를 책임진다.
@PrePersist란?
- JPA에서 제공하는 엔티티 생명주기 콜백 어노테이션 중 하나
- 해당 엔티티가 처음으로 persist() 될 때, 즉 DB에 INSERT 되기 직전에 호출됨
- 데이터베이스에 저장되기 바로 직전의 상태에서 어떤 처리를 하고 싶을 때 사용
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
}
DB에 저장될 때마다 현재 시간을 자동으로 넣어주는 방식
이걸 통해 불필요하게 서비스나 컨트롤러에서 시간을 지정하지 않아도 되고, 일관성 있는 시간 처리가 가능
'웹개발 > SpringBoot' 카테고리의 다른 글
| 보안과 권한 시스템 - 3. Controller (1) | 2025.06.04 |
|---|---|
| 보안 인증과 권한 시스템 - 2. Repository (2) | 2025.06.03 |
| Springboot로 게시판(Post&Comment)만들기 - 두 테이블 연결 (0) | 2025.06.01 |
| SpringBoot 검색기능+Enum활용 (0) | 2025.05.31 |
| Spring Boot + MySQL로 구현한 Student CRUD (1) | 2025.05.30 |