본문 바로가기

프로그래밍/JAVA & SPRING

[LifeSoft] spring 20강 게시판 만들기1(목록, 글쓰기)

반응형

13. 게시판

가. 게시판의 주요 기능

 

1) 기본 기능

로그인 후 게시물 등록, 수정이 가능하도록 처리

글쓰기(스마트에디터 적용, 태그 문자 처리, 공백처리, 줄바꿈 처리)

게시물 상세정보, 조회수 증가 처리

게시물 수정

게시물 삭제(delete 방식)

게시물 삭제(update 방식)

검색기능

페이지 나누기

 

2) 파일업로드(ajax)

게시판에 파일 첨부

첨부파일 목록, 다운로드, 삭제

수정화면에서 새로운 파일 올리기

 

3) 댓글 기능

일반적인 방식으로 댓글 쓰기(RestController, Rest 방식)

$.ajax() 함수 호출하여 insert

컨트롤러에서 뷰로 포워딩한 responseText를 html 태그에 출력

컨트롤러에서 ArrayList를 json 형식으로 리턴받아 댓글 목록 출력(list_json.do)

목록에 댓글 갯수 표시

댓글 페이지 나누기(댓글 목록은 일반적인 방식으로 처리)

비밀댓글 쓰기, 표시

REST 방식으로 댓글 쓰기(insert_rest.do)

크롬 확장 프로그램을 이용한 입력 처리

{"replytext":"댓글", "replyer":"kim", "bno":"1", "secret_reply":"n" }

REST 방식으로 댓글 목록 출력

REST 방식으로 댓글 상세 화면, 수정, 삭제 기능 구현

 

나. 페이지 나누기

- 페이지당 게시물 수 : 10개

- 전체 게시물 수 : 991개

- 몇 페이지? : 100

  991 / 10 => 99.1 dhffla => 100

 

- 페이지의 시작번호, 끝번호 계산

where rn between 1 and 10

1페이지 => 1 ~10

2페이지 => 11 ~20

... ...

11페이지 => 101 ~110

57페이지 => 561 ~570

99페이지 => 981 ~990

100페이지 => 991 ~1000

 

시작번호=(현재페이지 - 1) * 페이지당 게시물수 + 1

  1페이지 => (1-1) * 10  + 1 => 1

  2페이지 => (2-1) * 10  + 1 => 11

  7페이지 => (7-1) * 10  + 1 => 61

끝번호 = 시작번호 + 페이지당 게시물수 - 1

  1페이지 => 1 + 10  - 1 = 10

  2페이지 => 11 + 10  - 1 = 20

 

* 전체 페이지 블록수

전체페이지갯수 / 10

91 / 10  => 9.1 => 10개

 

1 2 3 4 5 6 7 8 9 10 [다음]

[이전]11 12 13 14 15 16 17 18 19 20 [다음]

 

[이전]41 42 43 44 45 46 47 48 49 50 [다음]

[이전]51 52 53 54 55 56 57 58 59 60 [다음]

[이전]61 62 

 

다. 실습 소스

1)게시판 테이블

-- cascade constraints 제약조건까지 모두 삭제

drop table board cascade constraints;

create table board (

bno number not null,  -- 게시물 번호

title varchar2(200) not null, --제목

content varchar2(4000)  -- 내용

writer varchar2(50) not null,  -- 작성자아이디

regdate date default sysdate,  -- 날짜

viewcnt number default 0,  -- 조회수

primary key(bno)

);

 

시퀀스는 편리하긴 한데 객체를 관리해야 해서

서브쿼리로 해본다.

-- nvl(A, B)  A가 null이면 B, null이 아니면 A

insert into board (bno,title,content,writer) values
((select nvl(max(bno)+1,1) from board), '제목,,,', '내용,,,', 'id_park');

 

select * from board;

select max(bno)+1 from board;

select nvl(max(bno)+1, 1) from board;
commit

 

-- 조회수 증가 처리

update board set viewcnt=viewcnt+1 where bno=1;

-- 1번 게시물

select * from board where bno = 1;

-- 1번 게시물 수정

update board

set  writer='kim..' , title='제목수정..' , content='내용수정..'

where bno=1;

-- 1번 게시물 삭제

delete from board where bno = 1;

-- 2번 게시물 삭제

delete board where bno = 1;

 

after table board add show char(1) default 'Y';

-- 전체 검색이 아닐 때

select * from board

where writer like '%m%';

 

select * from board

where title like '%m%';

 

select * from board

where content  like '%m%';

 

-- 전체 검색일 때

select * from board

where writer like '%m%' or title like '%m%' or  content  like '%m%';

 

select * from board order by bno desc;

 

 

 

 

select bno,title,content,writer,b.regdate,viewcnt,username

from board b , tbl_member m

where b.writer = m.userid

order by bno desc;

 

delect from reply;

delect from attach;

delect from board;

-- 게시물 1000개 입력

declare
  i number := 1;
begin
  while i<=1000 loop
    insert into board (bno,title,content,writer)
    values
    ((select  nvl(max(bno)+1,1) from board) 
     ,'제목~'||i,'내용~~~'||i,'gksmf');
    i := i+1;
  end loop;
end;
/

 

select count(*) from board;

select * from board;

commit;

 

-- rownum : 출력순서

select *

from (

    select rownum as m, A.*

    from (

        select rownum, bno, title, content

            ,b.regdate,viewcnt,username

        from board b, tbl_member m

        where b.writer=m.userid

        order by bno desc, regdate desc

    ) A

) where rn between 41 and 50;

 

-- 게시판 : 댓글 (1:n, 1:다)

-- 댓글 테이블

drop table reply cascade constraints;

create table reply (

rno number not null primary key,

bno number default 0,

replytext varchar2(1000) not null,

replyer varchar2(50) not null,

regdate date default sysdate

);

-- foreign key 제약조건 추가

alter table reply add constraint fk_board

foreign key (bno) references board(bno);

 

-- 시퀀스 생성

drop sequence reply_seq;

create sequence reply_seq

 

 

 

 

select reply_seq.nextval from dual;

-- 1000번 게시물에 대한 댓글

insert into reply (rno, bno, replytext, replyer)

values (reply_seq,nextval, 1000, '댓글~~', 'kim');

 

select rno,bno,replyer,r.regdate,r.updatedate,name,replytext

from reply r, member m

where r.replyer=m.userid and bno=1000;

 

delete from reply;

delete from board;

 

select * from board order by bno desc;

 

-- 첨부파일 테이블

drop table attach cascade constraints;

create table attach (

  fullName varchar2(150) not  null, -- 첨부파일 이름

  bno number not null,  -- 게시물 번호

  regdate date default sysdate,  -- 업로드 날짜

  primary key (fullName)

)

 

-- bno 컬럼에 foreign key 설정

alter table attach add constraint fk_board_attach

foreign key(bno) references board(bno);

-- board 테이블의 bno 컬럼을 위한 시퀀스 생성

drop sequence seq_board;

create sequence seq_board

start with 1

increment by 1;

-- 게시판의 기존 게시물들을 삭제

delete from reply;

delete from board;

-- 게시물 번호가 1번인 글의 첨부파일

 

 

 

2) menu.jsp

<a href="${path}/board/list.do">게시판</a>

 

3)model.board.dto.BoardDTO.java

BoardDTO는 board 테이블 레코드 하나를 저장할 클래스.

public class BoardDTO {
 private int bno; //게시물 번호
 private String title; // 제목
 private String content; // 내용
 private String writer; // 작성자 아이디
 private Date regdate; // 날짜
 private int viewcnt; // 조회수
 private String name; // 작성자 이름(member테이블 조인)
 private int cnt;  // 댓글 갯수
 private String show; // 화면 표시 여부
 private String[] files; // 첨부파일의 이름 배열
 ...
}

 

 

5) BoardDAO.java

model.board.dao.BoardDAO.java (interface)

public interface BoardDAO {
 public void deleteFile(String fullName); // 첨부파일 삭제
 public List<String> getAttach(int bno); // 첨부파일 목록
 public void addAttach(String fullName); // 첨부파일 저장
 public void updateAttach(String fullName, int bno); // 첨부파일 수정
 public void create(BoardDTO dto) throws Exception; // 글쓰기
 public BoardDTO read(int bno) throws Exception; // 글읽기
 public void update(BoardDTO dto) throws Exception; // 글수정
 public void delete(int bno) throws Exception; // 글삭제
 public List<BoardDTO> listAll( // 목록(페이지 나누기, 검색 기능 포함)
   int start, int end,
   String search_option, String keyword) throws Exception;
 public void increaseViewcnt(int bno) throws Exception; // 조회수 증가처리  
 // 레코드 갯수 계산
 public int countArticel(String search_option,String keyword) throws Exception;
}

 

글목록 구하기 먼저 구현한다. (단순한 글목록 가져오기)

 

5) BoardDAOImpl.java

 

@Repository // dao bean으로 등록
public class BoardDAOImpl implements BoardDAO {

 @Inject // 의존관계 주입 (마이바티스 스프링이 관리)
 SqlSession sqlSession;
 ... 

 // 제일 처음 작업한다.
 @Override
 public List<BoardDTO> listAll(int start, int end,
   String search_option, String keyword) throws Exception {
  return sqlSession.selectList("board.listAll");
 }

}

 

7) boardMapper.xml

 <!-- 목록이기 때문에 본문은 필요없다. -->
 <select id="listAll" resultType="com.example.spring02.model.board.dto.BoardDTO">
  select bno,writer,title,regdate,viewcnt
  from board
  order by bno desc

 

8) controller.board.BoardController .java

@Controller // controller bean 등록
@RequestMapping("/board/*") // 공통적인 mapping
public class BoardController {

 @Inject
 BoardService boardService;
 
 @RequestMapping("list.do")
 public ModelAndView list() throws Exception {
  List<BoardDTO> list = boardService.listAll(0, 0, "", ""); // 목록
  ModelAndView mav = new ModelAndView();  
  mav.setViewName("board/list"); // 이동할 페이지 지정
  
  Map<String, Object> map = new HashMap<>(); //데이터가 많아서 Map에 담는다?
  map.put("list", list); // 맵에 자료 저장
  mav.addObject("map", map); // 데이터 저장  // 받는 쪽에서는 ${map.list}로 받는다.
  
  return mav;
 }
}

 

9) board/list.jsp

<thead>

 <tr>
    <th scope="col">번호</th>
    <th scope="col">제목</th>
    <th scope="col">이름</th>
    <th scope="col">날짜</th>
    <th scope="col">조회수</th>
 </tr>

</thead>

<c:forEach items="${map.list}" var="row">   
<tbody>  
   <tr scope="row">
    <td scope="col">${row.bno}</td>
    <td scope="col">${row.title}</td>
    <td scope="col">${row.writer}</td>
    <td scope="col"><fmt:formatDate value="${row.regdate}" pattern="yyyy-MM-dd HH:mm:ss" /></td>
    <td scope="col">${row.viewcnt}</td>
   </tr>
 </c:forEach> 

</tbody>

 

글목록이 잘 출력이 되면

글제목 클릭해서 상세보기로 가게 한다.

글쓰기는 로그인 후 가능하게 인터셉터를 설정한다.

 

<input class="" type="button" id="btnWrite" value="글쓰기">

 

$(function(){
 $("#btnWrite").on("click", function(){
  location.href="${path}/board/write.do"; 
 });
});

 

컨트롤러로 가서 매핑(write.do)을 추가한다.

컨트롤러에서 board/write.jsp를 호출하고

board/write에서 글작성 버튼을 누르면

컨트롤러의 insert.do를 호출한다.

여기서 작성자는 없는데 (로그인 하지 않았기 때문에)

이것은 인터셉터로 이후에 설정하겠다.

 

컨트롤러에 "insert.do" 매핑을 해주고

jsp에서 보내는 파라미터를 BoardDTO로 받아서

로직을 처리해준다.

if(writer == null ) { // 로그인화면으로 보냄 } => 인터셉터로 처리

 

@RequestMapping("insert.do")

 public String insert(@ModelAttribute BoardDTO dto, HttpSession session) throws Exception {

  String writer = (String) session.getAttribute("userid"); // 로그인한 사용자의 아이디

  dto.setWriter(writer);

  boardService.create(dto); // 레코드가 저장됨

  return "redirect:/board/list.do"; // 목록 갱신

 }



 

매퍼에서 입력할때 서브쿼리 대신 시퀀스를 사용한다.

첨부파일 올릴때 번호를 가져올 때 에로사항이 있다.

  insert into board (bno,title,content,writer)  values
  ((select nvl(max(bno)+1,1 from board) , #{title}, #{content}, #{userid})

 

글번호(bno)를 시퀀스로 만들어서 시퀀스를 가지고 관리한다.

( seq_board.nextval, #{title}, #{content}, #{userid})

-- board 테이블의 bno 컬럼을 위한 시퀀스 생성

delete from board;

drop sequence seq_board;(있으면 삭제)

create sequence seq_board

start with 1

increment by 1;

 

-- 시퀀스 적용 후 입력
insert into board (bno,title,content,writer)
values
( seq_board.nextval, '첫번째 제목이여', '첫번째 게시물 내용이여', 'gksmf');
select * from board;
commit;

 

로그인 후에 글쓰기를 해본다. 성공!!!

로그아웃 하고 글쓰기를 시도하면 에러가 뜨는데 에러내용 중에

Error setting null for parameter #3 with JdbcType OTHER .

3번 writer는 null이어서 에러메시지가 뜬다. (보기에 안좋다)

이런 이슈에 intercepter를 적용시켜보자.

servlet-contextxml에 mapping정보 설정을 추가한다.

  <mapping path="/board/write.do"/>
  <mapping path="/board/insert.do"/>

 사용자가 write.do나 insert.do를 요청하면

login interceptor를 거처가게 설정하는 것이다.

if(session.getAttribute("userid") == null) {로그인페이지로감.. }