국비교육(22-23)

55일차(1)/Spring(15) : Cafe 게시판 기능 구현 (목록보기, 새글 작성)

서리/Seori 2022. 12. 25. 19:55

55일차(1)/Spring(15) : Cafe 게시판 기능 구현 (목록보기, 새글 작성)

 

- Cafe 게시판 만들기

- Dao, Mapper 완성

- getList 메소드 (페이징처리, 검색기능)

- 새글 작성 메소드

 

 

cafe 기능 생성

- controller / dto / dao / service 패키지 생성

 

CafeDto

package com.sy.spring04.cafe.dto;

public class CafeDto {
	   private int num;
	   private String writer;
	   private String title;
	   private String content;
	   private int viewCount;
	   private String regdate;
	   private int startRowNum;
	   private int endRowNum;
	   private int prevNum; //이전 글의 글번호
	   private int nextNum; //다음 글의 글번호
	
	 public CafeDto() {}

	public CafeDto(int num, String writer, String title, String content, int viewCount, String regdate, int startRowNum,
			int endRowNum, int prevNum, int nextNum) {
		super();
		this.num = num;
		this.writer = writer;
		this.title = title;
		this.content = content;
		this.viewCount = viewCount;
		this.regdate = regdate;
		this.startRowNum = startRowNum;
		this.endRowNum = endRowNum;
		this.prevNum = prevNum;
		this.nextNum = nextNum;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getWriter() {
		return writer;
	}

	public void setWriter(String writer) {
		this.writer = writer;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public int getViewCount() {
		return viewCount;
	}

	public void setViewCount(int viewCount) {
		this.viewCount = viewCount;
	}

	public String getRegdate() {
		return regdate;
	}

	public void setRegdate(String regdate) {
		this.regdate = regdate;
	}

	public int getStartRowNum() {
		return startRowNum;
	}

	public void setStartRowNum(int startRowNum) {
		this.startRowNum = startRowNum;
	}

	public int getEndRowNum() {
		return endRowNum;
	}

	public void setEndRowNum(int endRowNum) {
		this.endRowNum = endRowNum;
	}

	public int getPrevNum() {
		return prevNum;
	}

	public void setPrevNum(int prevNum) {
		this.prevNum = prevNum;
	}

	public int getNextNum() {
		return nextNum;
	}

	public void setNextNum(int nextNum) {
		this.nextNum = nextNum;
	}
	 
	 
}

 

CafeDao 인터페이스

package com.sy.spring04.cafe.dao;

import java.util.List;

import com.sy.spring04.cafe.dto.CafeDto;

public interface CafeDao {
	//글목록
	public List<CafeDto> getList(CafeDto dto);
	//글의 갯수
	public int getCount(CafeDto dto);
	//글 추가
	public void insert(CafeDto dto);
	//글정보 얻어오기
	public CafeDto getData(int num);
	//조회수 증가 시키기
	public void addViewCount(int num);
	//글 삭제
	public void delete(int num);
	//글 수정
	public void update(CafeDto dto);
	
}

 

- 검색 키워드를 담아오기 위해 dto를 받아오는 메소드로 만든 것.

 

CafeDaoImpl

package com.sy.spring04.cafe.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.sy.spring04.cafe.dto.CafeDto;

@Repository
public class CafeDaoImpl implements CafeDao{

	@Autowired
	private SqlSession session;
	
	@Override
	public List<CafeDto> getList(CafeDto dto) {
		/*
		 * 검색 기능은 
		 * 1. 제목+내용
		 * 2. 제목
		 * 3. 작성자
		 * 검색 3가지 기능을 제공할 예정이다. 
		 */
		return session.selectList("cafe.getList", dto);
	}

	@Override
	public int getCount(CafeDto dto) {
		
		return session.selectOne("cafe.getCount", dto);
	}

	@Override
	public void insert(CafeDto dto) {
		session.insert("cafe.insert", dto);
		
	}

	@Override
	public CafeDto getData(int num) {
		
		return session.selectOne("cafe.getData", num);
	}
	//조회수 올리는 메소드
	@Override
	public void addViewCount(int num) {
		session.update("cafe.addViewCount", num);
		
	}

	@Override
	public void delete(int num) {
		session.delete("cafe.delete", num);
		
	}

	@Override
	public void update(CafeDto dto) {
		session.update("cafe.update", dto);
		
	}

}

 

CafeMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cafe">

	<sql id="searchCondition">
      <choose>
         <when test="title != null and content != null">
            WHERE title LIKE '%'||#{title}||'%' 
            OR content LIKE '%'||#{content}||'%'
         </when>
         <when test="title != null">
            WHERE title LIKE '%'||#{title}||'%'
         </when>
         <when test="writer != null">
            WHERE writer LIKE '%'||#{writer}||'%'
         </when>
      </choose>
	</sql>
	
	 <select id="getList" parameterType="cafeDto" resultType="cafeDto">
	      SELECT *
	      FROM
	         (SELECT result1.*, ROWNUM AS rnum
	         FROM
	            (SELECT num,writer,title,content,viewCount,regdate
	            FROM board_cafe
	            <include refid="searchCondition"/>
	            ORDER BY num DESC) result1)
	      <![CDATA[ 
	      WHERE rnum >= #{startRowNum} AND rnum <= #{endRowNum}
	      ]]>
	</select>
   
   <select id="getCount" parameterType="cafeDto" resultType="int">
      SELECT NVL(MAX(ROWNUM), 0)
      FROM board_cafe
      <include refid="searchCondition"/>
   </select>   

	<insert id="insert" parameterType="cafeDto">
	INSERT INTO board_cafe
	(num, writer, title, content, viewCount, regdate)
   	VALUES(board_cafe_seq.NEXTVAL, #{writer}, #{title}, 
   		#{content}, #{viewCount}, SYSDATE)   	
	</insert>
	
	<select id="getData" parameterType="int" resultType="cafeDto">
		SELECT num,  writer, title, content, viewCount, 
			TO_CHAR(regdate, 'YY.MM.DD HH24:MI') AS regdate
		FROM board_cafe
		WHERE num=#{num}
	</select>
	
	<update id="addViewCount" parameterType="int">
		UPDATE board_cafe
		SET viewCount=viewCount+1
		WHERE num=#{num}
	</update>
	
	<delete id="delete" parameterType="int">
		DELETE FROM board_cafe
		WHERE num=#{num}				
	</delete>
	
	<update id="update" parameterType="cafeDto">
		UPDATE board_cafe
		SET title=#{title}, content='${content}'
		WHERE num=#{num}
	</update>
</mapper>

 

configuration에 mapper추가해두기

 

 

DaoImpl insert, getData 작성

@Override
public void insert(CafeDto dto) {
    session.insert("cafe.insert", dto);		
}

@Override
public CafeDto getData(int num) {		
    return session.selectOne("cafe.getData", num);
}

 

getData 작성

- num을 가지고가서 row 하나를 select 해오기

 

mapper - insert, getData

<insert id="insert" parameterType="cafeDto">
    INSERT INTO board_cafe
    (num, writer, title, content, viewCount, regdate)
    VALUES(board_cafe_seq.NEXTVAL, #{writer}, #{title}, 
        #{content}, #{viewCount}, SYSDATE)   	
</insert>

<select id="getData" parameterType="int" resultType="cafeDto">
    SELECT num,writer,title,content,viewCount,TO_CHAR(regdate, 'YY.MM.DD HH24:MI') AS regdate,
         FROM board_cafe
    WHERE num=#{num}
</select>

 

- addViewCount, delete, update 문 작성

@Override
public void addViewCount(int num) {
    session.update("cafe.addViewCount", num);

}

@Override
public void delete(int num) {
    session.delete("cafe.delete", num);

}

@Override
public void update(CafeDto dto) {
    session.update("cafe.update", dto);

}

 

 mapper 작성

<update id="addViewCount" parameterType="int">
    UPDATE board_cafe
    SET viewCount=viewCount+1
    WHERE num=#{num}
</update>

<delete id="delete" parameterType="int">
    DELETE FROM board_cafe
    WHERE num=#{num}				
</delete>

<update id="update" parameterType="cafeDto">
    UPDATE board_cafe
    SET title=#{title}, content='${content}'
    WHERE num=#{num}
</update>

 

- 작업에 실패했을 때의 처리는 모아서 할 예정!

- Spring에는 exception Controller가 따로 있다.여기서 모아서 처리한다.

 

 

 

- myBatis에는 ${ }도 있다. (jsp가 아니므로 EL은 아니다)

- 전처리 구문이라고 한다. 실행하기전에 미리 저렇게 작성해놓고 실행하는 것!

 

- #{ } 은 내부적으로 ?으로 바뀌고, 여기에 setString이 들어가는 것이다.

- ${ } 는 content안에 들어있는 내용을 이 sql문을 실행하기 전에 미리 실행해서 값을 불러와놓고

 sql문의 실행을 시작하는 것이다.(그래서 따옴표로 감싸야한다)

- 안의 내용이 그대로 들어가므로 보안상의 문제가 발생할 수 있다.

 

- 어떤 변수에 특정 문자열을 넣어놓고 ${ }를 사용해서 집어넣을 수도 있다.

 

- mapper에 searchCondition 작성 (검색기능 관련)

<sql id="searchCondition">
  <choose>
     <when test="title != null and content != null">
        WHERE title LIKE '%'||#{title}||'%' 
        OR content LIKE '%'||#{content}||'%'
     </when>
     <when test="title != null">
        WHERE title LIKE '%'||#{title}||'%'
     </when>
     <when test="writer != null">
        WHERE writer LIKE '%'||#{writer}||'%'
     </when>
  </choose>
</sql>

 

 

- 이런 경우에 전처리구문을 사용하면 || 연결연산자를 사용하지 않아도 되어서 편리하다!

- 실행될 때 이 자리에 해당 내용이 쏙 들어간다!

 

 

Dao - getList

@Override
public List<CafeDto> getList(CafeDto dto) {
    /*
     * 검색 기능은 
     * 1. 제목+내용
     * 2. 제목
     * 3. 작성자
     * 검색 3가지 기능을 제공할 예정이다. 
     */
    return session.selectList("cafe.getList", dto);
}

 

- getList 메소드 작성. dto에 제목내용/제목/작성자 가 담겨서 넘어올 것이다.

 

- 이것을 바탕으로 동적 sql문을 작성해야 한다.

 

- sql문 작성, id도 부여

- 자주 쓰이는 내용을 이렇게 미리 저장해놓고 id를 사용해서 불러다가 사용할 수 있다.

 

- 동적으로 sql을 만들어주는 구문

 

- 이렇게 작성해서 빠르게 불러와 사용할 수 있다.

 

 

- include로 적어주면, 실행하기 전에 그 자리에 내용을 불러다놓고 실행한다고 생각하면 된다.

 

- 다른 file에 있으면 namespace. 형태로 점을 찍어서 찾아갈 수 있다

ex) file.searchCondition과 같이...

 

- 전체 글의 개수(getCount) 메소드에도 필요하다.

 


 

** <![CDATA[ ]]> 란? xml 문서에서 사용하는 문법이다.

 

- json vs xml 어플리케이션에서 자주 사용하는 문서는 둘 중 하나이다. 

- json에서는 {"key" : value}  로 작성

- xml은 <key> value </key>, <data key="value" /> 형태로 작성

 

- 그렇기 때문에 xml문서는 <> 꺾쇠기호에 굉장히 예민하다.

- < 나 >가 하나라도 잘못 들어가 있으면 인식오류가 발생한다.

- 따라서 내용에 문제를 발생시킬 소지가 있다. sql문 안에 <!-- --> 주석을 적지 않는 이유!

 

- 연산자 중 >=, <= 이 기호가 문제를 일으킨다. xml문서이기 때문에!

- CDATA 블럭은 이런 문제를 방지하기 위해서 있는 기호이다.

 

< ! [CDATA [ ] ] >

- XML에서 만든 정해진 block이다. 이런 식으로 쓰고 이 안에 쿼리문을 적으면 이 안에서는 < > 를 인식하지 않는다.

 

- BETWEEN은 오라클에서만 써야한다. 그런데 <= >= 를 쓰면 오류가 날 수 있다.

→ 이럴 때 CDATA 기호를 사용한다. 에러를 발생시키지 않기 위해!

 


 

- DaoImpl - getCount 메소드 작성

- service도 만들어주면 된다.

 

CafeService 인터페이스생성

package com.sy.spring04.cafe.service;

import javax.servlet.http.HttpServletRequest;

import com.sy.spring04.cafe.dto.CafeDto;

public interface CafeService {
	public void getList(HttpServletRequest request); //목록불러오기
	public void getDetail(HttpServletRequest request);
	public void saveContent(CafeDto dto); //글 저장
	public void updateContent(CafeDto dto);
	public void deleteContent(int num, HttpServletRequest request);
	public void getData(HttpServletRequest request); //글 수정하기 위해 정보 불러오는 기능
	
}

 

CafeServiceImpl

package com.sy.spring04.cafe.service;

import java.net.URLEncoder;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import com.sy.spring04.cafe.dao.CafeDao;
import com.sy.spring04.cafe.dto.CafeDto;

@Service
public class CafeServiceImpl implements CafeService{

	@Autowired
	private CafeDao cafeDao;
	
	@Override
	public void getList(HttpServletRequest request) {
		//한 페이지에 몇개씩 표시할 것인지
		final int PAGE_ROW_COUNT=5;
	    //하단 페이지를 몇개씩 표시할 것인지
	    final int PAGE_DISPLAY_COUNT=5;

	    //보여줄 페이지의 번호를 일단 1이라고 초기값 지정
	    int pageNum=1;

	    //페이지 번호가 파라미터로 전달되는지 읽어와 본다.
	    String strPageNum=request.getParameter("pageNum");
	    //만일 페이지 번호가 파라미터로 넘어 온다면
	    if(strPageNum != null){
	       //숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
	       pageNum=Integer.parseInt(strPageNum);
	    }   
	      
	    //보여줄 페이지의 시작 ROWNUM
	    int startRowNum=1+(pageNum-1)*PAGE_ROW_COUNT;
	    //보여줄 페이지의 끝 ROWNUM
	    int endRowNum=pageNum*PAGE_ROW_COUNT;
	      
	    /*
	       [ 검색 키워드에 관련된 처리 ]
	       -검색 키워드가 파라미터로 넘어올수도 있고 안넘어 올수도 있다.      
	    */
	    String keyword=request.getParameter("keyword");
	    String condition=request.getParameter("condition");
	    //만일 키워드가 넘어오지 않는다면 
	    if(keyword==null){
	       //키워드와 검색 조건에 빈 문자열을 넣어준다. 
	       //클라이언트 웹브라우저에 출력할때 "null" 을 출력되지 않게 하기 위해서  
	       keyword="";
	       condition=""; 
	    }

	    	//특수기호를 인코딩한 키워드를 미리 준비한다. 
	    String encodedK=URLEncoder.encode(keyword);
	         
	    //FileDto 객체에 startRowNum 과 endRowNum 을 담는다.
	    CafeDto dto=new CafeDto();
	    dto.setStartRowNum(startRowNum);
	    dto.setEndRowNum(endRowNum);
	   
	    //만일 검색 키워드가 넘어온다면 
	    if(!keyword.equals("")){
	       //검색 조건이 무엇이냐에 따라 분기 하기
	       if(condition.equals("title_content")){//제목 + 내용 검색인 경우
	          dto.setTitle(keyword);
	          dto.setContent(keyword);
	       }else if(condition.equals("title")){ //제목 검색인 경우
	          dto.setTitle(keyword);
	       }else if(condition.equals("writer")){ //작성자 검색인 경우
	          dto.setWriter(keyword);
	       } // 다른 검색 조건을 추가 하고 싶다면 아래에 else if() 를 계속 추가 하면 된다.
	    }
	      
	      
	    //파일 목록을 select 해 온다.(검색 키워드가 있는경우 키워드에 부합하는 전체 글) 
	    List<CafeDto> list=cafeDao.getList(dto);
	      
	    //전체 글의 갯수(검색 키워드가 있는경우 키워드에 부합하는 전체 글의 갯수)
	    int totalRow=cafeDao.getCount(dto);
	      
	    //하단 시작 페이지 번호 
	    int startPageNum = 1 + ((pageNum-1)/PAGE_DISPLAY_COUNT)*PAGE_DISPLAY_COUNT;
	    //하단 끝 페이지 번호
	    int endPageNum=startPageNum+PAGE_DISPLAY_COUNT-1;
	      
	    //전체 페이지의 갯수 구하기
	    int totalPageCount=(int)Math.ceil(totalRow/(double)PAGE_ROW_COUNT);
	    //끝 페이지 번호가 이미 전체 페이지 갯수보다 크게 계산되었다면 잘못된 값이다.
	    if(endPageNum > totalPageCount){
	         endPageNum=totalPageCount; //보정해 준다. 
	    }
	      
	    //응답에 필요한 데이터를 view page 에 전달하기 위해  request scope 에 담는다
	    request.setAttribute("list", list);
	    request.setAttribute("pageNum", pageNum);
	    request.setAttribute("startPageNum", startPageNum);
	    request.setAttribute("endPageNum", endPageNum);
	    request.setAttribute("totalPageCount", totalPageCount);
	    request.setAttribute("keyword", keyword);
	    request.setAttribute("encodedK", encodedK);
	    request.setAttribute("totalRow", totalRow); 
	    request.setAttribute("condition", condition);
	}
		
	

	@Override
	public void getDetail(HttpServletRequest request) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void saveContent(CafeDto dto) {		
		cafeDao.insert(dto);		
	}

	@Override
	public void updateContent(CafeDto dto) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteContent(int num, HttpServletRequest request) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void getData(HttpServletRequest request) {
		// TODO Auto-generated method stub
		
	}

}

 

- 필드명을 cafeDao로 적어준다.

- 지금까지는 dao 하나에만 의존했지만, 여러개의 dao에 의존할 수도 있다. 구분하기 위해 이름을 다르게 작성!

 


 

- insert, 페이징처리, 검색 기능 구현하기

 

CafeController

package com.sy.spring04.cafe.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.sy.spring04.cafe.dto.CafeDto;
import com.sy.spring04.cafe.service.CafeService;

@Controller
public class CafeController {

	@Autowired
	private CafeService service;
	
	@RequestMapping("/cafe/list")
	public String list(HttpServletRequest request) {
		service.getList(request);
		
		return "cafe/list";
	}
	
	@RequestMapping("/cafe/insertform")
	public String insertForm() {
		return "cafe/insertform";
	}
	
	@RequestMapping("/cafe/insert")
	public String insert(CafeDto dto, HttpSession session) {
		//글 작성자는 세션에서 얻어낸다.
		String writer=(String)session.getAttribute("id");
		//dto는 글의 제목과 내용만 있으므로 글작성자는 직접 넣어준다.
		dto.setWriter(writer);
		service.saveContent(dto);		
		return "cafe/insert";
	}
	
}

 

- 글 목록을 받아오면 service의 메소드에서 사용해준다.

 

service - getList 작성

@Override
public void getList(HttpServletRequest request) {
    //한 페이지에 몇개씩 표시할 것인지
    final int PAGE_ROW_COUNT=5;
    //하단 페이지를 몇개씩 표시할 것인지
    final int PAGE_DISPLAY_COUNT=5;

    //보여줄 페이지의 번호를 일단 1이라고 초기값 지정
    int pageNum=1;

    //페이지 번호가 파라미터로 전달되는지 읽어와 본다.
    String strPageNum=request.getParameter("pageNum");
    //만일 페이지 번호가 파라미터로 넘어 온다면
    if(strPageNum != null){
       //숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
       pageNum=Integer.parseInt(strPageNum);
    }   

    //보여줄 페이지의 시작 ROWNUM
    int startRowNum=1+(pageNum-1)*PAGE_ROW_COUNT;
    //보여줄 페이지의 끝 ROWNUM
    int endRowNum=pageNum*PAGE_ROW_COUNT;

    /*
       [ 검색 키워드에 관련된 처리 ]
       -검색 키워드가 파라미터로 넘어올수도 있고 안넘어 올수도 있다.      
    */
    String keyword=request.getParameter("keyword");
    String condition=request.getParameter("condition");
    //만일 키워드가 넘어오지 않는다면 
    if(keyword==null){
       //키워드와 검색 조건에 빈 문자열을 넣어준다. 
       //클라이언트 웹브라우저에 출력할때 "null" 을 출력되지 않게 하기 위해서  
       keyword="";
       condition=""; 
    }

        //특수기호를 인코딩한 키워드를 미리 준비한다. 
    String encodedK=URLEncoder.encode(keyword);

    //FileDto 객체에 startRowNum 과 endRowNum 을 담는다.
    CafeDto dto=new CafeDto();
    dto.setStartRowNum(startRowNum);
    dto.setEndRowNum(endRowNum);

    //만일 검색 키워드가 넘어온다면 
    if(!keyword.equals("")){
       //검색 조건이 무엇이냐에 따라 분기 하기
       if(condition.equals("title_content")){//제목 + 내용 검색인 경우
          dto.setTitle(keyword);
          dto.setContent(keyword);
       }else if(condition.equals("title")){ //제목 검색인 경우
          dto.setTitle(keyword);
       }else if(condition.equals("writer")){ //작성자 검색인 경우
          dto.setWriter(keyword);
       } // 다른 검색 조건을 추가 하고 싶다면 아래에 else if() 를 계속 추가 하면 된다.
    }


    //파일 목록을 select 해 온다.(검색 키워드가 있는경우 키워드에 부합하는 전체 글) 
    List<CafeDto> list=cafeDao.getList(dto);

    //전체 글의 갯수(검색 키워드가 있는경우 키워드에 부합하는 전체 글의 갯수)
    int totalRow=cafeDao.getCount(dto);

    //하단 시작 페이지 번호 
    int startPageNum = 1 + ((pageNum-1)/PAGE_DISPLAY_COUNT)*PAGE_DISPLAY_COUNT;
    //하단 끝 페이지 번호
    int endPageNum=startPageNum+PAGE_DISPLAY_COUNT-1;

    //전체 페이지의 갯수 구하기
    int totalPageCount=(int)Math.ceil(totalRow/(double)PAGE_ROW_COUNT);
    //끝 페이지 번호가 이미 전체 페이지 갯수보다 크게 계산되었다면 잘못된 값이다.
    if(endPageNum > totalPageCount){
         endPageNum=totalPageCount; //보정해 준다. 
    }

    //응답에 필요한 데이터를 view page 에 전달하기 위해  request scope 에 담는다
    request.setAttribute("list", list);
    request.setAttribute("pageNum", pageNum);
    request.setAttribute("startPageNum", startPageNum);
    request.setAttribute("endPageNum", endPageNum);
    request.setAttribute("totalPageCount", totalPageCount);
    request.setAttribute("keyword", keyword);
    request.setAttribute("encodedK", encodedK);
    request.setAttribute("totalRow", totalRow); 
    request.setAttribute("condition", condition);
}

 

- mapper에서 동적 sql문으로 때에 따라 필요한 내용만 들어갈 수 있게 한다.

- when문 안에 들어있는 부분이 해당 구문이 들어가는 조건!

 

- 그러면 service에서 원하는 조건에 맞는 데이터만 불러오도록 한다.

 

뷰페이지 - list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/cafe/list.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</head>
<body>
	<div class="container">
      <a href="${pageContext.request.contextPath }/cafe/insertform">새글 작성</a>
      <h3>CAFE 게시판 목록 보기</h3>
      <table class="table table-striped">
         <thead class="table-dark">
            <tr>
               <th>번호</th>
               <th>작성자</th>
               <th>제목</th>               
               <th>조회수</th>
               <th>등록일</th>
               <th>삭제</th>
            </tr>
         </thead>
         <tbody>
         <c:forEach var="tmp" items="${list }">
            <tr>
               <td>${tmp.num }</td>
               <td>${tmp.writer }</td>
               <td>
               	<a href="detail?num=${tmp.num }">${tmp.title }</a>
               </td>               
               <td>${tmp.viewCount }</td>
               <td>${tmp.regdate }</td>
               <td>
                  <c:if test="${tmp.writer eq sessionScope.id }">
                     <a href="javascript:deleteConfirm(${tmp.num })">삭제</a>
                  </c:if>
               </td>
            </tr>
         </c:forEach>
         </tbody>
      </table>
      <nav>
         <ul class="pagination">
            <%--
               startPageNum 이 1 이 아닌 경우에만 Prev 링크를 제공한다. 
               &condition=${condition}&keyword=${encodedK}
             --%>
            <c:if test="${startPageNum ne 1 }">
               <li class="page-item">
                  <a class="page-link" href="list?pageNum=${startPageNum-1 }&condition=${condition}&keyword=${encodedK}">Prev</a>
               </li>
            </c:if>
            <c:forEach var="i" begin="${startPageNum }" end="${endPageNum }">
               <li class="page-item ${pageNum eq i ? 'active' : '' }">
                  <a class="page-link" href="list?pageNum=${i }&condition=${condition}&keyword=${encodedK}">${i }</a>
               </li>
            </c:forEach>
            <%--
               마지막 페이지 번호가 전체 페이지의 갯수보다 작으면 Next 링크를 제공한다. 
             --%>
            <c:if test="${endPageNum lt totalPageCount }">
               <li class="page-item">
                  <a class="page-link" href="list?pageNum=${endPageNum+1 }&condition=${condition}&keyword=${encodedK}">Next</a>
               </li>
            </c:if>
         </ul>
      </nav>
      <!-- 검색 폼 -->
      <form action="list" method="get">
         <label for="condition">검색조건</label>   
         <select name="condition" id="condition">
            <option value="title_content" ${condition eq 'title_content' ? 'selected' : '' }>제목 + 내용</option>
            <option value="title" ${condition eq 'title' ? 'selected' : '' }>제목</option>
            <option value="writer" ${condition eq 'writer' ? 'selected' : '' }>작성자</option>
         </select>
         <input type="text" name="keyword" placeholder="검색어..." value="${keyword }"/>
         <button type="submit">검색</button>
      </form>
      <c:if test="${not empty condition }">
         <p>
            <strong>${totalRow }</strong> 개의 자료가 검색 되었습니다.
            <a href="list">리셋</a>
         </p>
      </c:if>
   </div>
   <script>
      function deleteConfirm(num){
         let isDelete=confirm("삭제 하시겠습니까?");
         if(isDelete){
            location.href="delete?num="+num;
         }
      }
   </script>
</body>
</html>

 

- jstl과 EL로 forEach문 돌면서 출력

 

- 검색 폼은 file에서 사용했던 내용을 그대로 사용해도 된다.

- filename이었던 부분만 content로 수정해서!

 

- 이렇게 리스트가 완성된다.

 


 

- 새글 작성 메소드

 

- 인터셉터에 cafe 하위항목 추가하기

 

 

- 스마트에디터 추가

SmartEditor-popup-photo_uploader.jsp

- 파일이 업로드되는 경로를 수정해주기. resources/upload로!

 

- 2개 파일을 수정해주면 된다.

 

insertform 컨트롤러

@RequestMapping("/cafe/insertform")
public String insertForm() {
    return "cafe/insertform";
}

 

- 스마트에디터 사용시의 주의사항! button 요소에 꼭 저 함수가 걸려있어야 한다.

 

- smarteditor 가 들어가있는 경로수정

 

- textarea의 크기가 스마트에디터의 크기가 된다.

- css로 textArea의 크기를 제어하면 크기 조절 가능!

 

- 그리고 name 속성의 value를 content로 해두어야 한다.

- 밑에 걸려있는 javascript 함수들이 적용되는 곳이 모두 content 라고 지정되어 있는 것을 기억!

 

- 만들어진 스마트에디터 폼

 

- 스마트에디터에서 파일을 저장하면 upload 폴더에 저장된다.

- 폴더는 미리 만들어두어야 한다!!


 

컨트롤러- insert 메소드

@RequestMapping("/cafe/insert")
public String insert(CafeDto dto, HttpSession session) {
    //글 작성자는 세션에서 얻어낸다.
    String writer=(String)session.getAttribute("id");
    //dto는 글의 제목과 내용만 있으므로 글작성자는 직접 넣어준다.
    dto.setWriter(writer);
    service.saveContent(dto);		
    return "cafe/insert";
}

 

- session으로 id를 얻어내고 제목/내용은 dto에서 얻어내기

 

@RequestMapping("/cafe/insert")
public ModelAndView insert(CafeDto dto, ModelAndView mView) {
    service.saveContent(dto);
    mView.setViewName("cafe/insert");
    return mView;
}

- 내가 잘못 작성했던 메소드.. session으로 id값을 얻어오지 않았다 ㅠㅠ

 

 

- 서비스- 새글 저장 메소드

@Override
public void saveContent(CafeDto dto) {		
    cafeDao.insert(dto);		
}

 

- 이 dto 안에는 지금 writer, title, content가 들어있을 것!

 

- mapper에 3가지 값이 들어가고, viewCount에는 0이라는 초기값이 들어간다.

 

 

- insert 뷰페이지 작성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/cafe/insert.jsp</title>
</head>
<body>	
	<script>
		alert("새글이 추가 되었습니다.");
		location.href="${pageContext.request.contextPath}/cafe/list";
	</script>
</body>
</html>

- 응답