3 분 소요


화면 제작 마무리..

Html, Css를 통해서 화면은 어느정도 틀만 잡아놓고, 디테일한 디자인 같은 경우는 추후에 추가하기 위해서 일단 마무리 했다.

너무 대충 만든 감이 있지만, 필요한 기능만 일단 다 넣어놨다.


게시판 메인 페이지 제작 캡쳐
community_page

디테일한 디자인은 나중에.. 할 수 있겠지..


백엔드 개발 시작

이번에 해야할 목록

  • BaseTimeEntity 생성
  • User Entity 생성
  • 필요한 DTO 생성
  • 회원가입 Repository 개발
  • 회원가입 검증기 작성
  • 회원가입 Repository와 service 연결

BaseTimeEntity

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    // Entity가 생성되서 저장될 때 시간이 자동 저장
    @CreatedDate
    private String createdDate;

    // 조회한 Entity 값을 변경할 때 시간이 자동 저장
    @LastModifiedDate
    private String modifiedDate;

    /*
    * 엔티티를 저장하기 이전에 실행
    * 시간을 가져온 뒤 알맞게 포멧
    * */
    @PrePersist
    public void onPrePersist() {
        this.createdDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        this.modifiedDate = this.createdDate;
    }

    @PreUpdate
    public void onPreUpdate() {
        this.modifiedDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }
}
  • 공통 적으로 사용되는 생성 일자와, 수정 일자
  • Auditing 기능을 포함 : 시간에 대해서 자동으로 값을 넣어주는 기능
  • Main Class에 @EnableJpaAuditing 어노테이션을 추가해줘야 한다.

User Entity

@Entity
@Getter
@NoArgsConstructor
public class User extends BaseTimeEntity{
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_PK")
    private Long id;

    @Column(length = 40, nullable = false)
    private String userName;

    @Column(length = 40, nullable = false)
    private String userId;

    @Column(length = 50, nullable = false)
    private String password;

    @Enumerated(EnumType.STRING)
    @Setter
    @Column(length = 20)
    private Role role;

}

회원가입 기능

처음에는 로그인 기능을 목표로 해서 제작했는데, domain -> repository -> service -> controller 순으로 개발 계획을 세웠다.

회원가입 로직을 제작하는데, 처음에는 Entity에 모든 Validation을 다 넣어서 구성을 했다. 엔티티 자체로 회원가입할 때 쓰기위해서 그렇게 했는데, 생각해보면 엔티티를 그대로 노출하는 것도 그렇고, 필요 없는 정보(Ex. 2차 비밀번호 등)도 같이 Entity객체에 들어가서 코드가 지저분했다.

그래서 회원가입시 사용하는 DTO를 따로 만들고, 그걸 이용해서 값을 입력 받은 뒤 Entity객체로 변환해서 DB에 저장하기로 했다.

JoinForm

@Getter
public class JoinForm {

    @Id
    @GeneratedValue
    private Long id;

    @NotBlank
    @Pattern(regexp = "^[가-힣a-zA-Z]{2,10}$")
    private String userName;

    @NotBlank
    @Pattern(regexp = "[a-z0-9]{5,9}")
    private String userId;

    @NotBlank
    @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}")
    private String password;

    @NotBlank
    @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}")
    private String check_password;  // 2차 비밀번호

    public JoinForm(String userName, String userId, String password, String check_password) {
        this.userName = userName;
        this.userId = userId;
        this.password = password;
        this.check_password = check_password;
    }
}
  • @NotBlank : @NotEmpty를 사용해야 하나 고민했는데 @NotBlank가 스페이스바 같은 공백까지 막아준다고 하여 이걸로 선택하였다.
  • @Pattern : 처음에 패턴을 짜는데 많이 애먹은게, 보면서 해도 구성이 헷갈리고, 복잡해서 시간이 많이 걸렸다.

UserRepositoryImpl

처음에는 Service가 Repository를 바로 의존관계 주입하도록 했는데, 나중에 Repository 코드를 바꿀 가능성이 있기 때문에 Interface를 하나 만들어서 Service로직이 Interface를 주입받도록 설계했다.

@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {

    private final EntityManager em;

    // 회원을 저장하는 메소드
    @Override
    public void save(User user_info) {
        em.persist(user_info);
    }

    /*
    * DB ID값으로 회원 조회
    * 테스트 코드에서 사용
    * */
    @Override
    public User findById(Long id) {
        return em.find(User.class, id);
    }

    /*
    /*
    * 회원 아이디로 회원 엔티티 조회
    * Session을 통해 유저 정보를 가져올 때 id값이 없기 때문에 새로 조회하기 위해 사용
    * */
    @Override
    public User loadUserByUserId(String userId) {
        return (User) em.createQuery("select u from User u where u.userId =: userId")
                .setParameter("userId", userId)
                .getSingleResult();
    }

    /*
    * 회원 아이디로 회원 검색(회원가입 / 로그인시 확인용으로 사용)
    * */
    @Override
    public Optional<FindUserDto> findByUserId(String userId) {
        return findAll().stream()
                .filter(findUserDto -> findUserDto.getUserId().equals(userId))
                .findFirst();
    }

    /*
    * 전체 회원 조회
    * */
    @Override
    public List<FindUserDto> findAll() {
        return em.createQuery(
                "select new project.reviews.dto.FindUserDto(u.userId, u.userName, u.password)" +
                " from User u", FindUserDto.class)
                .getResultList();
    }

    @Override
    public void deleteUser(User user) {
        em.remove(user);
    }
}
  • findAll()
    • 전체 회원을 조회하는데, User Entity 그대로 받아오지 않고, 필요한 정보만 Dto로 받아오게 했다.
  • findByUserId()
    • userId값을 통해서 DB에 이미 가입되어 있는 회원인지 값을 가져온다.
    • findAll()을 사용해서 가져온 모든 데이터에서 필터링을 통해 원하는 정보만 뽑아올 수 있게 작성하였다.

FindUserDto

@Getter
@AllArgsConstructor
public class FindUserDto {

    private String userId;
    private String userName;
    private String password;
}


여기서 한 가지 고민은 Optional 사용 여부였다.
Optional을 찾아보니 비용이 비싸서 정확한 의도대로 사용하지 않으면 오히려 안쓰는 것보다 못하다고 했다.
우선 사용하긴 했지만, 나중에 리팩토링할 때 메모리를 많이 잡아먹는다고 생각되면 비교문으로 바꿔야 겠다.

UserService

Service 역할은 회원가입 form에서 받은 정보로 중복회원인지 확인하고, 중복회원이 아니라면 User Entity로 변환해서 Repository에 넘겨주어 DB에 저장하는 역할을 한다.

    public Long join(JoinForm form) {

        validateDuplicateUser(form);    // 중복 회원 검증

        /*
        * 중복 확인이 끝나면, User Entity로 변환해서 DB에 저장
        * */
        User user = new User(form.getUserName(), form.getUserId(), form.getPassword());
        userRepository.save(user);
        return user.getId();
    }

    /*
    * 회원가입시 입력한 아이디와 DB에 입력된 아이디 값을 비교해서 중복 회원가입인지 확인
    * */
    private void validateDuplicateUser(JoinForm form) {
        Optional<FindUserDto> findUser = userRepository.findByUserId(form.getUserId());
        if(!findUser.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }


중복회원 검증은 Repository에서 만든 findByUserId()를 통해서 진행한다.
사실 Service 로직은 중복회원 검증만 하고 저장하는 용도가 끝이다.

댓글남기기