간펴니
간편 자바프로그래밍
간펴니
전체 방문자
오늘
어제
  • 전체보기 (185)
    • 알고리즘 (2)
    • JAVA (69)
      • 이펙티브 자바 (47)
      • JAVA 병렬프로그래밍 (5)
      • 자바 (17)
    • SPRING (60)
      • Spring (12)
      • IceWater Community (37)
      • Homme Shop (10)
      • 토비의 스프링 (1)
    • SPRING BOOT (4)
      • WhiteRecord (7)
    • 오류 (9)
    • DB (10)
      • ORACLE (5)
      • MYSQL (1)
      • MYBATIS (4)
      • JPA (0)
      • 대용량 데이터 베이스 (0)
      • SQL (0)
    • FRONT (8)
      • JSP (2)
      • JavaScript (2)
      • Jquery (3)
      • Thymeleaf (1)
    • AWS (6)
    • JNI (10)
    • 회고 (0)
    • MQ (0)
    • Radis (0)
    • Git (0)
    • Docker (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 블로그 컨셉 변경

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
간펴니

간편 자바프로그래밍

[스프링]게시판 읽은 글 표시
SPRING/IceWater Community

[스프링]게시판 읽은 글 표시

2021. 10. 1. 17:03
728x90

 

이번엔 게시판에서, 회원마다 읽은 글을 목록에 표시해주는 기능 을 구현했다.

 

예시

 

 

 

join을 이용해서 구현했는데, 마침 조인쿼리는 경험이 적어서 많이 헤맸다.

 

특히, 익숙하지 않은 조인쿼리를 mybatis에서,

그것도 페이징 , 검색 , 조건부 정렬, 카테고리구분 의 기능이 적용되어있는 복잡한(내 기준 ㅜ)

게시글 목록 select문에 적용시키느라 쉽지 않았다.

 

적용시키려고 뷰,컨트롤러,VO,Mapper 등등 여기저기 다 건드리고,

디벨로퍼에서 수십번씩 쿼리를 써보고,

수많은 시행착오 끝에 결과적으로 조인쿼리에 대해 많이 알게됐다. 역시 직접 구르는게 최ㄱ..

 

Join은 실제로 많이 사용하는 쿼리라고 하니 참 좋은 경험이였던 것 같다.


코드설명에 앞서, 먼저 로직을 간단하게 설명해보면

 

  • 회원의 읽은 목록을 저장할 테이블 생성(MP_BOARD_CHECK)
  • 게시글목록(list)에서 제목을 클릭해서 게시글내용(readView)으로 이동(readView 컨트롤러 호출)
  • 게시글내용 컨트롤러에 insert문을 넣어서 목록의 PR과 회원의 PR을 저장
  • 목록은 List로 select문의 결과를 받아오는데, 조인쿼리를 추가해서 게시판테이블(MP_BOARD)과 저장테이블의 값을 받음
  • 뷰단에서 if(목록의 PR == 저장테이블에 저장된 목록PR &&  저장테이블의 회원PR == 로그인한 회원의PR)   으로 읽은 목록을 구분해서 표시

이다.

 

select쿼리를 이용해서 , 비로그인과 로그인시를  구분했다.

 


 

 

테이블 생성

create table MP_BOARD_CHECK ( CNO NUMBER NOT NULL,
MEMBER_ID VARCHAR2(30) NOT NULL,
BNO NUMBER NOT NULL,
CONSTRAINT MP_BOARD_CHECK_PR PRIMARY KEY (CNO),
CONSTRAINT MEMBER_ID_FK FOREIGN KEY(MEMBER_ID)
REFERENCES MP_MEMBER(MEMBER_ID) ON DELETE CASCADE,
CONSTRAINT BNO_FK FOREIGN KEY(BNO)
REFERENCES MP_BOARD(BNO) ON DELETE CASCADE
);
commit;

어느 회원이, 무슨 글을 읽었는지 구분을 위한,

회원ID (PR) , 게시글고유번호 (PR) 를 저장할  테이블을 생성해준다.

외래키와 delete cascade 제약조건을 추가했다.

 


 

로그인을 한 회원 (세션값에 ID가 있음)이 목록페이지(list)에서 제목을 눌러서 ,

게시글 내용페이지(readView)로 이동하기 위해 컨트롤러를 호출할때 , 

insert문을 통해 생성한 테이블에 값을 넣어준다.

 

 

readView 컨트롤러

@RequestMapping(value="/board/readView", method=RequestMethod.GET)
	public String read(HttpSession session,@ModelAttribute("scri") SearchCriteria scri,BoardVO boardVO, Model model) throws Exception{
		logger.info("read");
		if(session.getAttribute("login") != null) {
			MemberVO memberVO =(MemberVO) session.getAttribute("login");
		if(service.boardCheck(boardVO.getBno(),memberVO.getMemberId())==0) {
		
		service.insertBoardCheck(boardVO.getBno(), memberVO.getMemberId());
		}
		}
		
		/*model.addAttribute("read", service.read(boardVO.getBno()));
		model.addAttribute("scri", scri);
		model.addAttribute("replyVO",new ReplyVO());
		
		List<Map<String, Object>> fileList = service.selectFileList(boardVO.getBno());
		model.addAttribute("file", fileList);
		
		model.addAttribute("move", service.movePage(boardVO));
		logger.info("fileList=" + fileList); */
		
		
		return "/board/readView";
				
	}

(관련없는 부분은 주석처리 했다.)

 

HttpSession을 매개변수로 선언해주고,

로그인 시에 부여되는 세션의 key값으로 getAttribute를 통해 null이 아니면 (로그인이 되어있으면)

회원테이블에 사용되는 VO에 세션값을 담아준다.

 

 

그 다음, 저장테이블에 중복 된 값이 있는지 확인한 후 (count가 0) 

없다면 저장테이블에 값을 저장한다.

 

매개변수로 뷰단에서 게시글PR (bno) 과 로그인한 회원의 ID ( memberId )를 넣어준다.

 


Mapper.xml

<insert id="insertBoardCheck">
insert into MP_BOARD_CHECK(CNO , BNO , MEMBER_ID) 
values((SELECT NVL(MAX(CNO), 0) + 1 FROM MP_BOARD_CHECK) ,#{bno} ,#{memberId})
</insert>

<select id="boardCheck" resultType="int">
select count(*) from MP_BOARD_CHECK
where MEMBER_ID = #{memberId} AND BNO = #{bno}
</select>

컨트롤러에서 매개변수로 받은 bno와 memberId를 통해,

 

select문으로 중복저장을 방지하고 (컨트롤러에서 count값이 0이 나와야 insert문 실행)

 

insert문으로 읽은 게시글의 번호와 회원의 ID를 저장한다.

 


DAO,DAOImpl

 

//조회한 게시글 표시
	public void insertBoardCheck(int bno,String memberId)throws Exception;
	
	public int boardCheck(int bno,String memberId)throws Exception;
    
    
    	@Override
		public void insertBoardCheck(int bno,String memberId)throws Exception{
			Map<String,Object> map = new HashMap<String, Object>();
			map.put("memberId", memberId);
			map.put("bno", bno);
			sqlsession.insert("boardMapper.insertBoardCheck", map);
		}
		
		public int boardCheck(int bno,String memberId)throws Exception{
			Map<String,Object> map = new HashMap<String, Object>();
			map.put("memberId", memberId);
			map.put("bno", bno);
			
			return sqlsession.selectOne("boardMapper.boardCheck", map);
			
		}

간단하다. 파라미터를 map에 담아서 넣어준다.

 


Service, ServiceImpl

	//조회한 게시글 표시
	public void insertBoardCheck(int bno,String memberId)throws Exception;
	
	public int boardCheck(int bno,String memberId)throws Exception;
    
    
    	@Override
	public void insertBoardCheck(int bno,String memberId)throws Exception{
		dao.insertBoardCheck(bno, memberId);
	}
	
	@Override
	public int boardCheck(int bno,String memberId)throws Exception{
		return dao.boardCheck(bno,memberId);
	}

컨트롤러에서 받은 매개변수를 dao로 전달

 


이제 저장테이블에 정보가 입력되었으니, 그 정보를 이용해 회원이 읽은 게시글을 구분해서 표시해주도록 하자.

 

저장테이블의 값을 받을 VO를 생성해준다

public class BoardCheckVO {

	private int bno;
	private String memberId;
	private int cno;
    
   // getter&setter 생성 
    
    }

 

조인을 위해 BoardVO에 생성한 VO를 추가

public class BoardVO {
	private int bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private int hit;
	private int replyhit;
	private int next;
	private int last;
	private String nexttitle;
	private String lasttitle;
	private int bgnoinsert;
	private int bgno;
	private String id;
	private int likehit;
	private int hatehit;
	private int idpoint;
	
	private BoardCheckVO boardCheckVO;
    
    
    //getter&setter
    }

<resultMap type="kr.co.vo.BoardCheckVO" id="BoardCheckVO">
		<result column="CNO" property="cno"/>
		<result column="BNO" property="bno"/>
		<result column="MEMBER_ID" property="memberId"/>
	</resultMap>
	
	<resultMap type="kr.co.vo.BoardVO" id="BoardVO">
		<result column="BNO" property="bno"/>
		<result column="TITLE" property="title"/>
		<result column="WRITER" property="writer"/>
		<result column="CONTENT" property="content"/>
		<result column="HIT" property="hit"/>
		<result column="REPLYHIT" property="replyhit"/>
		<result column="REGDATE" property="regdate"/>
		<result column="BGNO" property="bgno"/>
		<result column="LIKEHIT" property="likehit"/>
		<result column="HATEHIT" property="hatehit"/>
		<result column="IDPOINT" property="idpoint"/>
		<result column="ID" property="id"/>
		<collection property="boardCheckVO" resultMap="BoardCheckVO"></collection>
	</resultMap>
	
	
	<select id="listPage" resultMap="BoardVO"
		parameterType="kr.co.vo.SearchCriteria">
		SELECT a.BNO,
		a.TITLE,
		a.CONTENT,
		a.WRITER,
		a.REGDATE,
		a.HIT,
		a.REPLYHIT,
		a.BGNO,
		a.LIKEHIT,
		a.HATEHIT,
		a.IDPOINT,
		a.ID,
		b.BNO,
		b.MEMBER_ID
		FROM (
		SELECT BNO,
		TITLE,
		CONTENT,
		WRITER,
		REGDATE,
		HIT ,
		REPLYHIT,
		BGNO,
		LIKEHIT,
		HATEHIT,
		IDPOINT,
		ID,
		ROW_NUMBER() OVER(
		<include refid="sort" />) AS RNUM
		FROM MP_BOARD
		WHERE 1=1
		<include refid="search" />
		<if test="bgno != 0">
		and BGNO=#{bgno}
		</if>
		
		)
        
        a left outer JOIN MP_BOARD_CHECK b 
        on a.bno = b.bno 
        
		<if test="memberId != null">
		AND b.member_id = #{memberId}
		</if>
        
        
		WHERE RNUM BETWEEN #{rowStart} AND #{rowEnd}

 

 

Join 사용에 대한 포스팅은 따로 작성을 예정이다.

mybatis에서 join사용법에 대한 부분은 따로 검색을 통해 알아보도록 하자.

 

include 부분은 다른 기능에 관한 것이니 신경쓸 필요가 없고,

 

결론적으로 from 뒤에 , where 사이에 조인을 추가해주고, on으로 조인 할 범위를 잡아주면 된다.

 

서브쿼리로 게시판 테이블의 내용을 정렬하고, 그 값에 a태그를 붙여준다.

저장테이블엔 b태그를 붙여주고,

 

비 로그인시

on a.bno = b.bno (이 쿼리 자체로는 select문에 변화는 없다)

 

로그인시 (memberId != null)

저장테이블의 id와 로그인한 회원의 id 값이 같으면 저장테이블에서 값을 가져온다.

 

즉 , 

MP_BOARD a Left Outer Join MP_BOARD_CHECK b

 

게시판 테이블의 모든 값과

게시판 테이블에 공통되는(ON)  저장테이블의 값을 select

 

 

 

ON a.bno = b.bno AND b.memberId = #{memberId}

 

게시판 테이블의 bno와 저장테이블의 bno가 같고,

저장테이블의 ID가 로그인한 회원의 ID와 같다면 값을 출력

 

 

두개의 조건을 모두 만족하지 않으면 게시판 테이블의 모든값을 출력하지만 저장테이블은 null을 출력 

즉 ,

뷰단에서 게시판 테이블의 select값과 저장테이블의 select값을 비교해야하는데 , 

저장테이블은 null값이 나와서 if문에서 빠져나오게 된다.

(저장테이블 값이 없다 = 읽지 않았다 = 목록에 표시 x)

 

 

 

mybatis 에서 , select문은 항상 resultType 또는 resultMap을 선언해줘야 한다.

조인을 위해 resultMap을 작성, boardVO에 조인VO를 넣어줌으로 써, boardVO resultMap으로 select결과값을 받는다.


Controller

	@RequestMapping(value = "board/list", method = RequestMethod.GET)
	public String list(HttpSession session,Model model, @ModelAttribute("scri") SearchCriteria scri) throws Exception{
		logger.info("list");
		
			if(session.getAttribute("login") != null) {
			MemberVO memberVO =(MemberVO) session.getAttribute("login");
			logger.info("memberId"+memberVO.getMemberId());
			
			scri.setMemberId(memberVO.getMemberId());
		
			}
		
		
		model.addAttribute("list", service.list(scri));
        
        
		/*model.addAttribute("notice", service.notice());
		
		PageMaker pageMaker = new PageMaker();
		pageMaker.setCri(scri);
		pageMaker.setTotalCount(service.listCount(scri));
		
		model.addAttribute("pageMaker", pageMaker);
		*/
	
	
		
		return "board/list";
		
	}

 

필요없는 부분은 주석처리 하였다.

조인 쿼리에서, 회원구분을 위해 memberId 파라미터를 넣어줘야 한다

 

비로그인, 로그인을 구분해서

HttpSession으로 memberId를 가져오고 목록의 필요한 parameter를 전달하는 VO에 

set으로 memberId를 넣어준다.

 

현재 내 프로젝트는 위에서 말했듯이 정렬 , 검색 , 카테고리구분 등의 기능 역할을 하는 scri를(VO)

parameterType으로 사용하기 때문에 , 따로 설명하진 않겠다.

 

자신의 프로젝트에 상황에 따라 세션에서 가져온 memberId를 model까지 전달해주도록 하자.

 

그 후 , list라는 key로 뷰로 데이터를 보낸다.


list.jsp

	<c:forEach items="${list}" var="list">		
						
	<c:choose >
	<c:when test="${not empty login and list.bno == list.boardCheckVO.bno and list.boardCheckVO.memberId == login.memberId}">
		<tr class="table-active">
	</c:when>
	<c:otherwise>
		<tr>
	</c:otherwise>
	</c:choose>

부트스트랩을 사용중인데, tr class="table-active" 는 표 테이블의 한 행을 마우스를 올렸을때 색으로 변경해준다.

 

 

if문을 사용해서 , 

 

not empty login    -->  ${login} 이 비어있지 않으면 true ( 세션값 )

 

list.bno == list.boardCheckVO.bno    -->  게시판테이블의 bno와 저장테이블의 bno가 같으면 true

 

list.boardCheckVO.memberId == login.memberId    -->  저장테이블의 ID 와 로그인한 회원의 ID가 같으면 true

 

3개의 값이 모두 참이여야 표테이블의 행의 색을 바꾼다.

 

즉 로그인상태, 어느 회원인지 , 회원이 읽은 게시글번호가 무엇인지를 비교한다.

 


결과

012345
슬라이드쇼 6장

  1. '3333' 회원으로 로그인 , 게시글목록 (list)
  2. #1174 번 게시글을 클릭해서 내용으로 이동 (readView)
  3. 저장테이블에 로그인한 회원의 ID와 읽은 글의 BNO가 저장되었음
  4. 총 3개의 게시글을 읽고 목록으로 나온 모습
  5. 카테고리를 옮겨도, 같은 테이블에서 카테고리를 나눴기 때문에 읽은 게시글은 그대로 표시됨
  6. '11111' 회원으로 로그인 한 직후의 모습 ( 테스트하며 다 1167빼고 다 읽은 상태)

 


이 기능을 마무리하면서 ,

게시글내용 (readView) 페이지에 구현해놓은 이전글 , 다음글 기능에도 표시를 하는 방법이 생각났다.

 

[스프링]게시판 이전글 다음글 구현 :: 간편 웹프로그래밍 (tistory.com)

 

[스프링]게시판 이전글 다음글 구현

오늘은 이전글 다음글을 구현해보려고 한다. 글 이동은 게시글내용(게시글상세보기) 뷰에서 할 것이므로, 처음에는 bno(게시글PR)로 게시글 구분을 하니, 버튼에 onclick= "location.href='상세페이지링

kimfk567.tistory.com

 

 

다음글 쿼리는,  해당 글의 카테고리의 다음글 bno를 산출한다.

 

컨트롤러에서, 다음글 bno를 따로 하나의 객체에 담아주고 ,  

 

select count(*) from mp_board_check where membre_id = #{memberId} AND bno = {다음글bno}

 

로그인 한 회원의 ID와 다음글 BNO에 맞는 열이 저장테이블에 있다면 (글을 읽었다면 저장테이블에 insert) 1 ,

없다면 0을 리턴한다. 

 

그 값을 resultType = int 형으로 받아서 컨트롤러에서 model에 담아서 뷰단으로 보내준다.

 

(model.addAttribute("nextCheck" , [결과값객체생성자]) )

 

<if test="${nextCheck == 0}">

<if test =${nextCheck != 0}"> 

이렇게 if문으로 값을 비교해서 0이 아닐때, 표시를 바꿔주면 되겠다 

 


짜여진 코드를 보고 변형해서 사용하는 것이 아닌 , 내가 코드를 직접 짜고 , 힘들게 구현한 만큼

배운 것도 많고 더 보람찬 개발이였다.

 

다 좋은데 불안한 점은 개발자 커뮤니티에서 본 글이 생각난다는 것이다.

 

"개발을 할 때, 내가 짠 코드는 신과 나만이 알고 있고 

개발을 완료하고 시간이 지나면, 내가 짠 코드는 신만이 알고 있다."

 

직접 구현한 만큼 잊어버려도 작성한 글을 보면 바로 생각날 것 같지만,

커뮤니티 프로젝트를 한달넘게 하면서 많은 기능들을 추가했더니 정말 가물가물해진다... 분명 내가 짯는데...

 

복습만이 살길...

728x90
저작자표시 (새창열림)

'SPRING > IceWater Community' 카테고리의 다른 글

[스프링] 댓글 좋아요/싫어요 구현 - (Ajax)  (4) 2021.10.06
[스프링] 게시판 이전글 다음글 읽은 글 표시  (1) 2021.10.01
[스프링] 공지사항 구현  (0) 2021.09.30
[스프링] 추천기능 구현 (OKKY사이트 추천기능 참고)  (4) 2021.09.28
[스프링] 회원정보수정 - 회원탈퇴 (ajax)  (0) 2021.09.25
    'SPRING/IceWater Community' 카테고리의 다른 글
    • [스프링] 댓글 좋아요/싫어요 구현 - (Ajax)
    • [스프링] 게시판 이전글 다음글 읽은 글 표시
    • [스프링] 공지사항 구현
    • [스프링] 추천기능 구현 (OKKY사이트 추천기능 참고)
    간펴니
    간펴니
    개발공부 기록하는 곳

    티스토리툴바