답변형(계층형) 게시판
- 먼저 기존 데이터 삭제 후 칼럼 추가
- b_ref : 서로 관련있는 글임을 나타내기 위한 글임을 설정
- 만약 답글이 아닌 새 글이면 글 번호와 동일하게 설정한다.
- 답글이면 부모를 글 번호가 값이 된다.
- b_level : 답글의 계층(깊이)를 나타내기 위한 칼럼
- 부모가 0이면1, 부모가 1이면 2가 된다.
- 새 글이면 0이다.
- b_step : 서로 관련있는 글일 때 글을 출력하기 위한 순서를 위한 칼럼
- 부모가 같을 때 , 이미 달려있는 댓글을 1씩 증가시키고(새 답글이 맨 위로 가야하므로) 자신이 위에 달린다.
- 새 글이면 자신이 최상위 부모가 되므로 0이다.
- 답글은 물론 1부터 항상 시작된다.
- vo 에 설정된 컬럼 3개 추가
package com.example.demo.vo;
import java.util.Date;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
@Data
public class BoardVO {
private int no;
private String title;
private String writer;
private String pwd;
private String content;
private Date regdate;
private int hit;
private String fname;
private int fsize;
private String ip;
private MultipartFile uploadFile;
private int b_ref;
private int b_level;
private int b_step;
}
- BoardMapper.xml 과 DB Manager dao 등에 게시물 번호를 변경하는 코드를 추가한다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board">
<select id="nextNo" resultType="java.lang.Integer">
select nvl(max(no), 0)+1 from borad
</select>
<select id="findAll" resultType="boardVO">
select * from board
</select>
<insert id="insert" parameterType="boardVO">
insert into board values(#{no},#{title},#{writer},#{pwd},#{content},sysdate,0,#{fname},#{fsize},#{ip}, #{b_ref}, #{b_level}, #{b_step})
</insert>
<select id="findByNo" resultType="boardVO">
select * from board where no=#{no}
</select>
<update id="update" parameterType="boardVO">
update board set title=#{title},content=#{content},fname=#{fname},fsize=#{fsize} where no=#{no} and pwd=#{pwd}
</update>
<delete id="delete">
delete board where no=#{no} and pwd=#{pwd}
</delete>
</mapper>
package com.example.demo.db;
import java.awt.print.Book;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.example.demo.vo.BoardVO;
//DBManager 클래스를 이용하여 기존 DAO 가 하던 작업을 대부분 여기서 진행한다.
public class DBManager {
//SqlSessionFactory 객체는 mybatis 프레임워크에서 사용한다.
//SqlSession 을 생성하는데 사용된다.
public static SqlSessionFactory factory = null;
//static 블록 안에서 아래와 같은 작업을 하면서, 애플리케이션 시작 시 한번만 초기화된다.
static {
//리소스 파일을 읽는 과정에서 예외가 발생할 수 있으므로 예외처리한다.
try {
//dbConfig 파일의 위치를 문자열로 저장
String resource = "com/example/demo/db/dbConfig.xml";
//Resources.getResourceAsStream(resource)는 Mybatis에서 리소스 파일을 스트림으로 읽어오는 유틸리티 메서드인데,
//설정파일 또는 매퍼 xml 파일을 읽을 떄 사용된다.
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSessionFactoryBuilder 클래스는 SqlSessionFactory 객체를 생성하는 빌더 클래스이다.
//설정 파일 즉, dbConfig 파일을 읽고 SqlSessionFactory 객체를 구성한다.
factory =
new SqlSessionFactoryBuilder().build(inputStream);
//inputStream 을 인수로 받아 설정파일을 읽고, 이 설정파일을 기반으로 SqlSessionFactory 객체를 생성한다.
}catch (Exception e) {
System.out.println("예외발생:"+e.getMessage());
}
}
public static List<BoardVO> findAll(){
List<BoardVO> list = null;
//Sqlsession 을 생성한다.
//SQql 세션은 트랜잭션을 관리하며, DB 상호 작용을 캡슐화하며, SQL 쿼리를 실행하고 매핑할 수 있다.
SqlSession session = factory.openSession();
//Mybatis Mapper 파일에서 board.finedAll 을 실행시킨다.
list = session.selectList("board.findAll");
//SqlSession 자원을 닫는다.
//SqlSession 은 DB연결, 캐시, 커서 등 다양한 리소스를 사용하는데, 이 리소스는 제한적이라서 반드시 해제되어야 한다.
session.close();
//BoardVO 리스트들을 리턴한다.
return list;
}
public static int insert(BoardVO b) {
//re 를 -1 초깃값으로 셋팅한다.
int re = -1;
//openSession 메소드에 true를 매개변수로 줄 경우 AutoCommit 이 true가 된다.
SqlSession session = factory.openSession(true);
re = session.insert("board.insert", b);
// session.commit();
session.close();
return re;
}
public static BoardVO findByNo(int no) {
BoardVO b = null;
SqlSession session = factory.openSession();
b = session.selectOne("board.findByNo", no);
session.close();
return b;
}
public static int update(BoardVO b) {
int re = -1;
//AutoCommit 을 true 하겠다는 의미로 true 를 매개변수로 준다.
SqlSession session = factory.openSession(true);
re = session.update("board.update", b);
session.close();
return re;
}
public static int delete(int no, String pwd) {
int re = -1;
HashMap<String , Object> map = new HashMap<>();
map.put("no", no);
map.put("pwd", pwd);
SqlSession session = factory.openSession(true);
re = session.delete("board.delete", map);
session.close();
return re;
}
public static int getNextNo() {
int no = 0;
SqlSession session = factory.openSession(true);
no = session.selectOne("board.nextNo");
session.close();
return no;
}
}
package com.example.demo.dao;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.example.demo.db.DBManager;
import com.example.demo.vo.BoardVO;
@Repository
public class BoardDAO {
public List<BoardVO> findAll(){
return DBManager.findAll();
}
public int insert(BoardVO b) {
return DBManager.insert(b);
}
public BoardVO findByNo(int no) {
return DBManager.findByNo(no);
}
public int update(BoardVO b) {
return DBManager.update(b);
}
public int delete(int no, String pwd) {
return DBManager.delete(no, pwd);
}
public int getNextNo() {
return DBManager.getNextNo();
}
}
- 새 글의 처리 - controller 에 insert 전에 다음 코드 추가
- b_ref : 글 번호 no 와 동일하게 처리
- b_level : 0
- b_step : 0
//새 글에 대한 처리
int no = dao.getNextNo();
int b_ref = no;
int b_level = 0;
int b_step = 0;
- 답글의 처리
- b_ref : 부모글의 b_ref 와 동일하게 처리
- b_level : 부모 글의 b_level +1
- b_step : 이미 달려있는 답글들의 b_step 을 1씩 증가시킨 후에 b_step+1 로 처리
- detail.jsp 에 답글 작성 버튼 추가
<a href="insertBoard?no=${g.no}" class="btn btn-light">답글 작성</a>
- 만약 2번글에 대하여 답글을 작성한다면 no/b_ref/b_level/b_step 이면, 4/2/1/1이 된다.
- 만약 1번글에 대하여 답글을 작성한다면 no/b_ref/b_level/b_step 이면, 5/1/1/1이 된다.
- 2번 글에 답글을 달면 변동이 있는 레코드는 다음과 같다.
- 추가되는 레코드 no/b_ref/b_level/b_step 이면, 6/2/1/1이 된다.
- 부모 레코드는 no/b_ref/b_level/b_step 이면, 2/2/1/0이 된다.
- 2 2 0 0
- 6 2 1 1
- 7 6 2 2
- 4 2 1 2
- 6 2 1 1
- 3 3 0 0
- 1 1 0 0
- 5 1 1 1
<!-- Mapper 에 추가-->
<update id="updateStep">
update board set b_step = b_step+1 where b_ref = #{b_ref} and b_step > #{b_step}
</update>
public void updateStep(HashMap map) {
DBManager.updateStep(map);
return;
}
메일 발송
- 구글 보안 정책으로 인한 앱 비밀번호 사용 설정 하기
- https://support.google.com/mail/answer/185833?hl=ko&sjid=8999540905604934767-AP#
인증 메일 입력 및 중복 체크까지 오늘의 최종 코드
디렉터리 구조
베이스 패키지(com.example.demo)
- SpringConfig.java
package com.example.demo;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSenderImpl;
//환경설정 파일임을 어노테이션으로 명시
//이 클래스 안에 정의된 메서드 들이 Spring Bean으로 등록된다.
@Configuration
public class SpringConfig {
//@Bean 어노테이션은 해당 메서드가 반환하는 객체가 Spring 컨테이너에 의해 관리되는 Bean이 됨을 의미
@Bean
public JavaMailSenderImpl javaMailSender() {
//JavaMailSenderImpl 객체 생성한다. 이 객체는 이메일을 보낼 수 있는 기능을 제공한다.
JavaMailSenderImpl jms = new JavaMailSenderImpl();
//메일 서버의 호스트 이름을 설정합니다. 여기서는 Gmail의 SMTP 서버(smtp.gmail.com)를 사용한다.
jms.setHost("smtp.gmail.com");
//Gmail 인 경우 Port번호는 587이다.
//메일 서버의 포트 번호를 설정합니다
jms.setPort(587);
//이메일을 발송할 때 사용할 Gmail 계정의 사용자명을 설정한다.
jms.setUsername("greylife5451");
//이메일 발송 시 사용할 Gmail 계정의 비밀번호를 설정한다. 비밀번호는 안전하게 관리되어야 하며, 코드에 직접 노출하지 않는 것이 좋다.
//Google의 강화된 보안 정책으로 2단계 인증을 먼저 하고, 앱 비밀번호 사용 관리에서 비밀번호를 발급받아 사용한다.
jms.setPassword("hvqg vgty pbcs tljl");
//이메일의 인코딩을 지정한다.
jms.setDefaultEncoding("UTF-8");
//키와 값으로 이루어진 프로퍼티 객체를 생성한다.
Properties prop = new Properties();
//TLS(Transport Layer Security) 활성화
//TLS를 활성화하여 보안 연결을 사용하도록 설정합니다. TLS는 이메일 전송 시 보안을 강화합니다.
prop.put("mail.smtp.starttls.enable", true);
//SMTP 인증 사용 설정
//SMTP 서버 인증을 사용하도록 설정합니다. 이메일을 보내기 전에 인증이 필요합니다.
prop.put("mail.smtp.auth", true);
//서버 인증 확인 설정
//서버 인증을 확인하도록 설정합니다. 이는 서버의 SSL 인증서를 확인하는데 사용됩니다.
prop.put("mail.smtp.ssl.checkserveridentity", true);
//SSL 신뢰 설정
//모든 서버의 SSL 인증서를 신뢰하도록 설정합니다. 보안상 위험할 수 있으므로 필요에 따라 수정이 필요합니다.
prop.put("mail.smtp.ssl.trust", "*");
//SSL 프로토콜 설정
//SSL 프로토콜을 TLSv1.2로 설정합니다. TLSv1.2는 보안이 강화된 버전입니다.
prop.put("mail.smtp.ssl.protocols", "TLSv1.2");
//Properties 객체 설정
//위에서 설정한 Properties 객체를 JavaMailSenderImpl 객체에 적용합니다.
jms.setJavaMailProperties(prop);
//JavaMailSenderImpl 객체 반환
//설정된 JavaMailSenderImpl 객체를 반환하여 Spring 컨테이너에 Bean으로 등록합니다.
//이 클래스는 주로 Spring 애플리케이션에서 이메일을 발송할 수 있도록 설정하는 데 사용됩니다.
return jms;
}
}
Controller
- BoardController.java
package com.example.demo.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
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.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.example.demo.dao.BoardDAO;
import com.example.demo.vo.BoardVO;
import jakarta.servlet.http.HttpServletRequest;
//info : 어노테이션 Controller를 설정한다.(URL을 매핑하여 뷰페이지를 반환한다)
//info : Controller역할을 부여하는데, Controller 는 요청을 받아 업무 로직을 호출하고, 적절한 View 를 호출하기 전까지 전체 처리 흐름을 제어한다.
@Controller
public class BoardController {
//info : 어노테이션 Autowired 를 붙여서 인젝션을 지시한다.
//info : 인젝션 지시 (주입 지시)
@Autowired
private BoardDAO dao;
//info : Get 방식으로 RequestMapping 한다. 이것은 아래와 같다.
//info : 기본 페이지 index 페이지를 Mapping 한다. 이것은 아래와 같다.
@GetMapping("/")
public String index() {
return "index";
}
//info : Get 방식으로 Request Mapping 한다. 이것은 아래와 같다.
@GetMapping("/listBoard")
public void list(Model model) {
//listBoard.jsp 화면에 list 를 상태 유지한다.
model.addAttribute("list", dao.findAll());
}
// @RequestMapping(value = "/insertBoard", method = RequestMethod.GET)
// public void insertForm() {
//
// }
//
// @RequestMapping(value = "/insertBoard", method = RequestMethod.POST)
// public ModelAndView insertSubmit() {
//
// }
//info : 이렇게 되면 Get 방식을 Mapping 한다.
//info : 게시물 작성 폼을 사용자에게 보여주기 위한 용도로 사용된다.
//info : 뷰를 만환하지 않지만 Spring 에서는 맵핑된 jsp 뷰 페이지가 있으면 자동으로 뷰를 찾을 수 있다.
//info : @RequestParam은 no의 값이 지정되지 않았을 때, 즉 null 일 때 defaulteValue의 기본값은 0으로 지정하는 것이다.
//info : detailBoard 페이지에서 답글 수정을 누르면 게시물의 no 값이 전달이 되므로 0이 아닌 부모의 no 값이 들어오게 된다.
//info : 반면, 새 글 입력은 listBoard.jsp 화면에서 게시물 등록 버튼을 눌렀을 때로, 전달하는 값이 없으므로 null이 된다.
//info : 따라서 답글 등록이 아닌 새 글 등록은 아래의 @RequestParam 에 의하여 0으로 셋팅되는 것이다.
@GetMapping("/insertBoard")
public void insertForm(@RequestParam(value="no", defaultValue="0") int no, Model model) {
//no 값을 상태 유지한다.
model.addAttribute("no", no);
//게시물 등록으로 미리 title 지정
String title = "게시물 등록";
// 위의 규칙으로 no 가 0이 아니면 답글 작성인 것을 알 수 있다. 따라서 아래로 title 을 설정해준다.
if(no != 0) {
title = "답글 작성";
}
//title 을 상태 유지한다.
model.addAttribute("title", title);
}
//info : 이렇게 되면 Post 방식을 Mapping 한다.
//info : 사용자가 작성한 게시물 데이터를 서버로 전송했을 때 그 데이터를 처리하는 역할을 맡는다.
@PostMapping("/insertBoard")
public ModelAndView insertSubmit(BoardVO b, HttpServletRequest request) {
//info_top : ModelAndView 를 반환한다. 이 때, VO를 매개변수로 넣고, HttpServletRequest를 매개변수로 만든다. 이처럼 필요에 따라 request, response 등을 매개변수로 가져올 수 있다.
//info : 요청 정보를 담을 request 객체에서 ServletContext를 얻는다.
//info : ServletContext는 웹 애플리케이션 전역정보를 담고있다.
//info : 가상경로를 실제 파일 시스템 경로로 반환한다.(upload 폴더 실제 경로 반환)
String path = request.getServletContext().getRealPath("upload");
//info : path 를 콘솔에 찍어본다.
System.out.println("path:"+path);
//info : fname 및 fsize 등등을 초깃값으로 셋팅한다.
String fname = null;
int fsize = 0;
//info : BoardVO에서 UploadFile 속성값을 얻어온다.
//info : MultipartFile는 Spring에서 파일 업로드를 처리할 때 사용하는 인터페이스이다.
//info : 뷰페이지 폼에서 전송된 파일 데이터를 다룰 때 사용된다.
MultipartFile uploadFile = b.getUploadFile();
//info : getOriginalFilename() 메서드는 클라이언트가 업로드한 파일의 원래 이름을 반환한다.
fname = uploadFile.getOriginalFilename();
byte[]data = null;
//getBytes() 또한 IO Exception 을 품고 있으므로 예외처리를 해준다.
try {
//업로드한 파일의 내용을 Byte 배열로 얻어온다.
data = uploadFile.getBytes();
//Byte 배열의 크기를 재서 fsize 를 얻는다.
fsize = data.length;
}catch (IOException e) {
System.out.println("예외발생:"+e.getMessage());
}
//BoradVO 에 fname 과 fsize의 속성 값을 넣어준다.
b.setFname(fname);
b.setFsize(fsize);
//요청 정보에서 클라이언트(사용자)의 IP 주소를 얻는다.
String ip = request.getRemoteAddr();
//VO 에 ip 속성 값을 넣어준다.
b.setIp(ip);
//no 값을 가져온다.
//detail.jsp 에서 타고 들어온 경우에는 선택한 게시글의 no 값을 가져오게 된다.
//반면, 완전히 새 글을 작성하는 경우에는 초깃값 0이 셋팅된다.
//즉 답글일 경우에는 부모의 no 값을 가져오고, 새글 작성의 경우 0이 셋팅되는 것이다.
int pno = b.getNo();
//새 글에 대한 처리
//no는 현재 글의 글 번호가 된다.
int no = dao.getNextNo();
//b_ref는 no의 값으로 셋팅한다.
int b_ref = no;
//새 글이므로 level 은 0으로 셋팅한다.
int b_level = 0;
//새 글이므로 step 도 0으로 셋팅한다.
int b_step = 0;
//답글에 대한 처리 / 답글이 아니면 pno 0 답글이면 부모의 no 를 가지게 됨
if(pno != 0) {
//부모의 no값에 따른 p즉, 부모의 게시글 정보를 찾음
BoardVO p = dao.findByNo(pno);
//부모의 b_ref 값으로 셋팅
b_ref = p.getB_ref();
//부모의 레벨값으로 먼저 셋팅
b_level = p.getB_level();
//부모의 step값으로 먼저 셋팅
b_step = p.getB_step();
//여기서 step은 자기를 제외한 다른 부모의 답글들의 step(순서)를 뒤로 하나씩 밀고 자신이 그 자리에 들어간다.
//따라서 먼저 Map 을 만들어 sql 에서 작업을 해준다.
//b_ref 는 부모가 한데로 엮여있는 다른 답글들을 찾기 위해서
//b_step은 하나씩 차례로 전부 밀기 위해서 값을 넘겨주는 것이다.
HashMap map = new HashMap();
map.put("b_ref", b_ref);
map.put("b_step", b_step);
//dao 에서 updateStep 메소드에 map 을 넘겨준다.
dao.updateStep(map);
//또한, 우선 자기 자신의 b_level(dept)과 b_step(순서)를 부모보다 1 순위로 밀어 넣는다.
//위의 Map 파일로 하나씩 미는 것은 자기 자신을 제외한 다른 답글들의 조정이므로 아래와 같이 1씩 더하는 연산을 하는 것이다.
b_level++;
b_step++;
}
b.setNo(no);
b.setB_ref(b_ref);
b.setB_level(b_level);
b.setB_step(b_step);
//데이터베이스에 값을 삽입한다.
int re = dao.insert(b);
//re는 삽입된 행의 갯수이므로 1일경우 insert를 성공했다는 뜻이다. 즉, 성공하면 and
//기본 값은 null 값 허용이므로 업로드 파일이 없을 수도 있다. 즉, 삽입도 성공하고 업로드 파일이 있다면
//업로드 된 파일의 이름이 비어있다면 업로드 된 파일이 이상한 것이므로 제대로 파일이 올라갔는지 여부를 판단한다.
if(re == 1 && fname != null && !fname.equals("")) {
//FileOutputStream은 FileNotFoundException과 IO Exception 등의 예외를 품고 있으므로 예외 처리를 해준다.
try {
//File 출력 스트림 객체를 생성하고 매개변수로 파일의 경로와 이름을 넣어준다.
//FileNotFoundException 이 발생할 수 있다.
FileOutputStream fos = new FileOutputStream(path + "/"+ fname);
//IO Exception 이 발생할 수 있다.
fos.write(data);
//자원을 닫아준다.
fos.close();
}catch (Exception e) {
System.out.println("예외발생:"+e.getMessage());
}
}
//ModelAndView 를 생성한다. 이 때 생성시에 매개변수로 바로 setName해줄 수 있다.
ModelAndView mav = new ModelAndView("redirect:/listBoard");
//만약 등록에 실패했을 경우
if(re <= 0) {
//오류 메세지를 상태유지한다.
mav.addObject("msg", "게시물 등록에 실패");
//에러페이지로 이동
mav.setViewName("error");
}
//뷰 페이지를 리턴한다.
return mav;
}
//해당 페이지로 Get방식일 때 동작할 메소드
@GetMapping("/detailBoard")
public void detail(Model model, int no) {
//detail 페이지로 VO 를 상태 유지 시킨다.
model.addAttribute("b", dao.findByNo(no));
}
//삭제 페이지 Get방식으로 요청했을 때로, Form 을 보여준다.
@GetMapping("/deleteBoard")
public void deleteForm(Model model, int no) {
//이 때, 게시판 번호를 상태 유지한다.
model.addAttribute("no", no);
}
//jsp에서 작성하여 Post 방식으로 보냈던 no 값과 pwd 값을 가져온다.
@PostMapping("/deleteBoard")
public ModelAndView deleteSubmit(int no, String pwd, HttpServletRequest request) {
//게시물 삭제시키기 전에 파일 이름을 먼저 알아놓는다.
String oldFname = dao.findByNo(no).getFname();
//ServletContext 의 경로를 가져와서 upload폴더의 경로를 path 변수에 담는다.
String path = request.getServletContext().getRealPath("upload");
//성공하면 list 화면으로 이동하도록 기본 설정
ModelAndView mav = new ModelAndView("redirect:/listBoard");
//dao 의 delete 메소드 설정
int re = dao.delete(no, pwd);
if(re <= 0) {
//삭제에 실패하면 에러 메세지를 상태유지한다.
mav.addObject("msg", "게시물 삭제에 실패하였습니다. 비번확인 요망");
//삭제에 실패하면 error 화면으로 이동
mav.setViewName("error");
}else {
//원래 파일이 있는지 확인
if(oldFname != null && !oldFname.equals("")) {
//파일 객체를 생성한다.
File oldFile = new File(path+"/"+oldFname);
//파일 객체의 제공되는 메소드를 이용하여 파일을 삭제한다.
oldFile.delete();
}
}
return mav;
}
}
- MemberController
package com.example.demo.controller;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.dao.MemberDAO;
import com.example.demo.vo.MemberVO;
//이 객체는 Controller 의 역할을 한다고 애너테이션으로 지정한다.
@Controller
public class MemberController {
//IoC 로 자동으로 생성하여 DI 로 자동으로 매칭해준다.
@Autowired
private MemberDAO dao;
//MailSender Spring Framework 에서 이메일 전송을 위한 인터페이스로 이메일을 보내기 위한 기본적인 메서드를 정의하고 있다.
//send() 메서드를 사용하여 이메일 메시지를 전송할 수 있다.
@Autowired
private MailSender mailSender;
//Get방식으로 해당 url이 매핑될 떄 메소드를 정의한다.
@GetMapping("/join")
public void joinForm() {
//단순히 join 페이지로 이동된다.
}
//Post 방식으로 해당 url 이 매핑될 떄 메소드를 정의한다.
//ResponseBody 는 메서드의 반환값을 HTTP 응답 본문에 직접 작성하도록 지정한다.
//즉, 반환된 데이터가 뷰 이름으로 해석되지 않고, 그대로 클라이언트에게 전송된다.
//주로 RESful 웹서비스에서 JSON 또는 XML 형식의 데이터를 반환할 때 사용된다.
//이 에너테이션을 사용하면 컨트롤러 메서드에서 반환하는 데이터가 HTTP 응답으로 직렬화되어 클라이언트에게 전달된다.
@PostMapping("/join")
@ResponseBody
public String JoinSubmit(MemberVO m) {
//dao에서 멤버의 값을 삽입한다.
int re = dao.join(m);
//삽입 실패시 에러 발생 출력문을 출력한다.
if(re < 0) {
System.out.println("멤버 추가 에러 발생:");
}
//"OK"를 응답하는데, 위의 ResponseBody 애너테이션으로 인하여 View 페이지 이동이 아니라 해당 문자열이 클라이언트에게 그대로 전달된다.
return "OK";
}
//Get방식으로 /sendCode 가 매핑될 떄 해당 메소드가 실행된다.
//이 떄, ResponseBody 로 data가 그대로 직렬화되어 클라이언트에게 전송된다.
@GetMapping("/sendCode")
@ResponseBody
public String sendCode(String email) {
String data="";
//SimpleMailMessage 객체는 Spring Framework 에서 이메일을 간단하게 작성하고 전송할 수 있도록 도와주는 클래스
//텍스트 기반 이메일 메시지를 구성하기 위해 사용되며, 첨부 파일이나 HTML 콘텐츠와 같은 복잡한 기능은 지원하지 않는다
SimpleMailMessage mailMessage = new SimpleMailMessage();
//랜덤 객체 생성
Random r = new Random();
//랜덤 객체를 이용하여 0~9까지의 4자리 수를 data 에 담는다.
data = r.nextInt(10)+""+r.nextInt(10)+""+r.nextInt(10)+""+r.nextInt(10);
//이메일 제목 설정
mailMessage.setSubject("인증코드 전송");
//발신자의 이메일 주소 설정
mailMessage.setFrom("greylife5451@gmail.com");
//수신자의 이메일 주소 설정
//여기서는 jsp 에서 넘어온 email 이 매개변수로 자동으로 받아져서 셋팅된다.
mailMessage.setTo(email);
//data를 이메일 본문 내용으로 설정한다.
mailMessage.setText(data);
try {
//위에서 생성했던 MailSender 객체를 통하여 위에서 셋팅했던 mailMessage를 전송한다.
mailSender.send(mailMessage);
} catch (Exception e) {
//예외 출력
System.out.println(e);
}
//추후에 data 와 사용자가 입력한 값과 비교해야 하므로 data 값을 리턴한다.
return data;
}
//idChk를 Get 방식으로 Mapping 할 떄 다음의 메소드가 실행된다.
@GetMapping("/idChk")
@ResponseBody
public boolean idChk(String userid) {
//dao의 findById를 통하여 vo 를 찾는다.
MemberVO vo = dao.findById(userid);
//이때 vo 가 null 이면 해당 id를 가진 회원이 없다는 것을 뜻한다.
if(vo == null) {
//사용할 수 있다는 의미로 true를 반환한다.
return true;
}
//null 값이 아니면 해당 회원이 있다는 뜻이므로 사용할 수 없다는 의미로 false 를 반환한다.
return false;
}
}
- UpdateBoardController
package com.example.demo.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.example.demo.dao.BoardDAO;
import com.example.demo.vo.BoardVO;
import jakarta.servlet.http.HttpServletRequest;
//컨트롤러로 지정하되 updateBoard 를 요청하면 다음 클래스로 이동
@Controller
@RequestMapping("/updateBoard")
public class UpdateBoardController {
//dao를 주입한다.
@Autowired
private BoardDAO dao;
//위에서 요청값을 적어줬으므로 여기서는 요청 방식만 적어준다.
//즉, Get 방식일 때 다음의 메소드가 실행된다.
@RequestMapping(method = RequestMethod.GET)
public void form(Model model, int no) {
//VO의 내용을 상태 유지한다.
model.addAttribute("b", dao.findByNo(no));
}
//Post 방식일 떄 다음의 메소드가 실행된다.
@RequestMapping(method = RequestMethod.POST)
public ModelAndView submit(BoardVO b, HttpServletRequest request) {
//upload 디렉토리의 경로를 path 변수에 담는다.
String path = request.getServletContext().getRealPath("upload");
//기본 ModelAndView페이지를 listBoard 페이지로 지정한다.
ModelAndView mav = new ModelAndView("redirect:/listBoard");
//원래의 파일이름과 크기를 미리 담아둔다.
//수정하고 나면 기존 파일을 삭제하기 위하여 저장해두는 것이다.
String oldFname = b.getFname();
int oldFsize = b.getFsize();
//기본 파일의 이름, 사이즈, 내용에 초깃값을 준다.
String fname= null;
int fsize = 0;
byte []data = null;
//업로드한 파일의 정보를 가지고 온다.
MultipartFile uploadFile = b.getUploadFile();
//파일 업로드 하는 것이 없음에도 불구하고 null 이 아니라 어떤 객체를 갖고 있으므로 null 이다라고 판단할 수 없다.
//그래서 업로드 한 파일의 이름을 가지고와서 판단하는 것이다.
fname = uploadFile.getOriginalFilename();
//업로드 한 파일 이름으로 제대로 올라갔는지 먼저 판단
if(fname != null && !fname.equals("")) {
try {
//제대로 올라갔다면 내용을 읽는다.
data = uploadFile.getBytes();
//file 의 크기를 배열의 크기를 재서 바이트 크기를 잰다.
fsize = data.length;
//VO 객체에 파일 이름 속성값을 준다.
b.setFname(fname);
//VO 객체에 파일 사이즈 속성값을 준다.
b.setFsize(fsize);
} catch (IOException e) {
System.out.println(e);
}
}
//db에 수정을 요청한다.
int re = dao.update(b);
//수정에 성공했고, 파일도 수정했다면 파일을 복사해준다.
if(re >= 1 && fname != null && !fname.equals("")) {
try {
//파일 출력하기 위한 스트림을 생성한다.
FileOutputStream fos = new FileOutputStream(path + "/" + fname);
//파일에 데이터를 기록한다.
fos.write(data);
//스트림을 닫아준다.
fos.close();
//원래 파일이 있는지 확인
if(oldFname != null && !oldFname.equals("")) {
//파일 객체를 생성한다.
File oldFile = new File(path+"/"+oldFname);
//파일 객체의 제공되는 메소드를 이용하여 파일을 삭제한다.
oldFile.delete();
}
} catch (Exception e) {
System.out.println(e);
}
}
//수정에 실패했다면
if(re <= 0) {
//수정 실패 메세지
mav.addObject("msg", "게시물 수정에 실패");
//에러 페이지로 이동
mav.setViewName("error");
}
//수정작업이 완료되면 게시판 목록 화면으로 돌아간다.
return mav;
}
}
DAO
- BoardDAO
package com.example.demo.dao;
import java.util.HashMap;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.example.demo.db.DBManager;
import com.example.demo.vo.BoardVO;
import com.example.demo.vo.MemberVO;
@Repository
public class BoardDAO {
public void updateStep(HashMap map) {
DBManager.updateStep(map);
return;
}
public List<BoardVO> findAll(){
return DBManager.findAll();
}
public int insert(BoardVO b) {
return DBManager.insert(b);
}
public BoardVO findByNo(int no) {
return DBManager.findByNo(no);
}
public int update(BoardVO b) {
return DBManager.update(b);
}
public int delete(int no, String pwd) {
return DBManager.delete(no, pwd);
}
public int getNextNo() {
return DBManager.getNextNo();
}
}
- MemberDAO
package com.example.demo.dao;
import org.springframework.stereotype.Repository;
import com.example.demo.db.DBManager;
import com.example.demo.vo.MemberVO;
@Repository
public class MemberDAO {
public int join(MemberVO m) {
return DBManager.join(m);
}
public MemberVO findById(String id) {
return DBManager.findById(id);
}
}
DB
- DBManager
package com.example.demo.db;
import java.awt.print.Book;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.example.demo.vo.BoardVO;
import com.example.demo.vo.MemberVO;
//DBManager 클래스를 이용하여 기존 DAO 가 하던 작업을 대부분 여기서 진행한다.
public class DBManager {
//SqlSessionFactory 객체는 mybatis 프레임워크에서 사용한다.
//SqlSession 을 생성하는데 사용된다.
public static SqlSessionFactory factory = null;
//static 블록 안에서 아래와 같은 작업을 하면서, 애플리케이션 시작 시 한번만 초기화된다.
static {
//리소스 파일을 읽는 과정에서 예외가 발생할 수 있으므로 예외처리한다.
try {
//dbConfig 파일의 위치를 문자열로 저장
String resource = "com/example/demo/db/dbConfig.xml";
//Resources.getResourceAsStream(resource)는 Mybatis에서 리소스 파일을 스트림으로 읽어오는 유틸리티 메서드인데,
//설정파일 또는 매퍼 xml 파일을 읽을 떄 사용된다.
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSessionFactoryBuilder 클래스는 SqlSessionFactory 객체를 생성하는 빌더 클래스이다.
//설정 파일 즉, dbConfig 파일을 읽고 SqlSessionFactory 객체를 구성한다.
factory =
new SqlSessionFactoryBuilder().build(inputStream);
//inputStream 을 인수로 받아 설정파일을 읽고, 이 설정파일을 기반으로 SqlSessionFactory 객체를 생성한다.
}catch (Exception e) {
System.out.println("예외발생:"+e.getMessage());
}
}
public static List<BoardVO> findAll(){
List<BoardVO> list = null;
//Sqlsession 을 생성한다.
//SQql 세션은 트랜잭션을 관리하며, DB 상호 작용을 캡슐화하며, SQL 쿼리를 실행하고 매핑할 수 있다.
SqlSession session = factory.openSession();
//Mybatis Mapper 파일에서 board.finedAll 을 실행시킨다.
list = session.selectList("board.findAll");
//SqlSession 자원을 닫는다.
//SqlSession 은 DB연결, 캐시, 커서 등 다양한 리소스를 사용하는데, 이 리소스는 제한적이라서 반드시 해제되어야 한다.
session.close();
//BoardVO 리스트들을 리턴한다.
return list;
}
public static int insert(BoardVO b) {
//re 를 -1 초깃값으로 셋팅한다.
int re = -1;
//openSession 메소드에 true를 매개변수로 줄 경우 AutoCommit 이 true가 된다.
SqlSession session = factory.openSession(true);
re = session.insert("board.insert", b);
// session.commit();
session.close();
return re;
}
public static BoardVO findByNo(int no) {
BoardVO b = null;
SqlSession session = factory.openSession();
b = session.selectOne("board.findByNo", no);
session.close();
return b;
}
public static int update(BoardVO b) {
int re = -1;
//AutoCommit 을 true 하겠다는 의미로 true 를 매개변수로 준다.
SqlSession session = factory.openSession(true);
re = session.update("board.update", b);
session.close();
return re;
}
public static int delete(int no, String pwd) {
int re = -1;
HashMap<String , Object> map = new HashMap<>();
map.put("no", no);
map.put("pwd", pwd);
SqlSession session = factory.openSession(true);
re = session.delete("board.delete", map);
session.close();
return re;
}
public static int getNextNo() {
int no = 0;
SqlSession session = factory.openSession(true);
no = session.selectOne("board.nextNo");
session.close();
return no;
}
public static void updateStep(HashMap map) {
SqlSession session = factory.openSession();
session.update("board.updateStep",map);
session.commit();
session.close();
}
public static int join(MemberVO m) {
int re = -1;
SqlSession session = factory.openSession(true);
re = session.insert("member.insert", m);
session.close();
return re;
}
public static MemberVO findById(String id){
MemberVO vo = null;
SqlSession session = factory.openSession();
vo = session.selectOne("member.findById",id);
session.close();
return vo;
}
}
- BoardMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board">
<!--
update board에서 step 값을 하나씩 올리는데, 조건은 부모의 no가 기존 no인 것과 b_step 이 기존 step 보다 큰 것들을 1씩 올린다.
물론 새 답글도 부모 렙에 맞춰 1씩 올라간다.
#{b_ref}에서 부모가 최상위이면 b_no 값과 같으므로 된다.
#{b_ref}에서 하위 이면 뒤의 b_step 조건으로 판별하는 것이다.
ref와 부모 자신의 dept 와 같은 것들은 b_step의 값이 올라가면 안되므로 b_step > #{b_step} 이 되는것이다.
여기서 ref 란 무조건 최상위이고, step 은 ref 끼리 묶인다. 또한, level이 dept 를 담당하는 것이다.
즉, step 과 dept 는 상관이 없다.
-->
<update id="updateStep">
update board set b_step = b_step+1
where b_ref = #{b_ref} and b_step > #{b_step}
</update>
<select id="nextNo" resultType="java.lang.Integer">
select nvl(max(no), 0)+1 from board
</select>
<select id="findAll" resultType="boardVO">
select * from board order by b_ref desc, b_step
</select>
<insert id="insert" parameterType="boardVO">
insert into board values(#{no},#{title},#{writer},#{pwd},#{content},sysdate,0,#{fname},#{fsize},#{ip}, #{b_ref}, #{b_level}, #{b_step})
</insert>
<select id="findByNo" resultType="boardVO">
select * from board where no=#{no}
</select>
<update id="update" parameterType="boardVO">
update board set title=#{title},content=#{content},fname=#{fname},fsize=#{fsize} where no=#{no} and pwd=#{pwd}
</update>
<delete id="delete">
delete board where no=#{no} and pwd=#{pwd}
</delete>
</mapper>
- MemberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="member">
<insert id="insert" parameterType="memberVO">
insert into member values(#{id}, #{pwd}, #{m_name}, #{email}, #{phone})
</insert>
<select id="findById" resultType="memberVO">
select * from member where id=#{id}
</select>
</mapper>
- db.Config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="com/example/demo/db/db.properties"/>
<typeAliases>
<typeAlias type="com.example.demo.vo.BoardVO" alias="boardVO"/>
<typeAlias type="com.example.demo.vo.MemberVO" alias="memberVO"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/demo/db/BoardMapper.xml"/>
<mapper resource="com/example/demo/db/MemberMapper.xml"/>
</mappers>
</configuration>
- db.properties
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:XE
username=c##madang
password=madang
- application.properties
spring.application.name=boardtests
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
VO
- BoardVO
package com.example.demo.vo;
import java.util.Date;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
@Data
public class BoardVO {
private int no;
private String title;
private String writer;
private String pwd;
private String content;
private Date regdate;
private int hit;
private String fname;
private int fsize;
private String ip;
private MultipartFile uploadFile;
private int b_ref;
private int b_level;
private int b_step;
}
- MemberVO
package com.example.demo.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MemberVO {
private String id;
private String pwd;
private String m_name;
private String email;
private String phone;
}
WEB-INF/views
- deleteBoard.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>게시물 삭제</h2>
<hr>
<!-- post 방식으로 deleteBoard 쪽으로 데이터를 보낸다. -->
<form action="deleteBoard" method="post">
<!-- pwd 값을 받는다 -->
글비번 : <input type="password" name="pwd"><br>
<!-- 상태 유지 했었던 게시판 번호를 전달하기 위해 hidden 타입을 사용한다. -->
<input type="hidden" name="no" value="${no}">
<input type="submit" value="삭제">
</form>
</body>
</html>
- 게시물 상세
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>게시물 상세</h2>
<hr>
글번호 : ${b.no }<br>
글제목 : ${b.title }<br>
글내용 : <br>
<textarea rows="10" cols="80">${b.content }</textarea><br>
등록일 : ${b.regdate }<br>
ip: ${b.ip }<br>
<!-- b.no데이터를 넘겨준다. -->
<a href="updateBoard?no=${b.no}">수정</a>
<a href="deleteBoard?no=${b.no}">삭제</a>
<!-- 답글 작성은 현재 detailBoard 에 해당하는 게시글 번호를 넘겨주게 되므로 부모 no 값을 넘겨주게 된다. -->
<a href="insertBoard?no=${b.no}" class="btn btn-light">답글 작성</a>
</body>
</html>
- emailChk.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>이메일 인증</h2>
<p>이메일로 간 인증 번호를 입력해주세요</p>
<hr>
<form method="post" action="join">
인증번호 : <input type="text" name="emailChkNums">
</form>
</body>
</html>
- error.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>죄송합니다. 서비스 중에 다음과 같은 문제가 발생하였습니다.</h4>
<hr>
${msg }
</body>
</html>
- index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="listBoard">게시물 목록</a><br>
<a href="insertBoard">게시물 등록</a><br>
<a href="join">회원 등록</a><br>
</body>
</html>
- insertBoard.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>${title}</h2>
<hr>
<form action="insertBoard" method="post" enctype="multipart/form-data">
<input type="hidden" name="no" value="${no}">
글제목 : <input type="text" name="title"><br>
작성자 : <input type="text" name="writer"><br>
글암호 : <input type="password" name="pwd"><br>
글내용 : <br>
첨부파일 : <input type="file" name="uploadFile"><br>
<textarea rows="10" cols="80" name="content"></textarea><br>
<input type="submit" value="등록">
<input type="reset" value="다시입력">
</form>
</body>
</html>
- join.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script>
$(function(){
let emailChkNums; //Controller 쪽에서 넘어온 랜덤 data 값
let idChkOk = false; //id중복 체크했는지 여부
let email; //인증 완료한 이메일
//이메일 인증 버튼을 눌렀을 때,
$("#btnSendEmail").click(function(){
//이메일 값을 email 에 담고, data 객체를 생성한다.
email = $("#email").val();
let data = {email:email};
//Ajax 통신으로 /sendCode 에 Get 방식으로 통신하여, data 즉, email 값을 보낸다.
$.ajax({
url:"/sendCode",
data:data,
success:function(data){
//성공적으로 통신이 되었다면 받아온 데이터(data) emailChkNum 에 넣는다.(랜덤값)
//또한 인증번호 적는 영역을 css 로 보여준다.
$("#box_check").css("display", "block");
emailChkNums = data;
}
});
})
//인증 요청을 눌렀을 때
$("#btnCheckEmail").click(function(){
//user가 적은 인증번호 값을 가져온다.
let userInNums = $("#checkNum").val();
//그리고 Controller 에서 넘어온 data 값이랑 같은지 비교하여
if(userInNums == emailChkNums){
//같으면 나머지 회원가입 부분도 작성시킨다.
$("#loginForm").css("display", "block");
}else{
//다르면 알림창을 띄운다.
alert("인증번호가 다릅니다.");
}
});
//중복체크 버튼을 누르면
$("#btnIsUnike").click(function(e){
//먼저 form안에 button 이 있으므로 이벤트를 중지 시킨다
e.preventDefault();
//id 의 값을 가져오고
let id = $("#id").val();
//ajax 통신으로 id 값을 보낸다.
$.ajax({
url:"/idChk",
data:{userid:id},
success:function(data){
//받아온 값이 true 냐 false 냐에 따라 다음과 같이 나눠서 작업한다.
if(data){
//알림창으로 사용 가능 여부를 보낸다.
alert("사용 가능 아이디");
//emailOK 필드에 기존의 인증 완료한 email 값을 넣어준다.
$("#emailOK").val(email);
//id 체크를 완료했다는 플래그를 true 로 변경한다.
idChkOk = true;
}else{
//알림창으로 중복임을 알린다.
alert("중복 아이디");
//emailOK 부분에 필드 값을 비운다.
$("#emailOK").val("");
//id 체크를 완료했다는 플래그를 false 로 변경한다.
idChkOk = false;
}
}
});
});
//멤버 등록 버튼을 누르면
$("#btnSubmit").click(function(e){
//id 중복체크 플래그가 false이면
if(!idChkOk){
//submit 을 중지시키고
e.preventDefault();
//중복 체크 먼저 요청한다.
alert("먼저 아이디 중복체크를 완료해주세요");
}
});
});
</script>
<style>
#loginForm, #box_check{
display:none;
}
</style>
</head>
<body>
<h2>회원가입</h2>
<hr>
<div id="box_email">
이메일 : <input type="email" name="email" id="email">
<button id="btnSendEmail">인증코드 전송</button>
</div>
<div id="box_check">
인증번호 : <input type="text" id="checkNum">
<button id="btnCheckEmail">입력</button>
</div>
<form action="join" method="post" id="loginForm">
아이디 : <input type="text" name="id" id="id"><button id="btnIsUnike">중복체크</button><br>
비밀번호 : <input type="password" name="pwd"><br>
이름 : <input type="text" name="m_name"><br>
이메일 : <input type="email" name="email" id="emailOK" readOnly><br>
휴대폰 : <input type="text" name="phone"><br>
<input type="reset" value="지우기">
<input type="submit" value="등록" id="btnSubmit">
</form>
</body>
</html>
- listBoard.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>게시물 목록</h2>
<hr>
<a href="insertBoard">게시물 등록</a><br>
<table border="1" width="80%">
<tr>
<th>글번호</th>
<th>글제목</th>
<th>작성자</th>
</tr>
<c:forEach var="b" items="${list }">
<tr>
<td>${b.no }</td>
<td>
<%-- b_level만큼 들여쓰기를 해주고 글 앞에 ㄴ을 붙여준다. --%>
<c:if test="${b.b_level > 0}">
<c:forEach begin ="1" end = "${b.b_level}">
</c:forEach>
<span style="color:#b7b7b7">ㄴ </span>
</c:if>
<a href="detailBoard?no=${b.no }">${b.title }</a>
</td>
<td>${b.writer }</td>
</tr>
</c:forEach>
</table>
</body>
</html>
- updateBoard.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>게시물 수정</h2>
<hr>
<form action="updateBoard" method="post" enctype="multipart/form-data">
글제목 : <input type="text" name="title" value="${b.title}"><br>
작성자 : ${b.writer}<br>
글암호 : <input type="password" name="pwd"><br>
글내용 : <br>
<textarea rows="10" cols="80" name="content">${b.content}</textarea><br>
<input type="hidden" name="fname" value="${b.fname}">
<input type="hidden" name="fsize" value="${b.fsize}">
<input type="hidden" name="no" value="${b.no}">
첨부파일 : ${b.fname}(${b.fsize})<br>
<input type="file" name="uploadFile" ><br>
<input type="submit" value="수정">
<input type="reset" value="다시입력">
</form>
</body>
</html>
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--XMl 선언으로 XML 파일의 버전과 인코딩 지정-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--project 태그는 Maven 프로젝트의 루트 요소로 이 안에 모든 설정이 포함된다.-->
<!--xmlns 이 속성은 POM(XML 파일)의 네임스페이스를 지정한다.-->
<!--xmlns:xsi 이 속성은 XML 스키마 인스턴스 네임스페이스를 지정한다.-->
<!--xsi:schemaLocation 이 속성은 XML 스키마 파일의 위치를 지정하여 XML 파일이 스키마에 따라 올바르게 구성되었는지 검증한다.-->
<modelVersion>4.0.0</modelVersion>
<!--이것은 이 파일의 모델 버전을 지정한다. Maven 2.0 이상에서는 항상 4.0.0 을 사용한다.-->
<parent>
<!-- 이 태그는 이 프로젝트가 상속받을 부모 POM 을 정의한다.-->
<!-- 부모 POM은 기본 설정, 종속성 관리 등을 제공하는 상위 프로젝트이다.-->
<groupId>org.springframework.boot</groupId>
<!--이 태그는 부모 프로젝트의 그룹 ID를 지정한다. 이는 Spring Boot 프로젝트의 기본 그룹 ID 이다.
-->
<artifactId>spring-boot-starter-parent</artifactId>
<!--부모 프로젝트의 아티팩트 ID지정. 이 부모 POM 은 SPring Boot 프로젝트를 위한 부모 POM 사용-->
<version>3.3.2</version>
<!--부모 프로젝트 버전 지정-->
<relativePath /> <!-- lookup parent from repository -->
<!--부모 POM 을 로컬에서 찾을 때 사용할 경로를 지정한다. -->
<!--비워져있으면 Maven 은 기본적으로 중앙 리포지토리 또는 설정된 리포지토리에서 부모 POM 을 찾는다.-->
</parent>
<groupId>com.example</groupId>
<!--프로젝트 그룹 ID를 지정하며, 이 프로젝트를 식별하는 데 사용한다.-->
<!-- 일반적으로 조징이나 회사의 도메인 이름을 사용한다.-->
<artifactId>boardtests</artifactId>
<!--프로젝트 아티팩트 ID를 지정한다. -->
<!-- 아티팩트 ID는 특정 프로젝트를 식별하는 데 사용되며, 보통 프로젝트의 이름을 나타낸다.-->
<version>0.0.1-SNAPSHOT</version>
<!--프로젝트의 버전을 지정한다. 0.0.1-SNAPSHOT은 개발 중인 초기 버전을 의미-->
<!--아직 이 버전이 릴리즈 되지 않음-->
<packaging>war</packaging>
<!--pakaging 은 Maven 프로젝트가 어떻게 패키징될지 지정한다.-->
<!-- war 로 설정되어있다면 WAR(Web Application Archive)파일로 패키징 된다는 것을 의미한다.-->
<name>boardtests</name>
<!--프로젝트 이름 지정-->
<description>Demo project for Spring Boot</description>
<!--프로젝트에 간단한 설명을 제공-->
<url />
<!-- 프로젝트의 URL 을 지정하며, 보통 프로젝트 홈페이지나 관련 정보를 제공한ㄴ 웹사이트 URL 을 입력-->
<licenses>
<!--프로젝트에서 사용하는 라이센스 정리-->
<license />
</licenses>
<developers>
<!--프로젝트의 개발자 정보를 지정함.-->
<developer />
</developers>
<scm>
<!--소스 코드 관리(SCM) 시스템에 대한 정보를 지정-->
<connection />
<!--익명 사용자가 소스 코드에 접근할 수 있는 URL 을 지정-->
<developerConnection />
<!-- 개발자가 소스 코드에 접근할 수 있는 URL 을 지정-->
<tag />
<!-- 소스 코드의 특정 태그를 지정-->
<url />
<!-- 소스 코드가 호스팅된 URL 을 지정-->
</scm>
<properties>
<!--Maven 빌드에서 사용할 사용자 정의 속성을 정의-->
<java.version>17</java.version>
</properties>
<dependencies>
<!-- 프로젝트에서 사용할 외부 라이브러리를 지정하는 부분-->
<dependency>
<!--Spring boot 웹 애플리케이션을 개발하는 데 필요한 기본적인 라이브러리들을 포함하는 것을 추가-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Oracle DB 연결을 위한 JDBC 드라이버 ojdbc11을 추가-->
<!--범위는 runtime 이므로 애플리케이션 실행 시에만 포함-->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok 라이브러리 추가-->
<!-- optional 속성이 true 이므로 선택적인 의존성-->
<!-- 선택적 의존성 : 프로젝트에서는 필요하지만 이 프로젝트를 의존성으로 사용하는 다른 프로젝트에서는 필수적이지 않음을
뜻함-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Tomcat 서블릿 컨테이너를 포함함-->
<!-- provided 가 범위로 되어있어, 배포 패키지에 포함되지 않음-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!--이 의존성은 Spring Boot에서 테스트를 지원하는 라이브러리들을 포함하는 spring-boot-starter-test를 추가-->
<!-- test 범위로 지정되어 있어, 테스트 시에만 포함-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--이 의존성은 MyBatis 라이브러리 mybatis를 추가-->
<!--MyBatis는 SQL 매핑 프레임워크로, 데이터베이스와의 상호작용을 간소화-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<!--이 의존성은 JSP(JavaServer Pages)를 지원하기 위한 Tomcat Jasper를 추가-->
<!-- provided 가 범위로 되어있어, 배포 패키지에 포함되지 않음-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--JSP 의 JSTL 사용할 수 있게 해줌-->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>2.0.0</version>
</dependency>
<!--서블릿 API 을 추가-->
<!-- provided 가 범위로 되어있어, 배포 패키지에 포함되지 않음-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--이 의존성은 Apache Commons FileUpload 라이브러리를 추가하여 파일 업로드 기능을 제공-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
<!-- 이 의존성은 Apache Commons IO 라이브러리를 추가하여 다양한 IO 기능을 제공 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
<!-- 메일발송 지원 기능 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.24</version>
</dependency>
<!-- Java Mail API -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
</dependencies>
<!--프로젝트 빌드 관련 설정 정의-->
<build>
<!--Maven 빌드에서 사용할 플러그인을 정의-->
<plugins>
<!--Spring Boot 애플리케이션을 빌드하고 패키징하기 위한 것을 추가-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--플러그인의 구성을 정의-->
<configuration>
<!-- lombok을 빌드 시 제외하는 설정이 있다.-->
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
'Kosta DevOps 과정 280기 > Java' 카테고리의 다른 글
Wrapper 클래스 (0) | 2024.08.08 |
---|---|
MVC 패턴과 DI (0) | 2024.08.01 |
어노테이션 기반의 DI (0) | 2024.08.01 |
DI와 XML 이용-2 (0) | 2024.08.01 |
어제 복습 (0) | 2024.08.01 |