pom.xml
프로젝트 진행 순서
Table 생성
freeboard
CREATE TABLE freeboard(
bno NUMBER(10, 0),
title VARCHAR2(300) NOT NULL,
writer VARCHAR2(50) NOT NULL,
content VARCHAR2(2000) NOT NULL,
regdate DATE DEFAULT sysdate,
updatedate DATE DEFAULT NULL
);
ALTER TABLE freeboard
ADD CONSTRAINT freeboard_pk PRIMARY KEY(bno);
CREATE SEQUENCE freeboard_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 5000
NOCYCLE
NOCACHE;
VO 객체 생성
VO 객체
package com.spring.myweb.command;
import java.sql.Timestamp;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class FreeBoardVo {
private int bno;
private String title;
private String writer;
private String content;
private Timestamp regDate;
private Timestamp updateDate;
private boolean newMark;
}
Page
page에 대한 정보들과 검색 정보들을 담은 객체 생성
package com.spring.myweb.util;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class PageVO {
private int pageNum;
private int cpp;
private String keyword;
private String condition;
}
PageCreater
paging 알고리즘을 수행하기 위한 클래스
package com.spring.myweb.util;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class PageCreator {
private PageVO paging;
private int articleTotalCount;
// PageVO에서 받아오는 값들이며 이것들을 통해 연산 수행
private int endPage;
private int beginPage;
private boolean prev;
private boolean next;
private final int buttonNum = 5;
private void calcDataOfPage() {
this.endPage = (int) Math.ceil(paging.getPageNum() / (double) this.buttonNum) * this.buttonNum;
this.beginPage = (this.endPage - this.buttonNum) + 1;
this.prev = (this.beginPage == 1) ? false : true;
this.next = this.articleTotalCount <= (this.endPage * this.paging.getCpp()) ? false : true;
if (!this.next) {
this.endPage = (int) Math.ceil(this.articleTotalCount / (double) this.paging.getCpp());
}
}
//컨트롤러가 총 게시물의 개수를 PageCreator에게 전달한 직후에
//바로 페이징 버튼 알고리즘이 돌아갈 수 있도록 setter를 커스텀.
public void setArticleTotalCount(int articleTotalCount) {
this.articleTotalCount = articleTotalCount;
calcDataOfPage();
}
// URI 파라미터를 쉽게 만들어주는 유틸 메서드
public String makeURI(int page) {
UriComponents ucp = UriComponentsBuilder.newInstance().queryParam("page", page)
.queryParam("cpp", paging.getCpp())
.queryParam("keyword", paging.getKeyword())
.queryParam("condition", paging.getCondition())
.build();
// uri를 컨트롤러에서 받은 searchVO객체인 paging에서 받아서 구성
return ucp.toUriString();
}
}
Interface Mapper
Interface Mapper 생성
package com.spring.myweb.freeboard.mapper;
import java.util.List;
import com.spring.myweb.command.FreeBoardVo;
import com.spring.myweb.util.PageVO;
public interface IFreeBoardMapper {
// 글 등록
void regist(FreeBoardVo board);
// 글 목록
List<FreeBoardVo> getList(PageVO page);
// 사용자가 선택한 페이지 정보와 검색 정보를 알 수 있는 PageVO 객체 매개변수 받음
// 총 게시물 수
int getTotal(PageVO page);
// 검색에 따른 페이지 수 등이 다르므로 해당 정보를 가지고 있는 객체 매개변수로 받음
// 상세보기
FreeBoardVo getContent(int bno);
// 수정
void update(FreeBoardVo board);
// 삭제
void delete(int bno);
}
Mapper
resultMap을 통해 VO객체의 변수 이름과 DB의 컬럼 이름이 다른 것을 Mapping
typeAlias 태그를 통해 type의 이름이 길어지는 문제를 해결
<?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="com.spring.myweb.freeboard.mapper.IFreeBoardMapper">
<!-- namespace는 어떤 interface를 참조하는지 지정 -->
<resultMap type="FreeBoardVO" id="boardMap">
<!-- mybatis-config에서 별칭을 지정했으므로 type에 별칭 지정 -->
<!-- 해당 위치에 있는 객체와 db컬럼을 비교한다는 뜻 -->
<result property="regDate" column="regdate"/>
<result property="updateDate" column="updatedate"/>
<!-- VO객체의 변수명과 db이름이 다른것을 맞춰줌 -->
</resultMap>
<!-- 동적 sql -->
<sql id="search">
<if test="condition == 'title'">
WHERE title LIKE '%'||#{keyword}||'%'
</if>
<if test="condition == 'content'">
WHERE content LIKE '%'||#{keyword}||'%'
</if>
<if test="condition == 'writer'">
WHERE writer LIKE '%'||#{keyword}||'%'
</if>
<if test="condition == 'titleContent'">
WHERE title LIKE '%'||#{keyword}||'%'
OR content LIKE '%'||#{keyword}||'%'
</if>
</sql>
<insert id="regist" >
INSERT INTO freeboard(bno, title, writer, content)
VALUES(freeboard_seq.NEXTVAL, #{title}, #{writer}, #{content})
<!-- 변수명만 제대로 작성하면 insert 끝 -->
</insert>
<select id="getList" resultMap="boardMap" >
<!-- interface의 getList라는 메서드의 sql문이며 참고할 것은 baordMap을 통해 참고 -->
<!-- resultType="com.spring.myweb.command.FreeBoardVO" -->
SELECT * FROM
(SELECT ROWNUM as rn, tbl.* FROM
(SELECT * FROM freeboard
<include refid="search" />
ORDER BY bno DESC) tbl)
<![CDATA[
WHERE rn > (#{pageNum} -1 ) * #{cpp}
AND rn <= #{pageNum} * #{cpp}
]]>
</select>
<select id="getTotal" resultType="int">
SELECT count(*) FROM freeboard
<!-- 동적 sql paging -->
<include refid="search"/>
</select>
<select id="getContent" resultType="FreeBoardVO">
<!-- mybatis-config에서 별칭을 지정했으므로 type에 별칭 지정 -->
SELECT * FROM freeboard
WHERE bno = #{bno}
</select>
<update id="update">
UPDATE freeboard SET
writer = #{writer}, title = #{title}, content = #{content}, updatedate = sysdate
WHERE bno = #{bno}
</update>
<delete id="delete">
DELETE FROM freeboard
WHERE bno = #{bno}
</delete>
</mapper>
Servlet-config
servlet-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 자동으로 컨트롤러와 매핑 메서드 탐색 (handler mapping과 handler adapter bean 등록)-->
<annotation-driven />
<!-- css, img, js ... 의 파일 경로가 복잡할 때 많이 사용 -->
<resources mapping="/resources/**" location="/resources/" />
<resources mapping="/img/**" location="/resources/img/" />
<resources mapping="/css/**" location="/resources/css/" />
<resources mapping="/fonts/**" location="/resources/fonts/" />
<resources mapping="/js/**" location="/resources/js/" />
<!-- 컨트롤러가 리턴한 문자열 앞, 뒤에 적절한 경로를 붙여서 화면을 응답할 수 있도록 도와주는 viewResolver -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- annotation으로 등록된 클래스 객체들을 탐색해 주는 설정 태그이며
base-package에다가 탐색한 패키지 경로를 작성하면 하위 패키지까지 모두 검색 -->
<!-- 자동 bean 등록 -->
<context:component-scan base-package="com.spring.myweb" />
</beans:beans>
DB-config
Connection Pool은 자주 바뀌지 않으므로 따로 파일을 만들어서 사용
.properties라는 파일을 사용해서 생성한 파일에 DB 연결에 관련된 정보들을 입력
외부에 DB와 관련된 정보를 노출시키지 않을 수 있는 장점이 존재
정보의 변경이 발생했을 때 해당 파일의 정보만 변경하면 되기 때문에 유지보수 편리
## local oracle
ds.driverClassName = oracle.jdbc.driver.OracleDriver
ds.url = jdbc:oracle:thin:@localhost:1521:xe
ds.username = user아이디
ds.password = user비밀번호
## local mysql (if ~)
mydb.driverClassName = com.mysql.cj.jdbc.driver
mydb.url = jdbc:mysql://localhost:3306/spring(db name)
mydb.username = user아이디
mydb.password = user비밀번호
root-config.xml (DB연결 사항)
namespace에 jdbc, mybatis-spring 선택
mybatis 동작을 위한 bean 등록
mapper의 type이 길어지는 문제를 alias를 지정하여 간편하게 작성 가능
위에서 작성한 properties파일을 등록해야 동작 가능
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 프로젝트를 구성하다 보면 자주 변경되지 않는 설정 파일들이나 공통 정보들에 대한
내용이 존재하게 되고 그 내용은 한 번 지정되면 잘 바뀌지 않음
이러한 경우에는 .properties라는 파일을 사용하여 텍스트 형식으로 간단히 지정하고
필요할 때 불러와서 사용하는 방식 사용 -->
<!-- 외부에 따로 설정한 설정 파일을 참조하는 곳에서 찾아서 빈으로 등록하여 사용하는 클래스 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/db-config/Hikari.properties" />
<!-- 경로 지정 -->
</bean>
<!-- JDBC, DB 관련 빈을 등록하고 관리하는 설정 파일 -->
<!-- 히카리 커넥션 풀 빈 등록 -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<!-- property는 객체의 setter처럼 값 주입 -->
<property name="driverClassName"
value="${ds.driverClassName}" />
<property name="jdbcUrl"
value="${ds.url}" />
<property name="username" value="${ds.username}" />
<property name="password" value="${ds.password}" />
<!-- 가져온 파일 안에 적혀있는 내용르 작성하면 됨 (db종류에 따라)
mysql도 가능 -->
</bean>
<!-- 히카리 데이터 소스 빈 등록 -->
<bean id="ds" class="com.zaxxer.hikari.HikariDataSource">
<constructor-arg ref="hikariConfig" />
<!-- 히카리 데이터 소스에 히카리 커넥션 풀 주입 -->
</bean>
<!-- mybatis SQL 동작을 위한 핵심 객체 SqlSessionFactory 클래스 빈 등록 -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds" />
<property name="mapperLocations" value="classpath:/mappers/*Mapper.xml"/>
</bean>
<!-- 지정한 패키지를 스캔하여 존재하는 Mapper 인터페이스를 bean type으로 등록 -->
<!-- sqlSessionFactory가 xml파일을 클래스로 변환하여 bean으로 등록하려는 시도를 할 때 타입을 지정해야 하기
때문 -->
<mybatis-spring:scan
base-package="com.spring.myweb.freeboard.mapper" />
<!-- Mapper인터페이스가 어디에 있는지 경로 지정 -->
</beans>
Controller
사용자의 요청을 처리
package com.spring.myweb.controller;
import java.util.List;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.spring.myweb.command.FreeBoardVO;
import com.spring.myweb.freeboard.service.IFreeBoardService;
import com.spring.myweb.util.PageCreator;
import com.spring.myweb.util.PageVO;
@Controller
// controller bean 등록
@RequestMapping("/freeBoard")
public class FreeBoardController {
@Autowired
private IFreeBoardService service;
// 글 작성
@GetMapping("/freeRegist")
public String freeRegi() {
System.out.println("/freeBoard/freeRegist : GET");
System.out.println("작성 페이지로 이동");
return "/freeBoard/freeRegist";
}
@PostMapping("/freeRegist")
public String freeRegi(FreeBoardVO board, RedirectAttributes ra) {
System.out.println("/freeBoard/freeRegist : POST");
System.out.println("게시물 값 가져오는지 확인" + board.toString());
service.regist(board);
ra.addFlashAttribute("msg", "정상 등록 처리되었습니다.");
return "redirect:/freeBoard/freeList";
}
// 목록
@GetMapping("/freeList")
public String freeList(PageVO page, Model model) {
System.out.println("/freeboard/freeList : GET");
System.out.println("page : " + page.toString());
// 페이지 들고 오는지 확인
List<FreeBoardVO> allList = service.getList(page);
model.addAttribute("boardList", allList);
// 모델에 데이터를 담아서 보내줌
PageCreator pcv = new PageCreator();
pcv.setPaging(page);
pcv.setArticleTotalCount(service.getTotal(page));
System.out.println("pcv 객체 확인 : " + pcv.toString());
model.addAttribute("pcv", pcv);
// 모델에 데이터를 담아서 보내줌
return "/freeBoard/freeList";
}
// 상세보기
@GetMapping("/freeDetail/{bno}")
public String freeContent(@PathVariable int bno, Model model, PageVO page) {
// 파리미터 이름과 변수 이름이 같으면 ()안적고 넣을 수 있음
// 파라미터 명이 bno라는 것을 받아와서 상세보기 구현
System.out.println("/freeBoard/freeDetail : GET");
System.out.println("게시물 상세보기 , 게시물 번호 가져오는지 확인 : " + bno);
System.out.println("페이지 정보 가지고 오는지 확인 : " + page.toString());
model.addAttribute("p", page);
FreeBoardVO board = service.getContent(bno);
System.out.println("게시물 DB에서 가져오는지 확인 : " + board.getBno());
model.addAttribute("board", board);
return "/freeBoard/freeDetail";
}
// 게시물 수정
@GetMapping("/freeModify")
public String freeMoveModi(@RequestParam("bno") int bno, Model model) {
System.out.println("/freeBoard/freeModify : GET");
System.out.println("게시물 수정 , 게시물 번호 가져오는지 확인 : " + bno);
FreeBoardVO board = service.getContent(bno);
model.addAttribute("board", board);
return "/freeBoard/freeModify";
}
// 게시물 수정
@PostMapping("/freeModify")
public String freeModi(FreeBoardVO upBoard, RedirectAttributes ra) {
System.out.println("/freeBoard/freeModify : POST");
System.out.println("수정하고 싶은 값 받오는지 확인 : " + upBoard.toString());
service.update(upBoard);
ra.addFlashAttribute("msg", "수정완료");
return "redirect:/freeBoard/freeDetail/"+upBoard.getBno()+"/";
}
// 게시물 삭제
@PostMapping("/freeDelete")
public String freeDelete(@RequestParam("bno") int bno, RedirectAttributes ra) {
System.out.println("/freeBoard/freeDelete : POST");
System.out.println("게시물 번호 가져오는지 확인 : " + bno);
service.delete(bno);
ra.addFlashAttribute("msg", "게시글이 정상적으로 삭제되었습니다.");
return "redirect:/freeBoard/freeList";
// 다시 목록으로 돌아가게 해줌
}
}