티스토리 뷰
KH 정보교육원 [ Java ]
KH 119일차 - Spring ( Oracle 힌트 / 게시판 만들기 / 페이징 처리 ) (*******)
monimoni 2022. 8. 17. 15:27
[ 1. Oracle 힌트 ]
더보기
[ + 코드 보기 ]
-- 테이블의 구조 확인
DESC tbl_board;
-- ==============================================
-- 오라클 힌트 :
-- + 문법 : /*+ Oracle_Hint */
-- + ex : /*+ index_desc(테이블 인덱스명) */
-- + ex : /*+ index_desc(테이블) */
-- + ex : /*+ index_desc(tbl_board SYS_C0035593) */
-- + ex : /*+ index_asc(테이블 인덱스명) */
-- + ex : /*+ index_asc(테이블) */
-- + ex : /*+ index(테이블 인덱스명) */
-- + ex : /*+ index(테이블) */
-- + ex : /*+ full(테이블) */
-- + full은 인덱스에 접근하지 않고, 테이블에 저장된대로 그대로 출력해 준다.
-- + 인덱스명을 지정해주지 않으면, pk를 기준으로 정렬하게 된다.
-- + 인덱스명은 VS code에서는 확인이 어렵기에, 디벨로퍼에서 확인해야 한다.
-- + 인덱스명을 작성하지 않으면, 오라클이 무작위로 만들어 주기에 만드는 편이 좋다.
-- + 오라클 힌트는 강제하지 못하고 부탁하게 된다.
-- ==============================================
-- SYS_C0035593 인덱스의 저장구조
-- * 인덱스의 컬럼값은 이미 Binary-Tree 알고리즘에 따라, 이미 정렬되어있음
-- ----------------------------------
-- BNO(컬럼) | ROWID (행의 저장주소)
-- ----------------------------------
-- 1 | AAAAAABBBCCCDD
-- ----------------------------------
-- 2 | AAAAAABBBCCCDD
-- ----------------------------------
-- 3 | AAAAAABBBCCCDD
-- ----------------------------------
-- 4 | AAAAAABBBCCCDD
-- ----------------------------------
-- ... | AAAAAABBBCCCDD
-- ----------------------------------
-- 298 | AAAAAABBBCCCDD
-- ----------------------------------
-- 299 | AAAAAABBBCCCDD
-- ----------------------------------
-- 300 | AAAAAABBBCCCDD
-- ----------------------------------
-- ==============================================
SELECT
/*+ index_desc(tbl_board SYS_C0032875) */
*
FROM tbl_board;
-- ORDER BY bno DESC;
[ 2. 게시판 만들기 ] (******)
[ 2 - 1. Domain - DTO / VO ]
[ + DTO ]
더보기
[ + 코드 보기 ]
package org.zerock.myapp.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data // DTO는 @Data, VO는 @Value
public class BoardDTO {
private Integer bno;
private String title;
private String content;
private String writer;
} // end class
[ + VO ]
더보기
[ + 코드 보기 ]
package org.zerock.myapp.domain;
import java.sql.Timestamp;
import lombok.Value;
@Value
public class BoardVO {
private Integer bno;
private String title;
private String content;
private String writer;
private Timestamp insertTs;
private Timestamp updateTs;
} // end class
[ 2 - 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.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;
// ====================================================================
// + 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
[ + 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>
</mapper>
[ 2 - 3. 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.exception.ServiceException;
// + 롬복은 인터페이스에서 사용이 불가능하다.
// + 비지니스 로직은 Service에서 구현되어야 한다.
public interface BoardService {
// + 1. 게시파 전체 목록을 조회하여 리스트로 반환해주는 메소드
public abstract List <BoardVO> getAllList() 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
[ 2 - 4. 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.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 ( 전처리 작업)
// ===========================================================
} // end class
[ 2 - 5. 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.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
// ===============================================
// + 선처리
// ===============================================
@Override
public void afterPropertiesSet() throws Exception {
log.trace("afterPropertiesSet() invoked.");
// 필드에 의존성이 잘 주입 되었는지 확인한다.
Objects.requireNonNull(this.boardService);
log.info("\t + this.boardService : {}", this.boardService);
} // afterPropertiesSet
} // end class
[ 2 - 6. 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: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/list.jsp</h1>
<hr>
<p> </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}">${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>
[ + 상세 페이지 ( 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() {
$('#modifyBtn').on('click', () => {
self.location = "/board/modify?bno=${__BOARD__.bno}";
});
$('#listBtn').on('click', () => {
self.location = "/board/list";
});
});
</script>
</head>
<body>
<h1>/WEB-INF/views/board/get.jsp</h1>
<hr>
<div id="wrapper">
<form action="#">
<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=""> </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";
});
});
</script>
</head>
<body>
<h1>/WEB-INF/views/board/modify.jsp</h1>
<hr>
<div id="wrapper">
<form action="/board/update" method="post">
<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=""> </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";
});
});
</script>
</head>
<body>
<h1>/WEB-INF/views/board/register.jsp</h1>
<hr>
<div id="wrapper">
<form action="/board/register2" method="post">
<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=""> </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>
[ 3. 페이징 처리 - OFFSET / FETCH ] (******)
[ 3 - 1. 페이징 처리를 위한 2가지 기준 정보를 가지는 domain 생성 ]
더보기
[ + 코드 보기 ]
package org.zerock.myapp.domain;
import lombok.Data;
@Data
//+ 페이징 처리를 위한 2가지 기준 정보를 가지는 클래스
public class Criteria {
// + 시작하는 페이지
private int currPage = 1;
// + 한 페이지에 보여지는 양
private int amount= 10;
} // end class
[ 3 - 2. mapper에서 SQL문 작성 ]
더보기
[ + 코드 보기 ]
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
[ + BoardMapper.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="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>
[ 3 - 3. Test하기 ]
더보기
[ + 코드 보기 ]
// =======================================================================
// + 페이징 처리 테스트
// =======================================================================
// @Disabled
@Test
@Order(7)
@DisplayName("7. BoardMapper.selectListWithPaging() test")
@Timeout(value=5, unit = TimeUnit.SECONDS)
void selectListWithPaging() throws DAOException {
log.trace("selectListWithPaging() invoked.");
Criteria cri = new Criteria();
List<BoardVO> list = this.mapper.selectListWithPaging(cri);
// + SELECT /*+ INDEX_DESC(tbl_board) */ * FROM tbl_board OFFSET (1 - 1) * 10 ROWS FETCH NEXT 10 ROWS ONLY
// + 위의 sql문 실행된다.
Objects.requireNonNull(list);
list.forEach(log::info);
} // selectListWithPaging
728x90
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 121일차 - Spring ( Restful 방식 ) (****) (0) | 2022.08.19 |
---|---|
KH 120일차 - Spring ( 페이징 처리 ) (******) (0) | 2022.08.18 |
KH 118일차 - Spring ( MockMVC ) (*****) (0) | 2022.08.16 |
KH 117일차 - Spring ( 게시판 만들기 - 비지니스 / 프레젠테이션 계층 ) (*****) (0) | 2022.08.12 |
KH 116일차 - Spring ( 게시판 만들기 - 영속성 / 비지니스 계층 ) (****) (0) | 2022.08.11 |
댓글