스프링부트를 사용하여 기본적인 CRUD 기능이 있는 게시판을 구현하였다. 그 과정을 정리하고자 한다. 모든 코드를 설명하는 것보다는 내가 게시판을 구현하면서 했던 고민과 생각을 중점적으로 글을 작성하였다.
인프런 김영한 님의 스프링 강의를 정주행하고 학습한 내용을 바탕으로 구현하려고 했는데 나는 아직 자바도 제대로 공부한 적도 없고 스프링도 처음 접하는 상황이었다. 때문에, 강의에서 알려주는 꿀팁이나 테크닉이 얼마나 유용한 것인지, 강의에서 제시하는 구현 틀과 방법이 얼마나 효율적인 구조인지 공감하기 힘들었다. 이 상태로 프로젝트를 진행하면 어떻게든 게시판을 완성할 수는 있겠지만 나에게 남는 게 많지 않을 것 같았다. 직접 무수한 시행착오를 겪으며 목표를 달성하고 강의를 다시 정주행한다면 깊은 공감에서 우러나오는 감동을 느낄 수 있을 것이라는 것이라 생각하여 노력과 시간이 많이 들 것을 알지만 무작정 게시판 구현을 시작하였다.
※ 디자인은 완전히 포기한 오직 기능 구현에 중점을 둔 게시판이다
- 전체 코드(깃허브) https://github.com/HyoBN/bulletin_board
게시판 기능 및 구조
앞서 말했듯이 이 게시판은 회원가입, 로그인하여 게시글을 조회, 작성, 수정,삭제할 수 있는 기본에 충실한 게시판이다.
등록된 회원만이 게시판 홈에 접근가능하고 자신이 작성한 게시글만 수정, 삭제 가능하다. 세부적인 특징이나 기능은 코드와 함께 설명하겠다.
먼저, 디렉토리 구조는 다음과 같다.
Member, Post 두 개의 엔티티를 기반으로 Service, Controller, Repository를 나누어 각각의 기능을 구현하였다. 확실히 이렇게 나누니까 나중에 엔티티에 대한 특정 기능을 추가할 때 어떤 코드를 어느 파일에 추가하고 어떻게 연결시켜야할지 알기 쉽고 에러가 발생했을 때에도 어느 파일의 코드를 수정하면 해결할 수 있을 지 파악하기가 용이했다.
home, boardHome 연결 구현
기본 홈페이지 화면이다. 여기서 로그인을 하면 게시판 홈페이지 화면으로 이동할 수 있다.
로그인하면 boardHome 페이지로 넘어가는 부분을 구현할 때 HomeController에서 세션 여부를 기준으로 home으로 연결할지, boardHome으로 연결할지 정하도록 구현하였다. 이렇게 하면 한 번 로그인하면 로그아웃 하지 않는 이상(세션이 만료되지 않는 이상) 홈으로 이동하거나 새로고침을 해도 home이 아닌 boardHome으로만 이동한다. 따라서 로그인한 상태에서 다시 로그인하려는 경우를 방지할 수 있을 것이라 생각하여 구현하였다.
아래는 HomeController 코드이다.
// HomeController.java
package com.board.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.ui.Model;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import com.board.entity.Member;
import com.board.service.PostService;
@Controller
public class HomeController{
private PostService postservice;
@Autowired
public HomeController(PostService postservice){
this.postservice=postservice;
}
//비회원의 home과 회원의 home을 분리하여 연결시킴.
@GetMapping("/")
public String home(HttpServletRequest request, Model model){
HttpSession session = request.getSession(false);
// 세션에 회원 데이터가 없으면 홈으로 이동
if(session==null){
return "home";
}
// 세션이 유지되면 로그인 홈으로 이동
Member loginMember = (Member) session.getAttribute("loginMember");
if (loginMember != null) {
model.addAttribute("loginMessage", loginMember.getName()+"님 로그인 상태입니다.");
model.addAttribute("posts",postservice.findPosts());
return "boardHome";
}
return "home";
}
}
회원 정보 중복 방지 구현
다음은 회원가입할 때 기존의 회원들과 id, name이 중복되지 않도록 방지하는 코드이다. 파이썬으로 소켓 기반 채팅 프로그램을 만들때 채팅 참여중인 멤버 이름과 클라이언트의 이름이 중복되는 것을 방지해야했다. 클라이언트의 요청 이름을 서버로 보낼 때와 해당 이름과 서버의 채팅 참여중인 멤버 이름 데이터와 비교하여 중복 여부를 클라이언트로 보낼 때 send()함수를 사용하여 특정 문자열(Overlap 등)을 송신하여 해당 문자열을 기반으로 방지하였다. 이 기억이 떠올라 아래와 같이 Service에서 join함수로 id, name 중복을 막고 Controller에서 join함수의 반환값 문자열인 joinMessage를 기반으로 id, name 중복 여부를 사용자에게 전달하였다.
자신의 게시글만 수정, 삭제 가능
자신이 작성한 게시글만 수정, 삭제 가능하도록 허용하는 부분이다.
먼저, 로그인할 때 session에 "loginMember"라는 속성을 추가한다. 아래 코드의 62번째줄 setAttribute 부분이다. 이렇게하면 로그인을 한 후에는 session에 로그인한 회원 정보가 속성으로 저장된다.
session에 저장되어 있는 회원 정보 중 이름(name)과 DB에 저장되어 있는 해당 post의 작성자 이름을 비교하여 로그인한 회원이 해당 post의 작성자인지 확인한다.
회원가입 실패 시, 입력했던 데이터 보존
회원가입 시 중복일 경우, 입력했던 값을 일부만 수정 가능하도록 값을 다 지우지 않고 그대로 다시 출력해주었다.
사진으로 설명하자면,
회원가입 페이지에서 정보를 입력하고 등록 버튼을 누르면 회원가입이 진행되며 ID가 중복되는 경우
아래와 같이 createMemberForm 페이지에 '이미 존재하는 ID입니다.' 라는 메시지가 출력된다.
![]() 입력했던 데이터를 보존하지 않은 경우 |
![]() 입력했던 데이터를 보존하여 불러온 경우 |
여기서 원래는 위의 왼쪽 화면과 같이 이전에 input에 입력했던 정보들이 초기화된 상태가 된다.
그렇게 되면 id와 이름을 다시 처음부터 입력해야하기 때문에 번거롭다. 따라서 일부만 수정하면 되도록 기존에 입력했던 정보들을 그대로 출력해주는 것이다. (비밀번호는 HTML에서 input type을 password로 했기 때문에 어차피 안보여서 출력하지 않았다.)
아래는 MemberController에서 구현한 코드이다.
Member 객체를 하나 생성하여 HTML에서 입력받았던 id, name, password 데이터를 불러와 저장한다.
그렇게 만들어진 member로 join을 수행하고 만약 중복이라면 member를 model의 속성값에 저장하여 createMemberForm으로 이동할 때 전달될 수 있도록 구현하였다.
Property or field 'id' cannot be found on null
이는 id property가 Null이 될 수 있기 때문에 발생하는 에러이다. 해당 property에 ?를 추가하면 이 에러를 해결할 수 있다. 아래의 코드에 적용하였다.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<form action="/members/new" method="post">
<div class="form-group">
<label for="id">id</label>
<input type="text" id="id" name="id" placeholder="id를 입력하세요"
th:value="${memberFormData?.id}"></input>
<br/>
<label for="name">이름</label>
<input type="text" id="name" name="name" placeholder="이름을 입력하세요"
th:value="${memberFormData?.name}"></input>
<br/>
<label for="password">비밀번호</label>
<input type="password" id="password" name="password"
placeholder="비밀번호를 입력하세요"></input>
<br/>
<p th:text="${loginMessage}" style="color:red"></p>
</div>
<button type="submit">등록</button>
</form>
</div>
</body>
</html>
Spring Data JPA
repository에서 db에 접근하는 부분은 Spring Data JPA를 사용하여 간편하게 구현하였다.
save, find, delete 등 필수적이고 직관적인 메소드를 제공해주며 order 등 추가적인 옵션도 적용가능하기에 정말 편리하다. 하지만 좀 더 복잡한 기능이 필요할때에는 JPQL 등을 활용하는 등 직접 쿼리문을 작성하는 필요성을 느꼈다.
//MemberRepository.java
package com.board.repository;
import com.board.entity.Member;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Member save(Member member);
Optional<Member> findById(String id);
Optional<Member> findByName(String name);
Optional<Member> findByIdAndPassword(String id, String password);
List<Member> findAll();
}
//PostRepository.java
package com.board.repository;
import com.board.entity.Post;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
Post save(Post post);
Post findById(Long id);
void deleteById(Long id);
Optional<Post> findByTitle(String title);
Optional<Post> findByDate(String date);
Optional<Post> findByWriter(String writer);
List<Post> findAllByOrderByIdDesc();
}
※ MySQL에 저장되어 있는 데이터(member, post)의 테이블 구조는 아래와 같다.
HTML로 페이지를 작성할 때는 thymeleaf 템플릿 엔진을 적극 활용하였다.
유용했던 thymeleaf 엔진의 기능이나 html 태그는 따로 정리할 예정이다.
이렇게 crud 게시판 프로젝트가 마무리되었다. 스프링을 공부하기 시작한 것은 3달 전이었다. 구현한 기능에 비해 걸린 기간이 너무 긴데 나름의 변명을 하자면 휴가도 갔다왔고 하루에 컴퓨터를 쓸 수 있는 시간이 적다. 또한 개발환경 이슈가 좀 컸다. 구름IDE에서 진행하였는데 초반 환경 세팅 과정에서 발생하는 다양한 문제들을 해결하려는데 구글링해도 잘 나오지 않아서 삽질하는데 시간이 많이 걸렸다. 또한 Devtools가 적용이 안돼서 파일을 수정할때마다 서버를 재시작해야했는데 사지방 컴퓨터 상태에 따라 길게는 5분까지 걸리기도 했다.. 빨리 인텔리제이 쓰고 싶다...
어찌저찌 달성하고자 했던 목표는 이루어서 만족스럽고 이제 인프런 강의를 다시 정주행하고 MVC, JPA 등의 강의도 들으며 탄탄한 뼈대를 구축할 것이다! 얼른 나만의 유용한 웹사이트를 배포하고 싶다
'Study > Spring' 카테고리의 다른 글
싱글톤 패턴 (0) | 2022.06.23 |
---|---|
게시판에 사용한 HTML 정리(feat. thymeleaf) (0) | 2022.01.16 |
좋은 객체 지향 설계의 5가지 원칙 - SOLID (0) | 2021.11.10 |
[구름IDE] 스프링부트 에러 메시지와 해결방법 모음 (0) | 2021.10.20 |
[구름IDE] 스프링부트 개발환경 세팅하기 (0) | 2021.09.23 |