티스토리 뷰

1.    페이징 처리에 필요한 3가지

-      1 ) 현재 페이지 번호 ( currPage )

-      2 ) 한 페이지의 게시물 개수 ( amount )

-      3 ) 페이지 번호 목록의 길이 ( pagesPerPage )


[ 1. 게시판 - 페이징 처리 ]

 

[ 1 - 1. 페이징 처리에 필요한 기준 정보를 지닌 클래스 생성 -  domain ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.domain;

import lombok.Data;


@Data
//+ 페이징 처리를 위한 2가지 기준 정보를 가지는 클래스
public class Criteria { 
	
	// + 현재 페이지 번호
	private int currPage = 1;
	
	// + 한 페이지에 보여지는 양
	private int amount= 10;

} // end class

 

[ 1 - 2. Mapper에 등록 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.DAOException;

// + 이 자바 인터페이스가 영속성 계층의 DAO 역할을 할
// + 마이바티스의 Mapper Interface로서의 역할을 하도록 구현
// + Mapper에서 CRUD하게 해준다. (***)
public interface BoardMapper {
	
	// ====================================================================
	// + 1. 게시물 전체 목록 출력하기
	// ====================================================================
	// + /*+ index_desc( 테이블명 indexName ) */는 오라클 힌트로 원래는 indexName을 지정해줘야 하지만,
	// + 지정해주지 않으면 테이블(tbl_board)의 PK를 기준으로 desc 내림차순으로 정렬해준다.
	// + INDEX (INDEX_ASC) : 오름차순 정렬, INDEX_DESC : 내림차순 정렬
	// + 같은 기능으로 ORDER BY가 있지만, 시스템에 부담이 되는 기능이기에 사용을 지양해줘야 한다.
	// ====================================================================
	
	@Select("SELECT /*+ index_desc(tbl_board) */ * FROM tbl_board WHERE bno > 0")
	public abstract List<BoardVO> selectAllList() throws DAOException;
	
	// ====================================================================
	// + 페이징 처리 (*****)
	// ====================================================================
	public abstract List<BoardVO> selectListWithPaging(Criteria cri) throws DAOException;
	
	
	// ====================================================================
	// + 2. 새로운 게시물 등록 -> Mapper XMl (***)
	// ====================================================================
	// + 게시물 등록에 필요한 데이터가 VO 객체로 들어왔기 때문에,
	// + @Param(바인드 변수명) 어노테이션과 @Insert(SQL문) 어노테이션으로 처리가 힘들다.
	// + 그렇기에 Mapper XML 파일에 SQL을 등록해서 처리하는 것이 좋다.
	// ====================================================================
	// + MyBatis 프레임 워크의 Mapper SQL의 자동실행규칙 :
	// ====================================================================
	// + 1 ) Mapper Interface가 소속된 패키지와 동일한 폴더구조를 CLASSPATH 아래에 만들어라
	// + CLASSPATH = src/main/java --> /WEB-INF/classes/ ( 이 폴더가 CLASSPATH의 시작점이다. )
	// + CLASSPATH = src/main/resources --> /WEB-INF/classes/ ( 이 폴더가 CLASSPATH의 시작점이다. )
	// + --> ex. src/main/resources/org/zerock/myapp/mapper
	// ====================================================================
	// + 2 ) 위에서 만든 폴더구조에서 Mapper Interface의 타입명과 동일한 이름으로 Mapper XML 파일 생성
	// + ex. 이 경우 BoardMapper라는 이름의 Mapper Interface이기에, BoardMapper.xml파일을 만들어야 한다.
	// ====================================================================
	// + 3 ) Mapper XML 파일에서 namespace의 이름을 Mapper Interface의 FQCN으로 지정한다.
	// + ex. 이 경우에는 namespace에 org.zerock.myapp.mapper.BoardMapper을 지정한다.
	// ====================================================================
	// + 4 ) SQL문장을 저장할 태그의 id(sqlId)값을 Mapper Interface 내에서 호출할 추상메소드의 이름과 동일하게 한다.
	// + ex. 이 경우에는 추상메소드의 이름이 insert이기에 id에 insert를 지정해 준다.
	// ====================================================================
	
	// @Insert("INSERT INTO tbl_board (bno, title, content, writer) VALUES ( ?, ?, ?, ? )")
	public abstract Integer insert(BoardDTO dto) throws DAOException; // insert
	
	public abstract Integer insertSelectKey(BoardDTO dto) throws DAOException;
	
	
	// ====================================================================
	// + 3. 게시물 삭제 -> @Delete + @Param ( #{ 바인드변수명 } )
	// ====================================================================
	// + Integer bno -> @Param("bno") -> #{bno}순으로 값이 전달된다.
	// ====================================================================
	
	@Delete("DELETE FROM tbl_board WHERE bno = #{bno}")
	public abstract Integer delete(@Param("bno")Integer bno) throws DAOException;
	
	
	// ====================================================================
	// + 4. 게시물 수정 -> Mapper XMl (***)
	// ====================================================================
	
	 public abstract Integer update(BoardDTO dto) throws DAOException;
	 
	 
	// ====================================================================
	// + 5. 특정한 게시물 출력 ( SELECT )
	// ====================================================================
	// + 칼럼의 순서는 VO 객체의 순서를 따라야 한다. (***)
	// + 그렇기에 VO는 테이블의 스키마를 보고 그대로 따라하는 것이 좋다. 
	// ====================================================================
	 
	 @Select("SELECT * FROM tbl_board WHERE bno = #{bno}")
	 public abstract BoardVO select(@Param("bno") Integer bno) throws DAOException;

	 
} // end interface

 

[ 1 - 3. Mapper.xml에 sql문 등록 ]

 

더보기

[ + 코드 보기 ]

<?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="org.zerock.myapp.mapper.BoardMapper">

    <insert id="insert">

        INSERT INTO tbl_board(bno, title, content, writer)
        VALUES( #{bno}, #{title}, #{content}, #{writer} )

    </insert>

    <insert id="insertSelectKey">

        <!-- 이는 bno는 시퀀스로 자동으로 얻어지는 값인데, (****) -->
        <!-- 이를 order=before로 하여 미리 값을 얻어가지고 이를 bno에 넘겨준다는 의미이다. -->
        <!-- order의 before와 after의 기준은 아래의 SQL문이다. -->
        <selectKey keyProperty="bno" order="BEFORE" resultType="int">
            SELECT "ADMIN"."ISEQ$$_93174".nextval FROM dual
        </selectKey>

        INSERT INTO tbl_board(bno, title, content, writer)
        VALUES( #{bno}, #{title}, #{content}, #{writer} )

    </insert>

    <update id="update">

        UPDATE tbl_board
        SET
            title = #{title},
            content = #{content},
            writer = #{writer},
            update_ts = CURRENT_DATE
        WHERE bno = #{bno}

    </update>

    <!-- 페이징 처리 (*****) -->
    <select 
        id="selectListWithPaging" 
        resultType="org.zerock.myapp.domain.BoardVO">
    
        SELECT
            /*+ INDEX_DESC(tbl_board) */
            *
        FROM
            tbl_board
        OFFSET (#{currPage} - 1) * #{amount} ROWS
        FETCH NEXT #{amount} ROWS ONLY
    
    </select>

</mapper>

 

[ 1 - 4. Service에 등록 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import java.util.List;

import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.ServiceException;

// + 롬복은 인터페이스에서 사용이 불가능하다.
// + 비지니스 로직은 Service에서 구현되어야 한다.
public interface BoardService {
	
	// + 1. 게시파 전체 목록을 조회하여 리스트로 반환해주는 메소드
	public abstract List <BoardVO> getAllList() throws ServiceException;
	public abstract List <BoardVO> getListPerPage(Criteria cri) throws ServiceException;
	
	// + 2. 게시판에서 특정 게시물 조회
	public abstract BoardVO get(BoardDTO dto) throws ServiceException;
	
	// + 3. 게시판에서 게시물 삭제 요청 처리
	public abstract Integer remove(BoardDTO dto) throws ServiceException;
	
	// + 4. 게시판에서 게시물 수정 요청 처리
	public abstract boolean update(BoardDTO dto) throws ServiceException;
	
	// + 5. 게시판에서 새로운 게시물 추가 요청 처리
	public abstract Integer add(BoardDTO dto) throws ServiceException;
	
	// + 6. 게시판에서 새로운 게시물 추가 요청 처리 2
	public abstract boolean addAuto(BoardDTO dto) throws ServiceException;

} // end interface

 

[ 1 - 5. Service 구현 클래스 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import java.util.List;
import java.util.Objects;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.DAOException;
import org.zerock.myapp.exception.ServiceException;
import org.zerock.myapp.mapper.BoardMapper;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Service
public class BoardServiceImpl implements BoardService, InitializingBean, DisposableBean {
// + InitializingBean는 빈 객체가 만들어진 직후에, DisposableBean는 빈이 파괴되기 직전에
	
	// ===========================================================
	
	// @Setter(onMethod_= {@Autowired})
	@Autowired
	private BoardMapper mapper;
	
	// ===========================================================
	// + 전체 게시글 조회
	// ===========================================================

	@Override
	public List<BoardVO> getAllList() throws ServiceException {
		// + 핵심 비지니스 로직 : DB 게시판 테이블을 조회하여, 게시글 전체목록을 얻어내 Model로 반환
		// + 하지만, Model은 Controller에서만 Model을 생성할 수 있다.
		// + 그러기 위해서는 mapper을 빈으로 가져와야 한다.
		
		log.trace("getAllList() invoked.");
		
		try {
			
			Objects.requireNonNull(this.mapper);
			return this.mapper.selectAllList();
			
		} catch ( DAOException e ) {
			throw new ServiceException(e);
		} // try - catch

	} // getAllList
	
	// ===========================================================
	// + 특정 게시글 조회
	// ===========================================================
	
	@Override
	public BoardVO get(BoardDTO dto) throws ServiceException {
		
		log.trace("get() invoked.");
		
		try {
			return this.mapper.select(dto.getBno());
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // get
	
	// ===========================================================
	// + 특정 게시글 삭제
	// ===========================================================
	
	@Override
	public Integer remove(BoardDTO dto) throws ServiceException {
		
		log.trace("getAllList() invoked.");
		
		try {
			return this.mapper.delete(dto.getBno());
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // remove
	
	// ===========================================================
	// + 게시물 추가 ( bno도 지정하는 버전 )
	// ===========================================================
	
	@Override
	public Integer add(BoardDTO dto) throws ServiceException {
		
		log.trace("getAllList() invoked.");
		
		try {
			return this.mapper.insert(dto);
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // add
	
	// ===========================================================
	// + 게시물 추가 2 ( bno를 시퀀스가 해주는 버전 ) (***)
	// ===========================================================

	@Override
	public boolean addAuto(BoardDTO dto) throws ServiceException {
		
		log.trace("getAllList() invoked.");

		try {
			return this.mapper.insertSelectKey(dto) == 1;
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // assAuto
	
	// ===========================================================
	// + 게시물 수정
	// ===========================================================
	
	@Override
	public boolean update(BoardDTO dto) throws ServiceException {
		
		log.trace("update() invoked.");
		
		try {
			return this.mapper.update(dto) == 1;
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // update
	
	// ===========================================================
	// + destroy : 후처리
	// ===========================================================

	@Override
	public void destroy() throws Exception {
		log.trace("destroy() invoked.");
		
	} // DisposableBean : destroy ( 후처리 작업 )
	
	// ===========================================================
	// + afterPropertiesSet : 필드의 의존성 주입이 정삭적인지 체크
	// ===========================================================

	@Override
	public void afterPropertiesSet() throws Exception {
		log.trace("afterPropertiesSet() invoked.");
		
		// + 의존성 주입 체크
		Objects.requireNonNull(this.mapper);
		log.trace("\t + this.mapper : {}", this.mapper);
		
	} // InitializingBean : afterPropertiesSet ( 전처리 작업)
	
	// ===========================================================
	// + getListPerPage : 페이징 처리한 목록 요청
	// ===========================================================
	
	@Override
	public List<BoardVO> getListPerPage(Criteria cri) throws ServiceException {
		log.trace("getListPerPage({}) invoked.", cri);
		
		try {
			return this.mapper.selectListWithPaging(cri);
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // getListPerPage

} // end class

 

[ 1 - 6. Controller에 등록 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.controller;

import java.util.List;
import java.util.Objects;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.ControllerException;
import org.zerock.myapp.service.BoardService;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Controller
@RequestMapping("/board/*")	// 기본 URI
public class BoardController implements InitializingBean {
	
	// + Bean 의존성 주입
	// + 자동 의존성 주입 발생 : (1) 주입받을 필드가 1개 (2) 이 필드를 매개변수로 가지는 생성자
	// + 위와 같은 상황일 때에는 따로 주입 시그널을 보내지 않아도 자동으로 주입된다.
	@Setter(onMethod_= {@Autowired})
	private BoardService boardService;
	
	// ===============================================
	// + 1. 게시판 목록조회 요청 처리 핸들러 메소드 
	// ===============================================
	
	// @ResponseStatus(code = HttpStatus.OK) : 컨트롤러 메소드 테스트 수행시 오류를 발생시킬 수 있다.
	@GetMapping("/list")	// 상세 URI
	public void list (Model model) throws ControllerException {
		log.info("list({}) invoked.", model);
		
		try {
			
			// + 목록 조회하는 service
			List<BoardVO> list = this.boardService.getAllList();
			
			// + 아래와 같은 방법 외에도 model이 아닌 Map으로 받아서, 
			// + model에 저장하는 것이 아니라 Map에 저장하는 방법도 있다.
			// Map<String, Object> map = new HashMap<>();
			// map.put("__LIST__", list);
			
			// + 리스트가 비어있지 않을 때 model로 바인딩
			if ( list != null ) {
				
				model.addAttribute("__LIST__",list);
				// + model은 Request Scope에 저장하게 되는데,
				// + 이때 공유 영역에서는 NULL을 저장하지 않는다.
				// + 그렇기에 이 경우 사실 if문은 필요 없다.
				
				list.forEach(log::info);
				
			} // if
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try - catch : ServiceException을 ControllerException으로
		
	} // list
	
	// ===============================================
	// + 특정 게시물 조회 처리 요청 메소드
	// ===============================================
	
	@GetMapping({"/get", "/modify"})	// 상세 URI
	public void get (BoardDTO dto, Model model) throws ControllerException {
		log.info("get({}) invoked.", model);
		
		try {
			
			// + 목록 조회하는 service
			BoardVO vo = this.boardService.get(dto);
			log.info("\t + vo : {}", vo);
			
			model.addAttribute("__BOARD__",vo);
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try - catch : ServiceException을 ControllerException으로
		
	} // list
	
	// ===============================================
	// + 2. 새로운 게시물 등록 처리 요청 메소드 (***)
	// ===============================================
	// + 단순 View 호출은 GET방식, 
	// + 비지니스 처리를 하기 위해서는 Post로 해야 한다.
	// ===============================================
	// + 전송파라미터를 객체로 얻어내면, Spring에서 이것을 Command Object라고 부르며
	// + Model을 사용하지 않아도 자동으로 View까지 전송된다. (***)
	// ===============================================
	
	@PostMapping("/register")
	public String register (BoardDTO dto, RedirectAttributes rttrs) throws ControllerException {
		
		log.trace("\t register({}, {}) invoked.", dto, rttrs);
		
		try {
			
			// + 필드에 주입받은 서비스 객체의 메소드 호출 -> 핵심 메소드 호출
			if ( this.boardService.add(dto) == 1 ) {
				rttrs.addAttribute("__RESULT__", "success");
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			// + 업데이트 한 후에는 전체 목록 조회 페이지로 이동해야 한다. (***)
			return "redirect:/board/list";
			
		} catch(Exception e) { 
			throw new ControllerException (e);
		} // try - catch
		
	} // register
	
	// ===============================================
	// + 등록 요청 메소드에서 return을 "redirect:/board/list"로 지정하였기에
	// + 만약 등록 요청이 들어왔다면, 요청처리 후 리스트로 돌아가게 된다.
	// ===============================================
	
	@PostMapping("/register2")
	public String register2 (BoardDTO dto, RedirectAttributes rttrs) throws ControllerException {
		
		log.trace("\t register2({}, {}) invoked.", dto, rttrs);
		
		try {
			
			// + 필드에 주입받은 서비스 객체의 메소드 호출 -> 핵심 메소드 호출
			if ( this.boardService.addAuto(dto) ) {
				
				rttrs.addAttribute("__RESULT__", "success");
				
				// rttrs.addFlashAttribute("__RESULT__", "success");
				// + addFlashAttribute도 Request Scope을 통해 전달은 가능하지만, 추천하지는 x
				
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			// + 업데이트 한 후에는 전체 목록 조회 페이지로 이동해야 한다. (***)
			return "redirect:/board/list";
			
		} catch(Exception e) { 
			throw new ControllerException (e);
		} // try - catch
		
	} // register
	
	// ===============================================
	// + 3. 게시판에 특정 게시물 삭제 요청 메소드
	// ===============================================
	
	@PostMapping("/remove")
	public String remove( BoardDTO dto, RedirectAttributes rttrs ) throws ControllerException {
		
		log.info("remove({}, {}) invoked.", dto, rttrs );
		
		try {
			
			if ( this.boardService.remove(dto) == 1 ) {
				rttrs.addAttribute("__RESULT__", "success");
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			return "redirect:/board/list";
			
		} catch(Exception e) {
			throw new ControllerException (e);
		} // try - catch
		
	} // remove
	
	// ===============================================
	// + 4. 새로운 게시물 수정 요청 메소드
	// ===============================================
	
	@PostMapping("/update")
	public String update ( BoardDTO dto, RedirectAttributes rttrs ) throws ControllerException {
		
		log.info("update({}, {}) invoked.", dto, rttrs);
		
		try {
			
			if ( this.boardService.update(dto) ) {
				rttrs.addAttribute("__RESULT__", "success");
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			return "redirect:/board/list";
			
		} catch (Exception e) { throw new ControllerException (e); } // try - catch
		
	} // update
	
	// ===============================================
	// + 5. 신규 게시물 등록화면 요청 처리
	// ===============================================
	
	@GetMapping("/register")
	public void register() {
		
		log.trace("register() invoked.");

	} // register
	
	// ===============================================
	// + 6. 선처리
	// ===============================================
	@Override
	public void afterPropertiesSet() throws Exception {
		
		log.trace("afterPropertiesSet() invoked.");
		
		// 필드에 의존성이 잘 주입 되었는지 확인한다.
		Objects.requireNonNull(this.boardService);
		log.info("\t + this.boardService : {}", this.boardService);
		
	} // afterPropertiesSet
	
	// ===============================================
	// + 7. 게시판 목록조회 요청 처리 핸들러 메소드 
	// ===============================================
	
	@GetMapping("/listPerPage")	// 상세 URI
	public void listPerPage ( Criteria cri, Model model) throws ControllerException {
		log.info("listPerPage({}, {}) invoked.", cri, model);
		
		try {
			
			List<BoardVO> list = this.boardService.getListPerPage(cri);
			list.forEach(log::info);
			
			model.addAttribute("__LIST_PER_PAEG__",list);
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try - catch : ServiceException을 ControllerException으로
		
	} // listPerPage

} // end class

 

[ 1 - 7. View 생성 ]

 

더보기

[ + 코드 보기 ]

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - board/listPerPage.jsp</title>

    <style>

        *{
            /* margin: 0 auto;으로 하면 가운데에 정렬되게 된다.  */
            margin: 0 auto;
            padding: 0;
        }

        #wrapper{
            width: 1024px;
            
            font-family: 'D2Coding';
            font-size: 14px;
        }

        table{
            width: 100%;

            /* border을 접어버린다. */
            border-collapse: collapse;
            border: 1px ridge lightslategrey;

            text-align: center;
        }

        th{
            padding: 10px;

            color: white;
            background-color: darkslateblue;

            font-size: 16px;
        }

        caption{
            font-size: 20px;
            font-weight: bold;

            padding-bottom: 10px;
        }

        tr:hover{

            /* tr에 호버링할 때, 마우스 커서가 손모양으로 변한다. */
            cursor: pointer;

            background-color: lightpink;

        }
        
        /* td태그 중에 2번째 td태그 */
        td:nth-child(2){
            text-align: left;
            padding-left: 10px;

            width: 40%;
        }

        #regBtn{
            width: 150px;
            height: 40px;

            border: none;

            font-size: 17px;
            font-weight: bold;

            color: white;
            background-color: dimgrey;

            float: right;
        }

        /* + 플롯팅 효과 없애는 방법!! (***) */
        #regBtn::after{
            content: '';
            display: block;
            clear: both;
        }

        a, a:link, a:visited{
            text-decoration-line: none;
        }

    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js" integrity="sha512-QDsjSX1mStBIAnNXx31dyvw4wVdHjonOwrkaIhpiIlzqGUCdsI62MwQtHpJF+Npy2SmSlGSROoNWQCOFpqbsOg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <script>

        $(function() {

            let prevStepResult = "${__RESULT__}";

            if( prevStepResult != null && prevStepResult.length > 0 ){
                alert(prevStepResult);
            } // if

            $('#regBtn').on('click', () => {
                self.location = "/board/register";
            });

         });

    </script>

</head>

<body>

    <h1>/WEB-INF/views/board/listPerPage.jsp</h1>
    <hr>

    <p>&nbsp;</p>

    <div id="wrapper">

        <button type="button" id="regBtn">REGISTER</button>

        <table border="1">
            <caption>TBL_BOARD</caption>

            <thead>
                <tr>
                    <th>bno</th>
                    <th>title</th>
                    <th>writer</th>
                    <th>insert_Time</th>
                    <th>update_Time</th>
                </tr>
            </thead>

            <tbody>
                <c:forEach var="board" items="${__LIST_PER_PAEG__}">
                   <tr>
                       <td><a>${board.bno}</a></td>
                       <td><a href="/board/get?bno=${board.bno}">${board.title}</a></td>
                       <td><a>${board.writer}</a></td>
                       <td><fmt:formatDate pattern="yyyy/mm/dd HH:mm:ss" value="${board.insertTs}" /></td>
                       <td><fmt:formatDate pattern="yyyy/mm/dd HH:mm:ss" value="${board.updateTs}" /></td>
                   </tr>
                </c:forEach>
            </tbody>

            <tfoot></tfoot>

        </table>

    </div>
    
</body>

</html>

[ 2. 페이징 처리 2 - 페이지 네이션 ( 페이지 번호 목록 ) 추가 ] (*****)

 

[ 2 - 1. 페이징 처리에 필요한 기준 정보 3가지를 가지고 있는 클래스 생성 - domain ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.domain;

import lombok.Data;


@Data
//+ 페이징 처리를 위한 3가지 기준 정보를 가지는 클래스
public class Criteria { 
	
	// + 현재 페이지 번호
	private int currPage = 1;
	
	// + 한 페이지에 보여지는 양
	private int amount= 10;
	
	// + 한 페이지에서 보여줄 pagination의 길이
	// + ( 페이지 번호 목록의 길이 )
	private int pagesPerPage = 5;

} // end class

 

[ 2 - 2. 페이지DTO 생성 ]

 

[ 2 - 3. Mapper에 총 레코드의 수를 구하는 추상 메소드 등록 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.DAOException;

// + 이 자바 인터페이스가 영속성 계층의 DAO 역할을 할
// + 마이바티스의 Mapper Interface로서의 역할을 하도록 구현
// + Mapper에서 CRUD하게 해준다. (***)
public interface BoardMapper {
	
	// ====================================================================
	// + 1. 게시물 전체 목록 출력하기
	// ====================================================================
	// + /*+ index_desc( 테이블명 indexName ) */는 오라클 힌트로 원래는 indexName을 지정해줘야 하지만,
	// + 지정해주지 않으면 테이블(tbl_board)의 PK를 기준으로 desc 내림차순으로 정렬해준다.
	// + INDEX (INDEX_ASC) : 오름차순 정렬, INDEX_DESC : 내림차순 정렬
	// + 같은 기능으로 ORDER BY가 있지만, 시스템에 부담이 되는 기능이기에 사용을 지양해줘야 한다.
	// ====================================================================
	
	@Select("SELECT /*+ index_desc(tbl_board) */ * FROM tbl_board WHERE bno > 0")
	public abstract List<BoardVO> selectAllList() throws DAOException;
	
	// ====================================================================
	// + 페이징 처리 (*****)
	// ====================================================================
	public abstract List<BoardVO> selectListWithPaging(Criteria cri) throws DAOException;
	
	
	// ====================================================================
	// + 2. 새로운 게시물 등록 -> Mapper XMl (***)
	// ====================================================================
	// + 게시물 등록에 필요한 데이터가 VO 객체로 들어왔기 때문에,
	// + @Param(바인드 변수명) 어노테이션과 @Insert(SQL문) 어노테이션으로 처리가 힘들다.
	// + 그렇기에 Mapper XML 파일에 SQL을 등록해서 처리하는 것이 좋다.
	// ====================================================================
	// + MyBatis 프레임 워크의 Mapper SQL의 자동실행규칙 :
	// ====================================================================
	// + 1 ) Mapper Interface가 소속된 패키지와 동일한 폴더구조를 CLASSPATH 아래에 만들어라
	// + CLASSPATH = src/main/java --> /WEB-INF/classes/ ( 이 폴더가 CLASSPATH의 시작점이다. )
	// + CLASSPATH = src/main/resources --> /WEB-INF/classes/ ( 이 폴더가 CLASSPATH의 시작점이다. )
	// + --> ex. src/main/resources/org/zerock/myapp/mapper
	// ====================================================================
	// + 2 ) 위에서 만든 폴더구조에서 Mapper Interface의 타입명과 동일한 이름으로 Mapper XML 파일 생성
	// + ex. 이 경우 BoardMapper라는 이름의 Mapper Interface이기에, BoardMapper.xml파일을 만들어야 한다.
	// ====================================================================
	// + 3 ) Mapper XML 파일에서 namespace의 이름을 Mapper Interface의 FQCN으로 지정한다.
	// + ex. 이 경우에는 namespace에 org.zerock.myapp.mapper.BoardMapper을 지정한다.
	// ====================================================================
	// + 4 ) SQL문장을 저장할 태그의 id(sqlId)값을 Mapper Interface 내에서 호출할 추상메소드의 이름과 동일하게 한다.
	// + ex. 이 경우에는 추상메소드의 이름이 insert이기에 id에 insert를 지정해 준다.
	// ====================================================================
	
	// @Insert("INSERT INTO tbl_board (bno, title, content, writer) VALUES ( ?, ?, ?, ? )")
	public abstract Integer insert(BoardDTO dto) throws DAOException; // insert
	
	public abstract Integer insertSelectKey(BoardDTO dto) throws DAOException;
	
	
	// ====================================================================
	// + 3. 게시물 삭제 -> @Delete + @Param ( #{ 바인드변수명 } )
	// ====================================================================
	// + Integer bno -> @Param("bno") -> #{bno}순으로 값이 전달된다.
	// ====================================================================
	
	@Delete("DELETE FROM tbl_board WHERE bno = #{bno}")
	public abstract Integer delete(@Param("bno")Integer bno) throws DAOException;
	
	
	// ====================================================================
	// + 4. 게시물 수정 -> Mapper XMl (***)
	// ====================================================================
	
	 public abstract Integer update(BoardDTO dto) throws DAOException;
	 
	 
	// ====================================================================
	// + 5. 특정한 게시물 출력 ( SELECT )
	// ====================================================================
	// + 칼럼의 순서는 VO 객체의 순서를 따라야 한다. (***)
	// + 그렇기에 VO는 테이블의 스키마를 보고 그대로 따라하는 것이 좋다. 
	// ====================================================================
	 
	 @Select("SELECT * FROM tbl_board WHERE bno = #{bno}")
	 public abstract BoardVO select(@Param("bno") Integer bno) throws DAOException;

	// ====================================================================
	// + 총 레코드의 수 ( 데이터의 수 ) 반환 
	// ====================================================================
	// + count()는 PK값으로 지정해야지 부담이 되지 않는다. (***)
	// ====================================================================
	 
	 @Select("SELECT count(bno) FROM tbl_board")
	 public abstract Integer getTotalAmount() throws DAOException;
	 
} // end interface

 

[ 2 - 4. Service에 등록 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import java.util.List;

import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.ServiceException;

// + 롬복은 인터페이스에서 사용이 불가능하다.
// + 비지니스 로직은 Service에서 구현되어야 한다.
public interface BoardService {
	
	// + 1. 게시판 전체 목록을 조회하여 리스트로 반환해주는 메소드
	public abstract List <BoardVO> getAllList() throws ServiceException;
	
	// + 페이징 처리 후의 게시물 조회
	public abstract List <BoardVO> getListPerPage(Criteria cri) throws ServiceException;
	
	// + 2. 게시판에서 특정 게시물 조회
	public abstract BoardVO get(BoardDTO dto) throws ServiceException;
	
	// + 3. 게시판에서 게시물 삭제 요청 처리
	public abstract Integer remove(BoardDTO dto) throws ServiceException;
	
	// + 4. 게시판에서 게시물 수정 요청 처리
	public abstract boolean update(BoardDTO dto) throws ServiceException;
	
	// + 5. 게시판에서 새로운 게시물 추가 요청 처리
	public abstract Integer add(BoardDTO dto) throws ServiceException;
	
	// + 6. 게시판에서 새로운 게시물 추가 요청 처리 2
	public abstract boolean addAuto(BoardDTO dto) throws ServiceException;
	
	// + 7. 총 레코드의 수를 반환
	public abstract Integer getTotal() throws ServiceException;

} // end interface

 

[ 2 - 5. Sercive 구현 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import java.util.List;
import java.util.Objects;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.exception.DAOException;
import org.zerock.myapp.exception.ServiceException;
import org.zerock.myapp.mapper.BoardMapper;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Service
public class BoardServiceImpl implements BoardService, InitializingBean, DisposableBean {
// + InitializingBean는 빈 객체가 만들어진 직후에, DisposableBean는 빈이 파괴되기 직전에
	
	// ===========================================================
	
	// @Setter(onMethod_= {@Autowired})
	@Autowired
	private BoardMapper mapper;
	
	// ===========================================================
	// + 전체 게시글 조회
	// ===========================================================

	@Override
	public List<BoardVO> getAllList() throws ServiceException {
		// + 핵심 비지니스 로직 : DB 게시판 테이블을 조회하여, 게시글 전체목록을 얻어내 Model로 반환
		// + 하지만, Model은 Controller에서만 Model을 생성할 수 있다.
		// + 그러기 위해서는 mapper을 빈으로 가져와야 한다.
		
		log.trace("getAllList() invoked.");
		
		try {
			
			Objects.requireNonNull(this.mapper);
			return this.mapper.selectAllList();
			
		} catch ( DAOException e ) {
			throw new ServiceException(e);
		} // try - catch

	} // getAllList
	
	// ===========================================================
	// + 특정 게시글 조회
	// ===========================================================
	
	@Override
	public BoardVO get(BoardDTO dto) throws ServiceException {
		
		log.trace("get() invoked.");
		
		try {
			return this.mapper.select(dto.getBno());
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // get
	
	// ===========================================================
	// + 특정 게시글 삭제
	// ===========================================================
	
	@Override
	public Integer remove(BoardDTO dto) throws ServiceException {
		
		log.trace("getAllList() invoked.");
		
		try {
			return this.mapper.delete(dto.getBno());
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // remove
	
	// ===========================================================
	// + 게시물 추가 ( bno도 지정하는 버전 )
	// ===========================================================
	
	@Override
	public Integer add(BoardDTO dto) throws ServiceException {
		
		log.trace("getAllList() invoked.");
		
		try {
			return this.mapper.insert(dto);
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // add
	
	// ===========================================================
	// + 게시물 추가 2 ( bno를 시퀀스가 해주는 버전 ) (***)
	// ===========================================================

	@Override
	public boolean addAuto(BoardDTO dto) throws ServiceException {
		
		log.trace("getAllList() invoked.");

		try {
			return this.mapper.insertSelectKey(dto) == 1;
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // assAuto
	
	// ===========================================================
	// + 게시물 수정
	// ===========================================================
	
	@Override
	public boolean update(BoardDTO dto) throws ServiceException {
		
		log.trace("update() invoked.");
		
		try {
			return this.mapper.update(dto) == 1;
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // update
	
	// ===========================================================
	// + destroy : 후처리
	// ===========================================================

	@Override
	public void destroy() throws Exception {
		log.trace("destroy() invoked.");
		
	} // DisposableBean : destroy ( 후처리 작업 )
	
	// ===========================================================
	// + afterPropertiesSet : 필드의 의존성 주입이 정삭적인지 체크
	// ===========================================================

	@Override
	public void afterPropertiesSet() throws Exception {
		log.trace("afterPropertiesSet() invoked.");
		
		// + 의존성 주입 체크
		Objects.requireNonNull(this.mapper);
		log.trace("\t + this.mapper : {}", this.mapper);
		
	} // InitializingBean : afterPropertiesSet ( 전처리 작업)
	
	// ===========================================================
	// + getListPerPage : 페이징 처리한 목록 요청
	// ===========================================================
	
	@Override
	public List<BoardVO> getListPerPage(Criteria cri) throws ServiceException {
		log.trace("getListPerPage({}) invoked.", cri);
		
		try {
			return this.mapper.selectListWithPaging(cri);
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // getListPerPage
	
	// ===========================================================
	// + getTotal : 레코드의 반환 
	// ===========================================================
	
	@Override
	public Integer getTotal() throws ServiceException {
		
		log.trace("getTotal() invoked.");
		
		try {
			return this.mapper.getTotalAmount();
		} catch (DAOException e) {
			throw new ServiceException(e);
		} // try - catch
		
	} // getTotal

} // end class

 

[ 2 - 6. Controller 등록 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.controller;

import java.util.List;
import java.util.Objects;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.domain.Criteria;
import org.zerock.myapp.domain.PageDTO;
import org.zerock.myapp.exception.ControllerException;
import org.zerock.myapp.service.BoardService;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Controller
@RequestMapping("/board/*")	// 기본 URI
public class BoardController implements InitializingBean {
	
	// + Bean 의존성 주입
	// + 자동 의존성 주입 발생 : (1) 주입받을 필드가 1개 (2) 이 필드를 매개변수로 가지는 생성자
	// + 위와 같은 상황일 때에는 따로 주입 시그널을 보내지 않아도 자동으로 주입된다.
	@Setter(onMethod_= {@Autowired})
	private BoardService boardService;
	
	// ===============================================
	// + 1. 게시판 목록조회 요청 처리 핸들러 메소드 
	// ===============================================
	
	// @ResponseStatus(code = HttpStatus.OK) : 컨트롤러 메소드 테스트 수행시 오류를 발생시킬 수 있다.
	@GetMapping("/listold")	// 상세 URI
	public void list (Model model) throws ControllerException {
		log.info("list({}) invoked.", model);
		
		try {
			
			// + 목록 조회하는 service
			List<BoardVO> list = this.boardService.getAllList();
			
			// + 아래와 같은 방법 외에도 model이 아닌 Map으로 받아서, 
			// + model에 저장하는 것이 아니라 Map에 저장하는 방법도 있다.
			// Map<String, Object> map = new HashMap<>();
			// map.put("__LIST__", list);
			
			// + 리스트가 비어있지 않을 때 model로 바인딩
			if ( list != null ) {
				
				model.addAttribute("__LIST__",list);
				// + model은 Request Scope에 저장하게 되는데,
				// + 이때 공유 영역에서는 NULL을 저장하지 않는다.
				// + 그렇기에 이 경우 사실 if문은 필요 없다.
				
				list.forEach(log::info);
				
			} // if
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try - catch : ServiceException을 ControllerException으로
		
	} // list
	
	// ===============================================
	// + 특정 게시물 조회 처리 요청 메소드
	// ===============================================
	
	@GetMapping({"/get", "/modify"})	// 상세 URI
	public void get (BoardDTO dto, Model model) throws ControllerException {
		log.info("get({}) invoked.", model);
		
		try {
			
			// + 목록 조회하는 service
			BoardVO vo = this.boardService.get(dto);
			log.info("\t + vo : {}", vo);
			
			model.addAttribute("__BOARD__",vo);
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try - catch : ServiceException을 ControllerException으로
		
	} // list
	
	// ===============================================
	// + 2. 새로운 게시물 등록 처리 요청 메소드 (***)
	// ===============================================
	// + 단순 View 호출은 GET방식, 
	// + 비지니스 처리를 하기 위해서는 Post로 해야 한다.
	// ===============================================
	// + 전송파라미터를 객체로 얻어내면, Spring에서 이것을 Command Object라고 부르며
	// + Model을 사용하지 않아도 자동으로 View까지 전송된다. (***)
	// ===============================================
	
	@PostMapping("/register")
	public String register (BoardDTO dto, RedirectAttributes rttrs) throws ControllerException {
		
		log.trace("\t register({}, {}) invoked.", dto, rttrs);
		
		try {
			
			// + 필드에 주입받은 서비스 객체의 메소드 호출 -> 핵심 메소드 호출
			if ( this.boardService.add(dto) == 1 ) {
				rttrs.addAttribute("__RESULT__", "success");
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			// + 업데이트 한 후에는 전체 목록 조회 페이지로 이동해야 한다. (***)
			return "redirect:/board/list";
			
		} catch(Exception e) { 
			throw new ControllerException (e);
		} // try - catch
		
	} // register
	
	// ===============================================
	// + 등록 요청 메소드에서 return을 "redirect:/board/list"로 지정하였기에
	// + 만약 등록 요청이 들어왔다면, 요청처리 후 리스트로 돌아가게 된다.
	// ===============================================
	
	@PostMapping("/register2")
	public String register2 (BoardDTO dto, RedirectAttributes rttrs) throws ControllerException {
		
		log.trace("\t register2({}, {}) invoked.", dto, rttrs);
		
		try {
			
			// + 필드에 주입받은 서비스 객체의 메소드 호출 -> 핵심 메소드 호출
			if ( this.boardService.addAuto(dto) ) {
				
				rttrs.addAttribute("__RESULT__", "success");
				
				// rttrs.addFlashAttribute("__RESULT__", "success");
				// + addFlashAttribute도 Request Scope을 통해 전달은 가능하지만, 추천하지는 x
				
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			// + 업데이트 한 후에는 전체 목록 조회 페이지로 이동해야 한다. (***)
			return "redirect:/board/list";
			
		} catch(Exception e) { 
			throw new ControllerException (e);
		} // try - catch
		
	} // register
	
	// ===============================================
	// + 3. 게시판에 특정 게시물 삭제 요청 메소드
	// ===============================================
	
	@PostMapping("/remove")
	public String remove( BoardDTO dto, RedirectAttributes rttrs ) throws ControllerException {
		
		log.info("remove({}, {}) invoked.", dto, rttrs );
		
		try {
			
			if ( this.boardService.remove(dto) == 1 ) {
				rttrs.addAttribute("__RESULT__", "success");
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			return "redirect:/board/list";
			
		} catch(Exception e) {
			throw new ControllerException (e);
		} // try - catch
		
	} // remove
	
	// ===============================================
	// + 4. 새로운 게시물 수정 요청 메소드
	// ===============================================
	
	@PostMapping("/update")
	public String update ( BoardDTO dto, RedirectAttributes rttrs ) throws ControllerException {
		
		log.info("update({}, {}) invoked.", dto, rttrs);
		
		try {
			
			if ( this.boardService.update(dto) ) {
				rttrs.addAttribute("__RESULT__", "success");
			} else {
				rttrs.addAttribute("__RESULT__", "failed");
			} // if - else
			
			return "redirect:/board/list";
			
		} catch (Exception e) { throw new ControllerException (e); } // try - catch
		
	} // update
	
	// ===============================================
	// + 5. 신규 게시물 등록화면 요청 처리
	// ===============================================
	
	@GetMapping("/register")
	public void register() {
		
		log.trace("register() invoked.");

	} // register
	
	// ===============================================
	// + 6. 선처리
	// ===============================================
	@Override
	public void afterPropertiesSet() throws Exception {
		
		log.trace("afterPropertiesSet() invoked.");
		
		// 필드에 의존성이 잘 주입 되었는지 확인한다.
		Objects.requireNonNull(this.boardService);
		log.info("\t + this.boardService : {}", this.boardService);
		
	} // afterPropertiesSet
	
	// ===============================================
	// + 7. 게시판 목록조회 요청 처리 핸들러 메소드 + 전체 레코드 수 반환
	// ===============================================
	
	@GetMapping("/list")	// 상세 URI
	public String listPerPage ( Criteria cri, Model model) throws ControllerException {
		log.info("listPerPage({}, {}) invoked.", cri, model);
		
		try {
			
			List<BoardVO> list = this.boardService.getListPerPage(cri);
			list.forEach(log::info);
			
			// + 페이징 처리를 위한 Pagination 정보를 자동산출하는 PageDTO 객체 생성 및
			// + View에서 이 pageDTO 객체의 정보를 사용할 수 있도록 Model에 추가
			PageDTO pageDTO = new PageDTO( cri, this.boardService.getTotal() );
			model.addAttribute("__PAGENATION__", pageDTO);
			
			// + value가 NULL이면 공유속성에 추가가 안되기에, NULL인지 아닌지 파악하지 않아도 OK!
			model.addAttribute("__LIST__",list);
			
			return "/board/list";
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try - catch : ServiceException을 ControllerException으로
		
	} // listPerPage

} // end class

 

[ 2 - 7. View에서 페이지 넘버 나오는 부분 추가 ]

 

더보기

[ + list 코드 보기 ]

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - board/list.jsp</title>

    <style>

        *{
            /* margin: 0 auto;으로 하면 가운데에 정렬되게 된다.  */
            margin: 0 auto;
            padding: 0;
        }

        #wrapper{
            width: 1024px;
            
            font-family: 'D2Coding';
            font-size: 14px;
        }

        table{
            width: 100%;

            /* border을 접어버린다. */
            border-collapse: collapse;
            border: 1px ridge lightslategrey;

            text-align: center;
        }

        th{
            padding: 10px;

            color: white;
            background-color: darkslateblue;

            font-size: 16px;
        }

        caption{
            font-size: 20px;
            font-weight: bold;

            padding-bottom: 10px;
        }

        tr:hover{

            /* tr에 호버링할 때, 마우스 커서가 손모양으로 변한다. */
            cursor: pointer;

            background-color: lightpink;

        }
        
        /* td태그 중에 2번째 td태그 */
        td:nth-child(2){
            text-align: left;
            padding-left: 10px;

            width: 40%;
        }

        #regBtn{
            width: 150px;
            height: 40px;

            border: none;

            font-size: 17px;
            font-weight: bold;

            color: white;
            background-color: dimgrey;

            float: right;
        }

        /* + 플롯팅 효과 없애는 방법!! (***) */
        #regBtn::after{
            content: '';
            display: block;
            clear: both;
        }

        /* a태그 효과 없애기 */
        a, a:link, a:visited{
            text-decoration: none;
            color: black;

            cursor: pointer
        }

        /* + 페이지 번호 목록 */
        #pagenation{
            width: 100%;
            margin: 0 auto;
        }

        #pagenation ul {
            float: right;
        }

        #pagenation li {
            float: left;

            list-style: none;
            /* 가로 중앙 정렬 */
            text-align: center;
            /* 세로 중앙 정렬 */
            line-height: 30px;

            width: 30px;
            height: 30px;
        }

        .prev, .next{

            /* !important는 무조건 이것을 적용하라는 의미 */
            width: 70px !important;

            color: white !important;
            background-color: darkgray !important;
        }

        /* 현재 페이지는 다르게 나오게 하기 */

        .currPage{
            color: red !important;
        }

    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js" integrity="sha512-QDsjSX1mStBIAnNXx31dyvw4wVdHjonOwrkaIhpiIlzqGUCdsI62MwQtHpJF+Npy2SmSlGSROoNWQCOFpqbsOg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <script>

        $(function() {

            let prevStepResult = "${__RESULT__}";

            if( prevStepResult != null && prevStepResult.length > 0 ){
                alert(prevStepResult);
            } // if

            // 등록
            $('#regBtn').on('click', () => {
                self.location = "/board/register";
            });

            // 이전 / 다음 페이지 목록
            // + 익명함수(에로우펀션) 내에서의 this는 도큐먼트를 의미한다.
            $('a.prev, a.next').click( function (e) {

                // ==============================================================
                // ( 주의 ) 함수 내에서의 this는 arrow 함수 내에서의 this와 다르다.
                // 1. 함수에서의 this : 이벤트 타겟 ( ex. a.prev, a.next )
                // 2. arrow 함수에서의 this : DOM
                // ==============================================================

                // a태그의 기본 기능을 무력화시킨다.
                // + 기존의 click 이벤트 무력화
                e.preventDefault();

                // form태그 직접 조작 및 전송
                let formObj = $('#pagenationForm');
                formObj.attr('action', '/board/list');
                formObj.attr('method', 'GET');

                console.clear();
                console.log('>>> this.href : ', $(this).attr('href'));

                formObj.find('input[type=hidden][name=currPage]').val($(this).attr('href'));
                // formObj.find('input[name=currPage]').val($(this).attr('href'));도 가능

                formObj.submit();

            });
            

         });

    </script>

</head>

<body>

    <h1>/WEB-INF/views/board/list.jsp</h1>
    <hr>

    <p>&nbsp;</p>

    <div id="wrapper">

        <button type="button" id="regBtn">REGISTER</button>

        <table border="1">
            <caption>TBL_BOARD</caption>

            <thead>
                <tr>
                    <th>bno</th>
                    <th>title</th>
                    <th>writer</th>
                    <th>insert_Time</th>
                    <th>update_Time</th>
                </tr>
            </thead>

            <tbody>
                <c:forEach var="board" items="${__LIST__}">
                   <tr>
                       <td><a>${board.bno}</a></td>
                       <td><a href="/board/get?bno=${board.bno}&currPage=${__PAGENATION__.cri.currPage}">${board.title}</a></td>
                       <td><a>${board.writer}</a></td>
                       <td><fmt:formatDate pattern="yyyy/MM/dd HH:mm:ss" value="${board.insertTs}" /></td>
                       <td><fmt:formatDate pattern="yyyy/MM/dd HH:mm:ss" value="${board.updateTs}" /></td>
                   </tr>
                </c:forEach>
            </tbody>

            <tfoot></tfoot>

        </table>

        <p>&nbsp;</p>

        <div id="pagenation">

            <form id="pagenationForm">

                <!-- 3가지 기준 정보를 hidden으로 제공 -->
                <input type="hidden" name="currPage">

                <!-- PAGEDTO를 통해서 PAGENATION 출력 -->
                <ul>

                    <!-- 이전 페이지 목록 -->
                    <!-- 이전 페이지 목록이 있을 때만 나타난다. -->
                    <c:if test="${__PAGENATION__.prev}">

                        <li class="prev"> <a href="${__PAGENATION__.startPage -1}" class="prev">이전</a></li>

                    </c:if>

                    <!-- 현재 PAGENATION 범위에 속한 페이지 번호 목록 출력 -->
                    <c:forEach var="pageNum" begin="${__PAGENATION__.startPage}" end="${__PAGENATION__.endPage}">

                        <li> 
                            <a 
                                href="/board/list?currPage=${pageNum}" 
                                class="${pageNum == __PAGENATION__.cri.currPage ? 'currPage' : '' }" > 

                                <strong>${pageNum}</strong>

                            </a>
                        </li>

                    </c:forEach>

                    <!-- 다음 페이지 목록 -->
                    <!-- 다음 페이지 목록이 있을 때만 나타난다. -->
                    <c:if test="${__PAGENATION__.next}">

                        <li class="next"> <a href="${__PAGENATION__.endPage +1}" class="next">다음</a></li>

                    </c:if>

                </ul>

            </form>

        </div>

    </div>
    
</body>

</html>

 

더보기

[ + get 코드 보기 ]

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - bord/list.jsp</title>

    <style>
        
        *{
            margin: 0 auto;
            padding: 0;
        }

        #wrapper{
            width: 1024px;
            margin-top: 30px;

            font-family: 'D2Coding';
            font-size: 14px;
        }

        #listBtn, #modifyBtn{
            width: 80px;
            height: 40px;

            border: 0px;

            font-size: 16px;
            font-weight: bold;
        }

        #modifyBtn{
            color: white;
            background-color: darkolivegreen;
        }

        #listBtn{
            color: white;
            background-color: darkslateblue;
        }

        button:hover{
            cursor: pointer;
        }

    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js" integrity="sha512-QDsjSX1mStBIAnNXx31dyvw4wVdHjonOwrkaIhpiIlzqGUCdsI62MwQtHpJF+Npy2SmSlGSROoNWQCOFpqbsOg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <script>

        $(function() {

            // 목록으로 돌아갔을 때, 그 전에 볼 수 있는 페이지로 돌아갈 수 있도록 currPage를 넘긴다.
            $('#modifyBtn').on('click', () => {
                self.location = "/board/modify?bno=${__BOARD__.bno}&currPage=${param.currPage}";
            });

            $('#listBtn').on('click', () => {
                self.location = "/board/list?currPage=${param.currPage}";
            });

        });

    </script>

</head>

<body>

    <h1>/WEB-INF/views/board/get.jsp</h1>
    <hr>

    <div id="wrapper">

        <form action="#">

            <input type="hidden" name="currPage">

            <table>

                <tbody>

                    <tr>
                        <td><label for="bno">글번호</label></td>
                        <td><input type="text" name="bno" id="bno" size="20" value="${__BOARD__.bno}" readonly></td>
                    </tr>

                    <!-- 제목 -->
                    <tr>
                        <td><label for="title">제목</label></td>
                        <td><input type="text" name="title" id="title" size="50" value="${__BOARD__.title}" readonly></td>
                    </tr>

                    <tr>
                        <td><label for="content">내용</label></td>
                        <td><textarea name="content" id="content" cols="52" rows="10" readonly>${__BOARD__.content}</textarea></td>
                    </tr>

                    <tr>
                        <td><label for="title">제목</label></td>
                        <td><input type="text" name="writer" id="writer" size="20" value="${__BOARD__.writer}" readonly></td>
                    </tr>

                    <tr>
                        <td colspan="">&nbsp;</td>
                    </tr>

                    <tr>

                        <td colspan="2">
                            <button type="button" id="modifyBtn">수정</button>
                            <button type="button" id="listBtn">목록</button>
                        </td>
                    </tr>

                </tbody>

            </table>

        </form>

    </div>
    
</body>

</html>

 

더보기

[ + modify 코드 보기 ]

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - bord/modify.jsp</title>

    <style>
        
        *{
            margin: 0 auto;
            padding: 0;
        }

        #wrapper{
            width: 1024px;
            margin-top: 30px;

            font-family: 'D2Coding';
            font-size: 14px;
        }

        #listBtn, #submitBtn, #deleteBtn{
            width: 80px;
            height: 40px;

            border: 0px;

            font-size: 16px;
            font-weight: bold;
        }

        #submitBtn{
            color: white;
            background-color: darkolivegreen;
        }

        #listBtn{
            color: white;
            background-color: darkslateblue;
        }

        #deleteBtn{
            color: white;
            background-color: red;
        }

        button:hover{
            cursor: pointer;
        }

    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js" integrity="sha512-QDsjSX1mStBIAnNXx31dyvw4wVdHjonOwrkaIhpiIlzqGUCdsI62MwQtHpJF+Npy2SmSlGSROoNWQCOFpqbsOg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <script>

        $(function() {

            // 삭제버튼 클릭시, Post /board/remove?bno=로 요청 전송
            $('#deleteBtn').on('click', () => {

                let formObj = $('form');
                
                // formObj.attr(속성이름, 속성값);
                formObj.attr('action', '/board/remove');
                formObj.attr('method', 'POST');
                formObj.submit();

            });

            $('#listBtn').on('click', () => {
                self.location = "/board/list?currPage=${param.currPage}";
            });

        });

    </script>

</head>

<body>

    <h1>/WEB-INF/views/board/modify.jsp</h1>
    <hr>

    <div id="wrapper">

        <form action="/board/update" method="post">

            <input type="hidden" name="currPage">

            <table>

                <tbody>

                    <tr>
                        <td><label for="bno">글번호</label></td>
                        <td><input type="text" name="bno" id="bno" size="20" value="${__BOARD__.bno}" readonly></td>
                    </tr>

                    <!-- 제목 -->
                    <tr>
                        <td><label for="title">제목</label></td>
                        <td><input type="text" name="title" id="title" size="50" value="${__BOARD__.title}"></td>
                    </tr>

                    <tr>
                        <td><label for="content">내용</label></td>
                        <td><textarea name="content" id="content" cols="52" rows="10">${__BOARD__.content}</textarea></td>
                    </tr>

                    <tr>
                        <td><label for="title">작성자</label></td>
                        <td><input type="text" name="writer" id="writer" size="20" value="${__BOARD__.writer}"></td>
                    </tr>

                    <tr>
                        <td colspan="">&nbsp;</td>
                    </tr>

                    <tr>

                        <td colspan="2">
                            <button type="submit" id="submitBtn">수정등록</button>
                            <button type="button" id="deleteBtn">삭제</button>
                            <button type="button" id="listBtn">목록</button>
                        </td>
                    </tr>

                </tbody>

            </table>

        </form>

    </div>
    
</body>

</html>

 

더보기

[ + register 코드 보기 ]

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - bord/register.jsp</title>

    <style>
        
        *{
            margin: 0 auto;
            padding: 0;
        }

        #wrapper{
            width: 1024px;
            margin-top: 30px;

            font-family: 'D2Coding';
            font-size: 14px;
        }

        #listBtn, #submitBtn {
            width: 80px;
            height: 40px;

            border: 0px;

            font-size: 16px;
            font-weight: bold;
        }

        #submitBtn{
            color: white;
            background-color: darkolivegreen;
        }

        #listBtn{
            color: white;
            background-color: darkslateblue;
        }

        button:hover{
            cursor: pointer;
        }

    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js" integrity="sha512-QDsjSX1mStBIAnNXx31dyvw4wVdHjonOwrkaIhpiIlzqGUCdsI62MwQtHpJF+Npy2SmSlGSROoNWQCOFpqbsOg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <script>

        $(function() {

            $('#listBtn').on('click', () => {
                self.location = "/board/list?currPage=${param.currPage}";
            });

        });

    </script>

</head>

<body>

    <h1>/WEB-INF/views/board/register.jsp</h1>
    <hr>

    <div id="wrapper">

        <form action="/board/register2" method="post">

            <input type="hidden" name="currPage">

            <table>

                <tbody>

                    <!-- 제목 -->
                    <tr>
                        <td><label for="title">제목</label></td>
                        <td><input type="text" name="title" id="title" size="50"></td>
                    </tr>

                    <tr>
                        <td><label for="content">내용</label></td>
                        <td><textarea name="content" id="content" cols="52" rows="10"></textarea></td>
                    </tr>

                    <tr>
                        <td><label for="title">작성자</label></td>
                        <td><input type="text" name="writer" id="writer" size="20"></td>
                    </tr>

                    <tr>
                        <td colspan="">&nbsp;</td>
                    </tr>

                    <tr>

                        <td colspan="2">
                            <button type="submit" id="submitBtn">등록</button>
                            <button type="button" id="listBtn">목록</button>
                        </td>
                    </tr>

                </tbody>

            </table>

        </form>

    </div>
    
</body>

</html>

 

 

 

728x90
댓글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
최근에 올라온 글
Total
Today
Yesterday
공지사항