국비 지원 IT(웹앱개발) 취업반 강의 51일차 (Spring)

BlackBird·2024년 8월 19일

개발자 취업 일지

목록 보기
83/116

처음 시작은 Rq 객체 관리를 Spring이 하게끔 바꾸는 것 부터 시작했다. 솔직히 뭔소린지 이해는 안되는데 코드를 보면 약간은 알 것 같기도 하고.

@Controller
public class UsrMemberController {

	@Autowired
	private MemberService memberService;
	
	@Autowired
	private Rq rq;
	
	@RequestMapping("/usr/member/join")
	public String showJoin() {
		return "/usr/member/join";
	}

	@RequestMapping("/usr/member/doJoin")
	@ResponseBody
	public String doJoin(HttpServletRequest req, String loginId, String loginPw,
			String name, String nickname, String cellphoneNum, String email) {
		rq = (Rq) req.getAttribute("rq");
		
		if (Ut.isEmptyOrNull(loginId))
			return Ut.jsHistoryBack("F-1", Ut.f("아이디를 입력해주세요."));

		if (Ut.isEmptyOrNull(loginPw))
			return Ut.jsHistoryBack("F-2", Ut.f("비밀번호를 입력해주세요."));

		if (Ut.isEmptyOrNull(name))
			return Ut.jsHistoryBack("F-3", Ut.f("이름을 입력해주세요."));

		if (Ut.isEmptyOrNull(nickname))
			return Ut.jsHistoryBack("F-4", Ut.f("닉네임를 입력해주세요."));

		if (Ut.isEmptyOrNull(cellphoneNum))
			return Ut.jsHistoryBack("F-5", Ut.f("전화번호를 입력해주세요."));

		if (Ut.isEmptyOrNull(email))
			return Ut.jsHistoryBack("F-6", Ut.f("이메일을 입력해주세요."));

		ResultData doJoinRd = memberService.doJoin(loginId, loginPw, name, nickname, cellphoneNum, email);

		if (doJoinRd.isFail()) {
			return Ut.jsHistoryBack(doJoinRd.getResultCode(), doJoinRd.getMsg());
		}

		Member member = memberService.getMemberById((int) doJoinRd.getData1());

		return Ut.jsReplace("S-1", "회원가입이 완료되었습니다.", "/usr/member/login");
	}

	@RequestMapping("/usr/member/login")
	public String showLogin(HttpServletRequest req) {
		return "/usr/member/login";
	}

	@RequestMapping("/usr/member/doLogin")
	@ResponseBody
	public String doLogin(HttpServletRequest req, String loginId, String loginPw) {

		rq = (Rq) req.getAttribute("rq");

		Member member = memberService.getMemberByLoginId(loginId);

		if (member == null) {
			return Ut.jsHistoryBack("F-3", Ut.f("%s는(은) 존재 하지않습니다.", loginId));
		}

		if (member.getLoginPw().equals(loginPw) == false) {
			return Ut.jsHistoryBack("F-4", Ut.f("비밀번호가 틀렸습니다."));
		}

		rq.login(member);

		return Ut.jsReplace("S-1", Ut.f("%s님 환영합니다", member.getNickname()), " / ");
	}

	@RequestMapping("/usr/member/doLogout")
	@ResponseBody
	public String doLogout(HttpServletRequest req) {

		// 로그아웃 처리
		rq.logout();

		return Ut.jsReplace("S-1", Ut.f("로그아웃 성공"), " / ");
	}
}

이런식으로 매번 Rq rq로 선언을 해줬는데 해당 기능을 적용하면서 그게 생략됐다.

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Rq {
	
	@Getter
	private boolean isLogined = false;
	@Getter
	private int loginedMemberId = 0;
	
	private HttpServletRequest req;
	private HttpServletResponse resp;
	
	private HttpSession session;
	
	public Rq(HttpServletRequest req, HttpServletResponse resp) {
		this.req = req;
		this.resp = resp;
		this.session = req.getSession();
		
		HttpSession httpSession = req.getSession();
		
		if (httpSession.getAttribute("loginedMemberId") != null) {
			isLogined = true;
			loginedMemberId = (int) httpSession.getAttribute("loginedMemberId");
		}
		this.req.setAttribute("rq", this);
	}

	public void printHistoryBack(String msg) throws IOException {
		resp.setContentType("text/html; charset=UTF-8");
		println("<script>");
		if (!Ut.isEmpty(msg)) {
			println("alert('" + msg + "');");
		}
		println("history.back();");
		println("</script>");
	}

	private void println(String str) {
		print(str + "\n");
	}

	private void print(String str) {
		try {
			resp.getWriter().append(str);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void logout() {
		session.removeAttribute("loginedMemberId");
	}

	public void login(Member member) {
		session.setAttribute("loginedMemberId", member.getId());
	}
	public void initBeforeActionInterceptor() {
		System.err.println("initBeforeActionInterceptor 실행");
	}
}

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
해당 코드를 추가하고 initBeforeActionInterceptor 메서드를 추가해서 해당 기능을 돌렸다.
거의 모든 전체 코드에
@Autowired
private Rq rq;
를 전역 변수로 선언해줬다.
여기서 Autowired가 뭔지 기억이 잘 안나니 한번 확인해보자.

@Autowired 애너테이션을 사용하면, 스프링이 해당 필드나 생성자, 메서드에 필요한 의존성을 자동으로 주입해 줍니다. 라고 한다.

이후에 doJoin, modify 등을 JSP로 화면 구성을 하셨는데 나는 이미 다 만들어둬서 내 코드랑 비교하면서 강사님이 하시는 것을 보았다. 보다보니까 나는 detail에 updateDate가 누락되어있어서 추가했다.

	<div class="detail-item">
		<span class="label">수정 날짜:</span> ${article.updateDate}
	</div>

간단한 것이다.

강사님은 화면 구성을 다 만드신 뒤에 head에 daisyUI를 추가하셔서 스타일을 적용시켰다. 나는 이미 css로 다 해둬서 해당 link 태그를 생성하니까 죄다 깨지길래 그냥 각주처리 해뒀다.

사용법은 해당 링크에서 복붙하면서 페이지에 적용되는 것을 확인하면 된다. 사이트 보면 해당 스타일들을 볼 수 있게 잘 만들어두었다.
데이지 UI 뿐 아니라 cdnjs에 다양한 UI들이 있으니 한번 참고해보는 것도 작업을 편하게 할 수 있게 해줄 것 같다.

다음으로 게시글 작성도 만드셨는데 마찬가지로 나는 이미 해 두어서 그냥 봤다. 나랑 비슷한데 차이점은 글 작성 버튼을 헤드에 두셨다는 거? 그거 말고는 근본적으로 다를게 없다.

어제 포기했었던 글 전체보기 같은 탭을 이제 만들 것 같다.

일단 게시물을 저장하는 게시판의 개념을 만든다. 따라서 DB에 게시판을 저장할 공간, 즉 테이블을 생성한다.

#게시판 테이블 생성
CREATE TABLE board(
                   id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
                   regDate DATETIME NOT NULL,
                   updateDate DATETIME NOT NULL,
                   `code` CHAR(50) NOT NULL Unique comment '공지사항, QnA 등',
                   `name` CHAR(50) NOT NULL Unique comment '',
					delStatus TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '탈퇴여부 (0=탈퇴 전, 1=탈퇴 후)',
					delDate DATETIME COMMENT '탈퇴 날짜'
);

게시판에 속한 게시물도 존재하니 게시물 테이블에 inner join할 수 있게 boardId를 추가해준다.

ALTER TABLE article ADD COLUMN boardId INT(10) UNSIGNED NOT NULL AFTER memberId;

이후에 테스트 데이터와 article에 boardId의 값을 채워준다.


-- 게시판 테스트 데이터.	
INSERT INTO board
SET regDate = NOW(),
updateDate = NOW(),
`code` = 'notice',
`name` = '공지사항';

INSERT INTO board
SET regDate = NOW(),
updateDate = NOW(),
`code` = 'free',
`name` = '자유';

INSERT INTO board
SET regDate = NOW(),
updateDate = NOW(),
`code` = 'QnA',
`name` = '질의응답';

UPDATE article
SET boardId = 1
WHERE id IN (1,2, 3, 4);

UPDATE article
SET boardId = 2
WHERE id in (5, 6);

UPDATE article
SET boardId = 3
WHERE id = 7;

이제 준비는 다 됐으니 프로젝트에 손을 대보자.

//기존 list
	@RequestMapping("/usr/article/list")
	public String showList(Model model) {
		List<Article> articles = articleService.getArticles();

		model.addAttribute("articles", articles);
		return "/usr/article/list";
	}

// 게시판 적용.
	@Autowired
	private BoardService boardService;
    
	public String showList(Model model, int boardId) {

		Board board = boardService.getBoardById(boardId);

		List<Article> articles = articleService.getArticles();

		model.addAttribute("articles", articles);
		model.addAttribute("board", board);

		return "usr/article/list";
	}

우린 아직 BoardService를 만들어준적이 없으니 만들어주자.

@Service
public class BoardService {

	@Autowired
	private BoardRepository boardRepository;

	public BoardService(BoardRepository boardRepository) {
		this.boardRepository = boardRepository;
	}

	public Board getBoardById(int boardId) {
		return boardRepository.getBoardById(boardId);
	}

}

BoardRepository도 마찬가지.

@Mapper
public interface BoardRepository {

	@Select("""
			SELECT *
			FROM board
			WHERE id = #{boardId}
			AND delStatus = 0
				""")
	public Board getBoardById(int boardId);

}

생성자와 getter setter를 담아둘 Board 클래스까지 만들면,

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Board {

	private int id;
	private String regDate;
	private String updateDate;
	private String code;
	private String name;
	private boolean delStatus;
	private String delDate;
}

이제 준비의 준비는 끝이 났다.

이제는 게시판을 분리해줘야된다.

	@RequestMapping("/usr/article/list")
	public String showList(Model model, int boardId) {

		Board board = boardService.getBoardById(boardId);

//		List<Article> articles = articleService.getArticles();
		
		List<Article> bIdarticles = articleService.getBIdArticles(boardId);
		
		model.addAttribute("articles", bIdarticles);
		model.addAttribute("board", board);

		return "usr/article/list";
	}

이렇게 하나 새로 선언해줬고

	public List<Article> getBIdArticles(int boardId) {
		return articleRepository.getArticlesByBorderId(boardId);	
		}
	@Select("""
			SELECT a.*, m.nickname AS extra__writer
			FROM article a
			INNER JOIN `member` m
			ON a.memberId = m.id
			WHERE a.boardId = #{boardId}
			ORDER BY
			a.id DESC
				""")
	public List<Article> getArticlesByBorderId(int boardId);

이렇게 해당 게시판의 게시물만 가져올 수 있게 만들었다.

그리고 List를 메뉴화 시켜야하는데,

<title>${pageTitle}</title>
</head>
<body>
	<header>
		<div class="flex h-20 mx-auto items-center text-3xl">
			<a href="/">LOGO</a>
			<div class="flex-grow"></div>
			<ul class="flex space-x-6">
				<li><a class="" href="/">HOME</a></li>
				<li><a class="" href="../article/list">LIST</a>
					<ul class="sub-menu">
						<li><a href="../article/list?boardId=1">Notice</a></li>
						<li><a href="../article/list?boardId=2">Free</a></li>
						<li><a href="../article/list?boardId=3">QnA</a></li>
					</ul></li>
				<c:if test="${!rq.isLogined()}">
					<li><a class="" href="../member/login">LOGIN</a></li>
					<li><a class="mr-4" href="../member/join">JOIN</a></li>
				</c:if>
				<c:if test="${rq.isLogined()}">
					<li><a
						onclick="if(confirm('로그아웃 하시겠습니까?') == false) return false;"
						class="mr-4" href="../member/doLogout">LOGOUT</a></li>
				</c:if>
			</ul>
		</div>
	</header>

헤드에 이렇게 추가해줬다.

아까 게시물을 게시판별로 분류하는 코드를 만들어서 화면상 해당 게시판에 속한 게시물이 잘 나온다.


이렇게.

그리고 2차메뉴는 이렇게.

write시 게시판 번호를 DB로 넘겨주지 않으니까 문제가 생긴다. write 기능도 수정이 필요하고 detail에 게시판이 무엇인지 명시되지 않는 것도 수정이 필요하다.

일단 write에 매개변수로 String boardId를 받아 서비스로 또 리포지트리로 넘기게 했고 쿼리에 해당 데이터가 들어갈 수 있게 수정했다.

	@RequestMapping("/usr/article/doWrite")
	@ResponseBody
	public String doWrite(HttpServletRequest req, String title, String body, String boardId) {
		
		Rq rq = (Rq) req.getAttribute("rq");
		
		
		if (Ut.isEmptyOrNull(title)) {
			return Ut.jsHistoryBack("F-1", "제목을 입력해주세요");
		}
		if (Ut.isEmptyOrNull(body)) {
			return Ut.jsHistoryBack("F-2", "내용을 입력해주세요");
		}
		if (Ut.isEmptyOrNull(boardId)) {
			return Ut.jsHistoryBack("F-3", "게시판 선택해주세요.");
		}
		
		int boardIdInt = Integer.parseInt(boardId);
		ResultData writeArticleRd = articleService.writeArticle(rq.getLoginedMemberId(), title, body, boardIdInt);

		int id = (int) writeArticleRd.getData1();

		Article article = articleService.getArticleById(id);

		return Ut.jsReplace(writeArticleRd.getResultCode(), writeArticleRd.getMsg(),"/usr/article/detail?id=" + id);
	}

////////////////
	public ResultData writeArticle(int memberId, String title, String body, int boardId) { 
				
		articleRepository.writeArticle(memberId, title, body, boardId);

		int id = articleRepository.getLastInsertId();

		return ResultData.from("S-1", Ut.f("%d번 글이 등록되었습니다", id), "등록 된 게시글의 id", id);
	}
////////////////
	public void writeArticle(int memberId, String title, String body, int boardId);
//////////////
	<insert id="writeArticle">
		INSERT INTO article
		SET regDate = NOW(),
		updateDate = NOW(),
		title = #{title},
		`body` = #{body},
		memberId = #{memberId},
		boardId = #{boardId}
	</insert>

이렇게.

그리고 write JSP에 해당 태그를 추가

		<div class="form-group">
			<label for="boardId">게시판 선택</label>
			<select id="boardId" name="boardId" class="form-select" required>
				<option value="">게시판을 선택하세요</option>
				<option value="1">Notice</option>
				<option value="2">Free</option>
				<option value="3">QnA</option>
			</select>
		</div>

화면상으로는 이렇게 보인다.

detail은 간단하게 해결했다.

이제는 페이지네이션이다. 테스트 데이터를 대량으로 늘렸다.

	@RequestMapping("/usr/article/list")
	public String showList(Model model, Integer page, @RequestParam(defaultValue = "1") int boardId) {
		
		if (page == null) {
	        page = 1; // 기본값 설정
	    }
		Board board = boardService.getBoardById(boardId);		
		model.addAttribute("board", board);
		
		int itemsPerPage = 10;
	    int totalItems = articleService.getTotalArticlesCount(boardId);
	    int totalPages = (int) Math.ceil((double) totalItems / itemsPerPage);
	    int offset = (page - 1) * itemsPerPage;

	    List<Article> articles = articleService.getArticlesByPage(boardId, offset, itemsPerPage);
	    

	    model.addAttribute("articles", articles);
	    model.addAttribute("currentPage", page);
	    model.addAttribute("totalPages", totalPages);
	    model.addAttribute("boardId", boardId);

	    return "usr/article/list";
	}
	//페이지네이션
	public List<Article> getArticlesByPage(int boardId, int offset, int limit) {
	    return articleRepository.getArticlesByPage(boardId, offset, limit);
	}

	public int getTotalArticlesCount(int boardId) {
	    return articleRepository.getTotalArticlesCount(boardId);
	}
	//페이지네이션
	public List<Article> getArticlesByPage(int boardId, int offset, int limit);

	public int getTotalArticlesCount(int boardId);
	<!-- 페이지네이션 -->
	<select id="getArticlesByPage" resultType="Article">
		SELECT *
		FROM article
		WHERE boardId = #{boardId}
		ORDER BY id DESC
		LIMIT #{limit} OFFSET #{offset}
	</select>
	<select id="getTotalArticlesCount" resultType="int">
		SELECT COUNT(*)
		FROM article
		WHERE boardId = #{boardId}
	</select>

페이지네이션은 이렇게 처리했고

이렇게 작동한다. 근데 지금 보면 writer가 누락되어있는데 이게 이유를 모르겠다.
gpt한테 물어봐서 얻은 결과는

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.ArticleRepository">

    <!-- resultMap 정의 -->
    <resultMap id="ArticleResultMap" type="com.example.demo.vo.Article">
        <result property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="body" column="body"/>
        <result property="extra__writer" column="extra__writer"/>
        <!-- 필요한 다른 필드들 추가 -->
    </resultMap>

    <update id="modifyArticle">
        UPDATE article
        <set>
            <if test="title != null and title != ''">title = #{title},</if>
            <if test="body != null and body != ''">`body` = #{body},</if>
            updateDate = NOW()
        </set>
        WHERE id = #{id}
    </update>

    <insert id="writeArticle">
        INSERT INTO article
        SET regDate = NOW(),
        updateDate = NOW(),
        title = #{title},
        `body` = #{body},
        memberId = #{memberId},
        boardId = #{boardId}
    </insert>

    <select id="getArticles" resultMap="ArticleResultMap">
        SELECT a.*, m.nickname AS extra__writer
        FROM article a
        INNER JOIN `member` m ON a.memberId = m.id
        ORDER BY a.id DESC
    </select>

    <select id="getArticleById" resultType="Article">
        SELECT a.*
        FROM article a
        WHERE id = #{id}
    </select>

    <!-- 페이지네이션 -->
    <select id="getArticlesByPage" resultMap="ArticleResultMap">
        SELECT a.*, m.nickname AS extra__writer
        FROM article a
        INNER JOIN `member` m ON a.memberId = m.id
        WHERE a.boardId = #{boardId}
        ORDER BY a.id DESC
        LIMIT #{limit} OFFSET #{offset}
    </select>

    <select id="getTotalArticlesCount" resultType="int">
        SELECT COUNT(*)
        FROM article
        WHERE boardId = #{boardId}
    </select>

</mapper>
	<!-- resultMap 정의 -->
    <resultMap id="ArticleResultMap" type="com.example.demo.vo.Article">
        <result property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="body" column="body"/>
        <result property="extra__writer" column="extra__writer"/>
        <!-- 필요한 다른 필드들 추가 -->
    </resultMap>

해당 코드를 추가함으로써 해결했다.

resultMap을 추가하고 제대로 명시해주니까 데이터를 찾아왔는데, board필드가 추가되면서 sql에서 맵핑을 해오지 못해서 나온 문제였던 것 같다.

아무튼 여기까지.. 페이지네이션은 지우고 한번 더 혼자 해봐야될 것 같다.

profile
한영신의 벨로그입니다.

0개의 댓글