목차
개요
본격적으로 코드 작성을 해보자
Entity - 유저 클래스
카피를 위한 코드링크
정보전달만의 기능을 가진 웹사이트가 아닌 이상 회원을 관리하는 기능, 회원별 기능은 필수적으로 존재한다. 하지못해 정보전달만을 제공하는 경우에 관리자나 중간관리자가 글을 작성하고 관리할때 마저도 일반회원은 이러한 기능에 접근을 하지 못하게 해야하기 때문에 회원 기능이 필요하다.
@Entity: Member 클래스가 JPA 엔티티임을 선언한다. (일단은 메소드들도 JPA 로 구현을 시작한다)
@Data: 클래스에 getter, setter 등의 메서드를 자동 생성한다. (기본 메서드들을 다 생성해준다.)
@Builder: 객체 생성 시 빌더 패턴을 사용할 수 있게 한다.
@AllArgsConstructor: 모든 필드를 인자로 받는 생성자를 생성한다.
@NoArgsConstructor: 인자 없는 기본 생성자를 생성한다.
이 둘은 필수는 아니지만 코드 작성에 있어서 편리해진다.
@AllArgsConstructor는 모든 필드를 인수로 받는 생성자를 자동으로 생성해주어 객체를 생성할 때 모든 필드 값을 한 번에 설정할 수 있다.
@NoArgsConstructor는 인수가 없는 기본 생성자를 자동으로 생성해주어 JPA, Hibernate와 같은 ORM 프레임워크에서 엔티티 클래스에 요구되는 생성자를 지원해줄수 있다.
@Table: 클래스와 데이터베이스 테이블을 매핑한다.
이 어노테이션 6가지는 꼭 알아두자
이 클래스는 Spring Security를 위해 UserDetails 인터페이스를 구현하며, 사용자의 권한 정보를 관리하는 전체적인 흐름을 가진다.
getAuthoritites()
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
// Collectors.toList() 메서드를 이용하여 반환되는 Stream 객체를 List 형태로 변환
.collect(Collectors.toList());
}
이 부분은 유심히 살펴볼 필요가 있는데 이건 Spring Security 의 UserDetails 인터페이스의 getAuthorities() 메서드를 구현하고 있는 코드이다.
Collection<? extends GrantedAuthority>의 반환 타입을 가지고 사용자가 가진 권한(역할) 정보를 반환해주는 GrantedAuthority를 구현하는 객체들의 컬렉션이다.
코드의 흐름을 보자면
자바의 스트림을 이용하여 roles 집합을 스트림으로 변환하고 map(role -> new SimpleGrantedAuthority(role.getName())): roles 스트림의 각 요소에 대해 Role 객체의 getName() 메서드를 호출하여 권한 이름을 가져온 후, 이를 SimpleGrantedAuthority 객체로 변환하는 과정을 거친다.
이후에는 Spring Security에 필요한 메서드들이다. 생략 불가능함
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
또한 이후에 로그인/회원가입시에 회원을 식별하는 토큰을 Member와 OneToOne관계로 매핑해준다.
@OneToOne(mappedBy = "member", cascade = CascadeType.ALL)
private VerificationToken verificationToken;
마지막으로 User와 Role이라는 두 개의 엔티티 사이에 Many-to-Many 관계를 설정하며, 이 관계를 "users_roles"라는 이름의 조인 테이블을 사용하여 매핑한다.
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(name = "member_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
- @ManyToMany(fetch = FetchType.EAGER)
Member 엔티티와 Role엔티티 사이에 Many-to-Many 관계가 있다는 것을 나타내며 FetchType.EAGER는 이 관계를 로딩하는 전략이다. EAGER 전략은 엔티티가 로딩될 때 관련 엔티티도 함께 로딩하라는 의미인데 자세한건 아래 포스팅을 참조하자 - @JoinTable
Many-to-Many 관계를 나타내는 별도의 조인 테이블을 설정하고 각 엔티티의 ID를 보관하여 두 엔티티 사이의 매핑을 보관한다. - name = "users_roles": 조인 테이블의 이름을 나타낸다.
- joinColumns = @JoinColumn(name = "member_id")
현재 엔티티(User)의 ID를 보관하는 컬럼의 이름이 "member_id"이라는 것을 나타낸다. - inverseJoinColumns = @JoinColumn(name = "role_id")
관계를 맺고 있는 다른 엔티티(Role)의 ID를 보관하는 컬럼의 이름이 "role_id"라는 것을 나타낸다.
다음으로는 Role Entity의 코드를 작성해보겠다
Entity -Role 클래스
위의 Member 클래스 처럼 애너테이션을 사용하여 roles라는 테이블을 선언해준다.
@Entity
@Data
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="role_id")
private Long id;
@Enumerated(EnumType.STRING)
@Column(length = 60)
private RoleName name;
public Role() {}
public Role(RoleName name) {
this.name = name;
}
public enum RoleName {
ROLE_USER,
ROLE_ADMIN
}
public String getName() {
return this.name.name();
}
public static final Role ROLE_USER = new Role(RoleName.ROLE_USER);
public static final Role ROLE_ADMIN = new Role(RoleName.ROLE_ADMIN);
}
위의 코드에서 보면 일단 enum 타입의 RoleName은 ROLE_USER와 ROLE_ADMIN이 각각 하드코딩 되어있다.
*DB 등록시에 해당 두개의 타입이 기본으로 들어감
또한 Role의 생성자가
1. 기본생성자 와 2. RoleName을 매개변수로 받는 생성자
이렇게 두가지가 있는것으로 보이는데 이는 각각 ORM 프레임워크의 요구사항과 객체 생성 시 필수값 설정이라는 두 가지 다른 목적을 충족시키기 위해 사용된 것이다.
Java에서는 클래스에 하나 이상의 생성자가 명시적으로 정의되면, 컴파일러는 자동으로 기본 생성자를 추가하지 않기 때문에 이 경우에는 Role 클래스에 RoleName을 매개변수로 받는 생성자가 이미 정의되어 있어서 이 코드에서는 기본 생성자는 명시적으로 선언되어야 한다.
repository - MemberRepository
위에서 작성하였던 Member entity를 Spring Data JPA를 이용하여 Repository를 정의한다.
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member,Long> {
Optional<Member> findByUsername(String username);
boolean existsByUsername(String username);
Optional<Member> findByNicknameAndFindQuesNumAndFindAnswer(String nickname, Integer findQuesNum, String findAnswer);
}
- MemberRepository는 JpaRepository<Member, Long> 인터페이스를 확장하여 JPA를 사용하여 CRUD 작업을 수행할 수 있게하고 <> 내부의 값들은 각각 Member는 엔티티 타입이고, Long은 그 엔티티의 id의 타입이다.
- Optional<Member> findByUsername(String username) 메소드는 주어진 사용자 이름에 해당하는 Member 엔티티를 찾습니다. 만약 찾지 못하면 Optional.empty()를 반환하여 null 값에 대비한다.
- boolean existsByUsername(String username) 메소드는 주어진 사용자 이름에 해당하는 Member 엔티티가 존재하는지 확인하여 존재하면 true, 그렇지 않으면 false를 반환한다.
- findByNicknameAndFindQuesNumAndFindAnswer 메소드는 추후에 사용자의 별명이나 가입시 적었던 아이디/비밀번호 찾기 질문과 답변을 넣으면 멤버 객체를 찾게하는 메소드이다.
repository - RoleRepository
위에서 작성하였던 Role entity를 Spring Data JPA를 이용하여 Repository를 정의한다.
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName (RoleName roleName);
}
위의 MemberRepository의 메서드 설명을 참조하면 좋을것 같다.
마무리
다음번에는 본격적으로 로그인 기능을 위한 Token들과 로그인, 회원가입하는 로직과 해당 로직을 구현할때 계층간의 분리 와 데이터 일관성, 성능 등을 위해서 DTO도 구현해보자
'Spring' 카테고리의 다른 글
JPA관계매핑시 @JoinColumn 옵션 그리고 JPA명명전략(naming strategy) (0) | 2023.06.25 |
---|---|
FetchType.EAGER vs FetchType.LAZY in JPA (0) | 2023.06.23 |
스프링 JPA: 엔티티 필드의 기본값에 대한 고찰 (0) | 2023.06.21 |
@Transient 어노테이션과 DTO를 사용한 레이어 분리 (0) | 2023.04.22 |
SpringFramework 구성요소 (0) | 2023.04.10 |