게시판 만들기

Drumj·2022년 11월 17일
post-thumbnail

진짜 오랜만에 벨로그에 등장해버린 나...
꽤나 귀차니즘이었을지도...?

이번주엔 게시판을 만들었다..

신입으로 들어가면 한번쯤 거쳐야 하는건가??
여기도 게시판, 저기도 게시판
게시판 만들면서 실력 파악하는 모양이다..!!

기존에 쓰던 JPA도 안쓰고.. 데이터 처리 방식도 DTO아니고..
xml, mapper, VO 를 사용하는 코드를 쭈우우욱 훑어보면서 상황에 맞게 개발을 시작했다.

회사의 코드는 오픈하면 안된다고 하니 내가 작성한 코드만 조금씩 기록해놔야겠다.


전체적인 코드의 흐름을 알아보자

평소(라고 해도 1개밖에 없는 프로젝트지만...)에 개발하던 방식과 다르니 처음에는 조금 애 먹었다.
하지만 내가 누구? it's DrumJ!

대강 어떻게 돌아가는지 알아보고 개발 착수

우선 코드의 흐름은 이렇다

???Mapper.xml -> ???Mapper.java -> ???Service.java -> ???Controller.java -> ???.html

(아..? 반대로 적었어야 했나...??)

여튼... 안쪽에서부터 코드를 알아가 보도록 하자!!
(이해 안되면 나중에 밑에서부터 읽지 뭐....ㅎ)


Mapper.xml

<!--공지사항 가져오기 & 페이징-->
    <select id="getNoticeList" resultType="Map">
        <![CDATA[
        select *
        from (select @ROWNUM:=@ROWNUM+1 as rnum, article_seq, title, content, view_cnt, create_date
                from tbl_board_article tba ,(SELECT @ROWNUM:=0) as r
                where board_id like 'notice' and deleted = 0
                order by tba.create_date desc) list
        where #{pageNum}*#{amount}>= rnum and rnum >(#{pageNum} -1)*#{amount}
        ]]>
    </select>

    <!--공지사항 갯수-->
    <select id="countNotice" resultType="int">
        select count(*)
        from tbl_board_article tba
        where board_id like 'notice' and deleted = 0
    </select>

    <!--공지사항 검색-->
    <select id="searchNotice" parameterType="Criteria" resultType="Map">
        <![CDATA[
        select *
        from (
            select @ROWNUM:=@ROWNUM+1 as rnum, article_seq, title, content, view_cnt, create_date
	        from (select *
		            from tbl_board_article tba
		            where board_id like 'notice' and deleted = 0 ) t1 ,(SELECT @ROWNUM:=0) as r
		            where t1.title like concat('%', #{keyword}, '%') or t1.content like concat('%', #{keyword}, '%')
		            order by t1.create_date desc
            ) list
        where #{pageNum}*#{amount}>= rnum and rnum >(#{pageNum} -1)*#{amount}
        ]]>
    </select>

    <!--공지사항 검색 갯수-->
    <select id="countSearchNotice" parameterType="string" resultType="int">
        select count(*)
        from (select * from tbl_board_article tba
                where board_id like 'notice' and deleted = 0 ) t1
                where t1.title like concat('%', #{keyword}, '%') or t1.content like concat('%', #{keyword}, '%')
        order by t1.create_date desc
    </select>

무야..!! <![DATA[]]>안에 있는 쿼리문은 주석처리 된 것 같잖아!!!

여튼 어디까지 기록해도 되는지 몰라서 select태그만 가져왔다. 사실 이 xml파일에 이거밖에 없긴함...

여기서 id 는 Mapper.java 파일에 작성한 것과 일치되는 모양이다.
parameterType 과 resultType을 정할 수 있다.


Mapper.java

public interface NoticeMapper {

    List<NoticeVO> getNoticeList(Criteria criteria);

    List<NoticeVO> searchNotice(Criteria criteria);

    int countNotice();

    int countSearchNotice(String keyword);
}

이 파일은 interface로 만들었다. 아마 repository 같은 녀석!

위에도 적었듯이 Mapper.xml 파일의 id 와 일치되는 쿼리를 작동시킨다.

여기까지가 DB 접근 기술! JPA를 사용하지 않고 xml로 직접 쿼리문을 만드는게 생각보다 귀찮고 어려웠다... DBeaver에서 할때는 쿼리가 잘 작동되다가 java로 넘어오면 안되는 경우도 있었고... 그건 다 내 잘못! Controller랑 Service파일을 보면서 확인 할 수 있을테지만

int countSearchNotice(String keyword);

여기서 String keyword를 넘겨줬어야 했는데 아무것도 안 넘겨줘서 쿼리가 제대로 작동을 안했었다... ㅠㅠ


Service.java

@Service
public class NoticeService {

    @Autowired
    private NoticeMapper noticeMapper;
    //조회
    public List<NoticeVO> getNoticeList(Criteria criteria){
        return noticeMapper.getNoticeList(criteria);
    }
    //조회 갯수
    public int countNotice() {
        return noticeMapper.countNotice();
    }
    //검색
    public List<NoticeVO> searchNotice(Criteria criteria) {
        return noticeMapper.searchNotice(criteria);
    }
    //검색 갯수
    public int countSearchNotice(String keyword) {
        return noticeMapper.countSearchNotice(keyword);
    }
}

Controller에서 데이터를 받아와서 mapper로 넘겨주는 Service 파일.
딱히 뭐... 알아볼게 없넹
아!! 저기 List<NoticeVO>로 받아왔는데 xml에 resultType은 map임..
형식이 안맞는데 잘 작동하는데 이거는 왜지...?? java 언어에 대해 더 공부할 필요성을 느꼈다.


Controller

@Controller
public class NoticeViewController {
    @Autowired
    private NoticeService noticeService;

    @GetMapping("/board/notice")
    public ModelAndView Notice(Criteria criteria) {
        UserDetailsImpl user = AuthUtils.getDetails();
        List<NoticeVO> noticeList = noticeService.getNoticeList(criteria);
        int totalCnt = noticeService.countNotice();

        ModelAndView mav = new ModelAndView();
        mav.setViewName("html/notice");
        mav.addObject("adminKind",user.getAdminKind());
        mav.addObject("noticeList",noticeList);
        mav.addObject("totalCnt",totalCnt);
        mav.addObject("pageMaker", new PageDto(criteria,totalCnt));

        return mav;
    }
}

여기 방식은 특이하게 viewController랑 ApiController를 따로 작성하더라
view는 그냥 보여주는거 Api는 말그대로 Api.

뷰에 뭘 많이 하긴 했는데 타임리프를 사용하기 위해서 ModelAndView를 사용해서
.setViewName() 으로 html 세팅하고 mav.addObject()로 데이터를 넘겨줬다.
오랜만에 타임리프를 사용하니까 꽤 오래걸렸다... ㅠㅠ

ApiController

@Controller
public class NoticeApiController {

   @Autowired
   private NoticeService noticeService;

   @GetMapping("/api/board/notice/search")
   public ModelAndView searchNotice(String keyword, Criteria criteria) {
       UserDetailsImpl user = AuthUtils.getDetails();

       List<NoticeVO> searchList = noticeService.searchNotice(criteria);
       int totalCnt = noticeService.countSearchNotice(keyword);

       ModelAndView mav = new ModelAndView();
       mav.setViewName("html/notice");
       mav.addObject("adminKind",user.getAdminKind());
       mav.addObject("searchList",searchList);
       mav.addObject("searchCnt",totalCnt);
       mav.addObject("pageMaker", new PageDto(criteria,totalCnt));

       return mav;
   }
}

ApiController 도 비슷하게 적용. 여기는 검색 관련 Api이다.


처음해보는 페이징처리!

페이징 처리를 하고 <select> 태그에서 몇개씩 보여줄지도 만들었어야 했다.

우선 파일 2개를 생성.

Criteria.java

@Getter @Setter
public class Criteria {

    private int pageNum;
    private int amount;

    private String keyword;

    public Criteria() {
        this(1, 10);
    }

    public Criteria(int pageNum, int amount) {
        this.pageNum = pageNum;
        this.amount = amount;
    }
}

pageNum 는 페이지 넘버 1쪽, 2쪽 같은거
amount 는 페이지당 보여줄 목록의 개수

pageDto

@Getter
public class PageDto {
    private int startPage;
    private int endPage;
    private boolean prev, next;
    private int total;
    private Criteria cri;

    public PageDto(Criteria cri, int total) {
        this.cri = cri;
        this.total = total;

        this.endPage = (int)(Math.ceil(cri.getPageNum()/10.0)) * 10;
        this.startPage = this.endPage - 9;

        int realEnd = (int)(Math.ceil((total * 1.0) / cri.getAmount()));

        if(realEnd < this.endPage) {
            this.endPage = realEnd;
        }

        this.prev = this.startPage > 1;
        this.next = this.endPage < realEnd;
    }
}

pageDto에서는 시작페이지와 끝 페이지 그리고 다음과 이전 버튼의 사용을 위한 코드들이 들어있다.

여기서 math.ceil은 나누기 계산하고 올림 해준다. 반올림 아니고 '올림'!!!

이렇게 두 파일을 작성하면 이러쿵저러쿵 해가지고 호로로록 하고 페이지네이션이 된다...(????)

아니 사실 여기저기 돌아다니면서 코드 줍줍 했는데... 제대로 이해하진 못했음 ㅠㅠ

대부분 JPA로 페이징 처리하는게 많아서... 다음에 개인프로젝트로 JPA 사용해서 한 번 만들어 봐야겠다. 어노테이션이랑 지원해주는 클래스 사용하면 꽤나 쉽게 만드는것 같던데.. 다음에 만들면서 xml 사용이랑 비교해봐야징!!! (할... 수 있겠지??)


끝..??

이러저러 해서 코드도 작성하고 xml 쿼리도 작성하고 작동은 된다!!!

여기서 문제점은 프론트에 덕지덕지 붙은 타임리프 코드들이랑(귀찮으니 프론트 코드는 기록 안...함...)
개발하면서 Test는 한번도 작성하지 않았던 것!!!

김영한님 강의 들으면서 Test를 어떻게 하는지 알긴 했는데 혼자 해볼려니 Test 할 생각도 못했고
어떻게 코드를 작성해야 할지도 모르겠다.

아직 갈 길이 멀고 많이 다듬어야 한다. 배울게 있다는건 꽤나 좋은 일인것 같다.

다음에는 개인 프로젝트로 게시판을 만들어봐야지!! 꼭!!!!

0개의 댓글