202209119_MON
- 프록젝트 내 검색 단축기
ctrl + shift + R : 파일명 검색
ctrl + H : 키워드 검색
실습내용
Q) 로그인 실패하면 다시 로그인창으로 이동하는데, 이 때 입력한 아이디값을 어떻게 보여지게하는가?
- 로그인 실패시, 로그인페이지로 다시 이동할시 때 커맨드객체로 memberVO가 있기때문에 자동으로 넘어온다. 그러면 컨트롤러에서 매개변수로 사용한 MemberVO 커맨드객체를 html에서 th:object 속성값으로 커맨드객체를 가져오면, th:field속성 id,name,value값들이 자동으로 생성되는데 여기서 value값이 있기때문에 화면에 보여질 수 있다.
- 참고사항) input 태그에 th:field값을 생성시 input태그 안에서 value값이 표현되지만, th:text값을 사용시에는 input태그 밖에서 값이 표현되는 것을 화면에서 알 수 있다. 이 둘의 차이점을 알 필요가 있다.
실습내용
- 게시글 목록 html 파일의 하단의 글쓰기 버튼 생성
- 버튼 클릭 시 게시글 작성 페이지로 이동
- 글쓰기 버튼은 로그인한 유저한테만 보여야 한다.
(로그인안하면 글쓰기 작성 불가)
- 글등록을 위해 제목,내용만 입력받는다.
- 작성자는 로그인한 사람으로 자동등록되도록 한다.
- 해당 글쓰기는 Validation 처리로 한다.
- 글등록 후, alert() 띄우고, 게시글 목록 페이지로 이동한다.
package kh.study.board.member.controller;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import kh.study.board.member.service.MemberService;
import kh.study.board.member.vo.MemberVO;
@Controller
@RequestMapping("/member")
public class MemberController {
@Resource(name = "memberService")//서비스임플에서 어노테이션으로 만든 객체가져온다.
private MemberService memberService;
// ---------------------- 커맨드객체 정의 -------------------------------------------//
// 커맨드객체는 html로 데이터를 전달하는 코드를 작성하지 않아도 자동으로 넘어간다.
// 이때 데이터가 넘어가는 이름은 클래스명에서 앞글자만 소문자로 변경된 이름으로 넘어간다.
//-----------------------------------------------------------------------------------//
// 회원가입_a태그로 top파일에서 넘어올 때 @GetMapping매핑
@GetMapping("/join") // a태그는 무조건 get 방식!!!(암기)
public String join(MemberVO memberVO) {
// 자동으로 커맨드 객체는 넘어간다.(memberVO) //커맨드객체에 아이디값 임의로 넣어주면, html로 넘어갈때 데이터 넘어감
System.out.println(memberVO);
return "content/member/join";
}
//-------------validation처리 :유효성 검사-----------------------------------------//
// @Valid : post로 전달된 데이터가 검증 규칙을 따르는지 판단하는 어노테이션
// 해당 어노테이션 다음에는 반드시 검증할 객체가 valid 다음 바로 와야한다
// BindingResult :검증 대상 객체와 검증 결과의 대한 정보를 담고 있는 객체.
// 검증객체 바로 다음에 선언되어야 한다.
// 실제로 input태그에 입력한 데이터가 memberVO에 들어오는 걸 바인딩이라고하는데, 그 결과를 말함.
// model도 매개변수로 넣어준이유?
// : model 같이 매개변수 사용하면 데이터를 굳이 담지않아도(눈에보이지않아도) 바인딩결과의 오류여부정보와 검증한 객체memberVO 값을
// 자동으로 리턴값 join.html에 보내준다.
//----------------------------------------------------------------------------------//
// 회원가입_form태그로 top파일에서 넘어올 때 @PostMapping매핑
@PostMapping("/join")
public String joinProcess(@Valid MemberVO memberVO, BindingResult bindingResult, Model model) {
//System.out.println("!!!!!!" + memberVO.getMemberTell()); // "010,1111,2222"
//System.out.println("!!!!!!" + memberVO.getMemberPw()); // "010,1111,2222"
//String[] tells = memberVO.getMemberTells(); for(String e : tells) {//리스트는
//반복문으로 데이터꺼내기 System.out.println(e); }
// 1) validation 체크 (데이터 유효성 검증)
if (bindingResult.hasErrors()) {// 바인딩하는데 오류가 생겼니?
System.out.println("error발생!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
return "content/member/join";// 에러발생하면 join페이지로 다시 이동
}
// 2)회원가입 쿼리 실행
memberService.join(memberVO);
// 3)페이지이동
return "redirect:/board/list";
}
// 로그인_a태그로 top파일에서 넘어올 때
//주의사항!!! login.html에서 커맨드객체를 object로 사용하려면, 반드시 매개변수로 memberVO가 있어야한다!
@GetMapping("/login") // a태그는 무조건 get 방식!!!(암기)
public String login(MemberVO memberVO) {
System.out.println("로그인 a태그로 클릭해서 넘어왔다!!!!!!!!!!!!!!!!!!");
return "content/member/login";
}
// 로그인_form태그로 top파일에서 넘어올 때
//!!로그인은 유효성검사 필요없다
@PostMapping("/login")//form태그와 ajax일때 -> 무조건 post!! 나머지 get!
public String loginProcess(HttpSession session, MemberVO memberVO) {
//로그인 쿼리 실행
MemberVO loginInfo = memberService.login(memberVO);
System.out.println("로그인하러 넘어왔다");
if (loginInfo != null) {
//로그인 세션사용하려면 위의 쿼리문으로 만든 로그인인포만든것을 세션에 값을 넣어주는대신,
//위에 매개변수로 만들어주면 된다.!!
session.setAttribute("loginInfo", loginInfo);
//로그인성공하면 바로 게시판목록페이지이동
}
else {
System.out.println("로그인실패!!!!!!!!!!!!!!!!!!!!!!!!!!");
//로그인 실패시에도 다시 로그인페이지로 이동
// 회원가입 실패시, 입력 데이터를 유지???
// 화면에 입력한 데이터를 그대로 가져가려면! 무조건 페이지로 이동해야한다.
// 다시말해서, 아래 redirect로는 데이터를 가져갈 수 있는 방법을 아직 안배워서 안된다.
// return "redirect:/member/login"; 데이터가져갈수없다!(아직..방법안배움)
return "content/member/login";//데이터가져가기때문에 input태그에 입력한 값들이 남아있는다.
//대신 html에 데이터넘기려면 model객체 매개변수필요할 수 있지만!
//커맨드 객체인 memeberVO가 이미 있기때문에 데이터를 보낼 때 굳이 model 객체가 매개변수로 필요하지않다.
}
return "redirect:/board/list";
}
// 로그아웃
@GetMapping("/logout")
public String logout(HttpSession session) {
//세션 데이터 제거하기(로그아웃)
session.removeAttribute("loginInfo");
return "redirect:/board/list";
}
}
- boardController
package kh.study.board.board.controller;
@Controller
@RequestMapping("/board")
public class BoardController {
@Resource(name = "boardService")
private BoardService boardService;
//게시글 목록페이지
@RequestMapping("/list")
public String select(Model model,BoardVO boardVO) {
model.addAttribute("boardList",boardService.selectBoard());
return "content/board/board_list";
}
// 글쓰러가기
@GetMapping("/reg")
public String reg(BoardVO boardVO) {
return"content/board/reg_board";
}
// 글 등록
@PostMapping("/reg")
public String reg(@Valid BoardVO boardVO, BindingResult bindingResult, Model model ,HttpSession session) {
//System.out.println(boardVO);
System.out.println("유효성 체크!!!!");
// 주의! 순서중요하다. 유효성체크 먼저 한 후, 로그인정보값 boardVO에 넣어주기 !!
if (bindingResult.hasErrors()) {
System.out.println("에러발생!!!!");
return"content/board/reg_board";//html페이지로 가야 데이터가 남아있는다
}
//작성자(memberId)는 PK이기때문에 글등록양식페이지에서 화면에 보이도록 띄워줘야함
MemberVO loginInfo = (MemberVO)session.getAttribute("loginInfo");
boardVO.setMemberId(loginInfo.getMemberId());
boardService.insertBoard(boardVO);
return "content/board/reg_result";
}
//게시글 상세조회
@GetMapping("/detail")
public String selectDetail(@RequestParam(required = false) int boardNum,Model model) {
model.addAttribute("board", boardService.selectDetailBoard(boardNum));
return "content/board/board_detail";
}
// 글수정하러가기
@GetMapping("/update")
public String update(BoardVO boardVO , int boardNum) {//매개변수는 커맨드객체 or model 값 둘 중 하나만 사용가능하다
//(주의)수정 전에는 어떤 글을 수정할 것인지 "상세조회" 반드시 먼저해준다!!!
//-----(방법1) 커맨드 객체 사용할 때---------------------------------//
// 커맨드객체를 사용하기때문에 update_form html파일에서 th:object와 th:field 사용가능하다
// 그리고 id,name,value 속성값이 자동생성 가능하다는 장점이 있다.
BoardVO result = boardService.selectDetailBoard(boardNum);
boardVO.setBoardNum(result.getBoardNum());
boardVO.setContent(result.getContent());
boardVO.setTitle(result.getTitle());
boardVO.setCreateDate(result.getCreateDate());
boardVO.setMemberId(result.getMemberId());
//-----(방법2) model사용할 때-------------------------------------------------//
// 하지만, model을 사용할때는 커맨드객체사용이 불가하기때문에 달러+{객체명.컬럼명}으로 데이터불러와 사용가능하다.
// 또 id,name,value 속성값이 자동생성되지않는다
//model.addAttribute("board",boardService.selectDetailBoard(boardNum) );
return "content/board/update_board_form";
}
// 글수정 등록
@PostMapping("/update")
public String update(BoardVO boardVO) {
boardService.update(boardVO);
return "content/board/update_result";//수정확인 후,alert창 띄워보기
// return "redirect:/board/detail?boardNum=" + boardVO.getBoardNum();//수정확인 후, 다시 상세보기로
}
// 글삭제
@GetMapping("/delete")
public String delete(int boardNum) {
boardService.delete(boardNum);
return "redirect:/board/list";
}
}
package kh.study.board.member.vo;
import java.util.Iterator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import groovy.transform.ToString;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ToString
public class MemberVO {
//참고//
//@NotNull //javax로시작하는 어노테이션 사용 : null허용x / "", " "빈값,공백 허용가능
@NotBlank(message = "id는 필수입력입니다.") //null,""," " 모두 허용x
private String memberId;
//정규식 코드 사용해야한다.(복사붙여넣기)
// 8-16자리, 영어대소문자,특수문자포함(#은 포함시키지않음! 주의!)
// @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,16}$"
// , message = "비밀번호 형식에 맞지 않습니다")
//(편의상 일단, validation없이 사용
@NotBlank(message = "PW는 필수입력입니다.") //null,""," " 모두 허용x
private String memberPw;
@NotBlank(message = "이름은 필수입력입니다.")
@Size(max = 5,message = "제한된 길이를 초과했습니다.")// max : 영어숫자 상관없이 최대문자길이 뜻함
private String memberName;
@Size(min = 9, max = 12)
private String memberTell;//01012345678
private String isAdmin;
private String memberStatus;
//memberTells라는 name값으로 연락처 세 개의 값을 받아서 배열을 먼저 만든다.
//배열로 만든 memberTells
private String[] memberTells;//010,1234,5678
//별도로 만들지 않아도 된다 왜냐며, 아래처럼lmbok으로 자동 생성된다.
// public String getMemberTell() {
// return memebrTell; }>>> 아무것도 변형을 안하면 null값이 들어온다.
// 그럼 null값이 안들오도록 어떻게 만드냐?
//변형하여 getter만든다.
public String getMemberTell() {
//아래처럼 리턴값을 던지려고 한다. 그러면 어떻게 해야할까?
/* return memberTells[0] + memberTells[1] + memberTells[2]; */
//if eles 문을 사용해서 데이터 던져준다.
if (memberTells == null) {
return null;
}
else {// 연락처가 null이 아니라면, 배열에서 하나씩 값을 빼와서 문자열을 누적시켜서 result값으로 데이터 던진다
String result = "";
for(String tell : memberTells) {//010,1234,5678
result += tell;//01012345678
}
return result; //01012345678
}
}
}
2.boardVO
package kh.study.board.board.vo;
import groovy.transform.ToString;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ToString
public class BoardVO {
private int boardNum;
@NotBlank(message = "제목은 필수 입력사항입니다.")
private String title;
@NotBlank(message = "내용은 필수 입력사항입니다")
private String content;
private String memberId;
private String createDate;
}
package kh.study.board.board.vo;
import groovy.transform.ToString;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ToString
public class BoardVO {
private int boardNum;
@NotBlank(message = "제목은 필수 입력사항입니다.")
private String title;
@NotBlank(message = "내용은 필수 입력사항입니다")
private String content;
private String memberId;
private String createDate;
}
package kh.study.board.board.service;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kh.study.board.board.vo.BoardVO;
@Service("boardService")
public class BoardServiceImpl implements BoardService{
@Autowired//어노테이션으로 객체생성
private SqlSessionTemplate sqlSession;
//글등록
@Override
public void insertBoard(BoardVO boardVO) {
sqlSession.insert("boardMapper.insertBoard",boardVO);
}
//게시글목록
@Override
public List<BoardVO> selectBoard() {
return sqlSession.selectList("boardMapper.selectBoard");
}
//상세보기
@Override
public BoardVO selectDetailBoard(int boardNum) {
return sqlSession.selectOne("boardMapper.selectDetailBoard", boardNum);
}
//글수정
@Override
public void update(BoardVO boardVO) {
sqlSession.update("boardMapper.update", boardVO);
}
//글삭제
@Override
public void delete(int boardNum) {
sqlSession.delete("boardMapper.delete",boardNum);
}
}
package kh.study.board.member.service;
public interface MemberService {
void join(MemberVO memberVO);
MemberVO login(MemberVO memberVO);
}
package kh.study.board.member.service;
@Service("memberService")//컨트롤러에 보내 사용할 객체를 어노테이션을 이용해 보내준다.
public class MemberServiceImpl implements MemberService{
@Autowired//어노테이션으로 객체생성
private SqlSessionTemplate sqlSession;
//회원가입
@Override
public void join(MemberVO memberVO) {
sqlSession.insert("memberMapper.join", memberVO);
}
//로그인
@Override
public MemberVO login(MemberVO memberVO) {
return sqlSession.selectOne("memberMapper.login", memberVO);
}
}
<?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="boardMapper">
<resultMap type="kh.study.board.board.vo.BoardVO" id="board">
<id column="BOARD_NUM" property="boardNum"/>
<result column="TITLE" property="title"/>
<result column="CONTENT" property="content"/>
<result column="MEMBER_ID" property="memberId"/>
<result column="CREATE_DATE" property="createDate"/>
</resultMap>
<!-- 글등록 -->
<insert id="insertBoard">
INSERT INTO BOARD (BOARD_NUM , TITLE , CONTENT , MEMBER_ID)
VALUES(
( SELECT NVL ( MAX (BOARD_NUM) , 0 ) + 1 FROM BOARD)
, #{title}
, #{content}
, #{memberId}
)
</insert>
<!-- 게시글목록조회 -->
<select id="selectBoard" resultMap="board">
SELECT BOARD_NUM,TITLE,CONTENT,MEMBER_ID,TO_CHAR(CREATE_DATE,'YYYY-MM-DD') AS CREATE_DATE
FROM BOARD
ORDER BY BOARD_NUM DESC
</select>
<!-- 게시글 상세조회 -->
<select id="selectDetailBoard" resultMap="board">
SELECT BOARD_NUM,TITLE,CONTENT,MEMBER_ID,TO_CHAR(CREATE_DATE,'YYYY-MM-DD') AS CREATE_DATE
FROM BOARD
WHERE BOARD_NUM = #{boardNum}
</select>
<!-- 글수정 -->
<update id="update" >
UPDATE BOARD
SET TITLE = #{title}
,CONTENT = #{content}
WHERE BOARD_NUM = #{boardNum}
</update>
<!-- 글삭제 -->
<delete id="delete">
DELETE BOARD
WHERE BOARD_NUM = #{boardNum}
</delete>
</mapper>
2.member-mapper
<?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="memberMapper">
<!-- 패키지명 클래스명 -->
<!-- type 오류방지위해 memberVO 위에 패키지명 복붙하면 된다! -->
<resultMap type="kh.study.board.member.vo.MemberVO" id="selectMember">
<id column="MEMBER_ID" property="memberId"/>
<result column="MEMBER_PW" property="memberPw"/>
<result column="MEMBER_NAME" property="memberName"/>
<result column="MEMBER_TELL" property="memberTell"/>
<result column="IS_ADMIN" property="isAdmin"/>
<result column="MEMBER_STATUS" property="memberStatus"/>
</resultMap>
<!-- 회원가입 -->
<!-- memberTells 배열값으로 받아오는 연락처는 어떻게 받아오는가?? -->
<!-- #{memberTell} -> memberVO.getMemberTell();
: 컨트롤러에서 넘어오는 memberVO라는 객체의 getter를 호출하게된다.
:memberTells에서 memberTell로 다시 만들어줘야한다.-->
<insert id="join">
INSERT INTO BOARD_MEMBER (
MEMBER_ID, MEMBER_PW, MEMBER_NAME, MEMBER_TELL,MEMBER_STATUS
) VALUES(
#{memberId},#{memberPw},#{memberName},#{memberTell},'ACTIVE'
)
</insert>
<!-- 로그인 -->
<!-- 주의사항!! : 조회시 관리자여부같이조회해야하면서, 회원상태가 활성화되어있어야하는 조건이 필요하다!!! -->
<select id="login" resultMap="selectMember">
SELECT MEMBER_ID , MEMBER_NAME ,IS_ADMIN
FROM BOARD_MEMBER
WHERE MEMBER_ID = #{memberId}
AND MEMBER_PW = #{memberPw}
AND MEMBER_STATUS = 'ACTIVE'
</select>
</mapper>
static
css
@charset "UTF-8";
@font-face {
font-family: 'HallymGothic-Regular';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2204@1.0/HallymGothic-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
body{
font-family: 'HallymGothic-Regular';
font-size: 25px;
}
a{
text-decoration: none;
color: #224B0C;
}
a:hover {
color: #A1C298;
}
.hasError {
color: red;
font-style: italic;
}
button{
text-decoration: none;
color: #224B0C;
}
<!DOCTYPE html>
<!-- 타임리프,레이아웃 기능 사용하겠다 -->
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- 부트스트랩 사용 1 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
<link href="/css/common.css" rel="stylesheet">
</head>
<body>
<!-- 화면에 보여지는 모든 내용은 여기 div태그 안에서 표시된다 -->
<div class="container">
<div class="row">
<div class="col">
<div th:replace="fragment/top::topFragment" ></div>
</div>
</div>
<div class="row">
<div class="col">
<div layout:fragment="content"></div>
</div>
</div>
</div>
<!-- jquery 로딩 -->
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<!-- 부트스트랩 로딩 _사용 2 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="topFragment">
<!-- 로그인 클릭시 로그인html파일 이동방법 -->
<!-- 1번 <a href="/board/login">login</a> -->
<!-- 2번 <a th:href="@{/board/login}"> login</a><!-- 타임리프 경로이동시!!! 골뱅이+중괄호 -->
<div class="row">
<div class="col text-end" style="color:#224B0C;">
<!-- 주의할 점 : sessionScope 는 jsp // session 은 타임리프 th-->
<th:block th:if="${session.loginInfo == null}">
<a th:href="@{/member/login}">login</a><!-- a태그 -> get방식!!! -->
<a th:href="@{/member/join}">join</a><!-- a태그 -> get방식!!! -->
<!-- <span th:onclick="location.href='/board/join';"></span> -->
</th:block>
<th:block th:unless="${session.loginInfo == null}">
[[${session.loginInfo.memberName}]]님 반갑습니다😊
<a th:href="@{/member/logout}">LOGOUT</a><!-- a태그 -> get방식!!! -->
</th:block>
</div>
</div>
<br>
<br>
<div class="row">
<div class="col text-center" style="background-color: #EEF2E6;">
<span style="font-weight: bold; font-size: 50px; color: #3D8361;">B O A R D</span>
</div>
</div>
</div>
</html>
board
board_list
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}"><!-- 콘솔 warn 안뜨기위해 -->
<div layout:fragment="content">
<br>
<br>
<div>
<table class="table">
<thead class="table-light">
<tr>
<td>글번호</td>
<td>글제목</td>
<td>작성일</td>
<td>작성자</td>
</tr>
</thead>
<tbody>
<th:block th:if="${#lists.size('boardList') == 0}">
<tr>
<td colspan="4"> 게시글이 없습니다.</td>
</tr>
</th:block>
<!-- boardListr가져올때 홀따옴표 감싸서 가져오기 주의!!!! -->
<th:block th:unless="${#lists.size('boardList') == 0}" >
<tr th:each="board : ${boardList}">
<td th:text="${board.boardNum}"></td>
<td>
<a th:href="@{/board/detail(boardNum=${board.boardNum})}" th:text="${board.title}"></a>
</td>
<td th:text="${board.createDate}"></td>
<td th:text="${board.memberId}"></td>
</tr>
</th:block>
</tbody>
</table>
</div>
<div align="center">
<th:block th:if="${session.loginInfo != null}">
<button type="button" class=" btn btn-outline-success" th:onclick="@{location.href='/board/reg';}">글쓰기</button>
</th:block>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<div class="row justify-content-center">
<form th:action="@{/board/reg}" method="post" th:object="${boardVO}">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">작성자 :</label>
[[${session.loginInfo.memberId}]]
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label" >글 제목 :</label>
<input name="title" type="text" class="form-control" th:field="*{title}" placeholder="제목을 입력하세요">
<div class="hasError" th:if="${#fields.hasErrors('title')}" th:errors="*{title}" ></div>
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">글 내용 :</label>
<textarea name="content" class="form-control" th:field="*{content}" rows="3" placeholder="내용을 입력하세요"></textarea>
<div class="hasError" th:if="${#fields.hasErrors('content')}" th:errors="*{content}" ></div>
</div>
<div align="center">
<button class="btn btn-light" type="submit" th:onclick="@{location.href='/board/reg';}">글등록</button>
</div>
</form>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<script type="text/javascript">
alert('글쓰기등록성공!');
location.href='/board/list';
</script>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<div class="mb-3" >
<label for="exampleFormControlspan1" class="form-label">글 번호 :</label>
<th:block th:text="${board.boardNum}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlspan1" class="form-label"> 작성자 :</label>
<th:block th:text="${board.memberId}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlspan1" class="form-label"> 작성일 :</label>
<th:block th:text="${board.createDate}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlspan1" class="form-label">글제목 :</label>
<th:block th:text="${board.title}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">글내용 :</label>
<div th:text="${board.content}"></div>
</div>
<div align="center">
<th:block th:if="${session.loginInfo != null}">
<button type="button" class="btn btn-light" th:onclick="|location.href='@{/board/update(boardNum=${board.boardNum})}'|">수정</button>
<button type="button" class="btn btn-danger" th:onclick="|location.href='@{/board/delete(boardNum=${board.boardNum})}'|">삭제</button>
</th:block>
<button type="button" class="btn btn-dark" th:onclick="@{location.href='/board/list';}">뒤로가기</button>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<form th:action="@{/board/update}" method="post" th:object="${boardVO}" >
<!-- 문제점 해결 -->
<!-- (유의) input태그에서 th:text를 사용하면 input태그밖에 값이 화면에 보여진다.
form태그는 값이 입력되는 값을 넘겨주기 때문에 th:block이 아닌 input으로 넘겨야한다!!! 아니면 데이터를 들고가지못함
input태그는 창이 입력받기때문에 글번호가 수정될수없도록 readonly값으로 속성을 둬야한다 -->
<!-- 글번호는 히든값으로 넘겨준다 pk이기때문에 상세보기가려면 필요하기때문이다! -->
<input type="hidden" th:field="*{boardNum}" readonly>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label"> 작성자 :</label>
<th:block th:text="*{memberId}" <th:field="*{memberId}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label"> 작성일 :</label>
<th:block th:text="*{createDate}" th:field="*{createDate}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">글제목 :</label>
<input th:field="*{title}" type="text" class="form-control" id="exampleFormControlInput1" >
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">글내용 :</label>
<textarea th:field="*{content}" th:text="${content}" class="form-control" id="exampleFormControlTextarea1" rows="3" ></textarea>
</div>
<div align="center">
<button type="submit" class="btn btn-light">수정완료</button>
</div>
</form>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<script type="text/javascript">
alert('글 수정 완료했습니다.');
location.href='/board/list';
</script>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<!-- th 타임리프사용하려면 경로이동은!!! 골뱅이+{}사용하기!!! -->
<!-- th:object="${memberVO}" 는 데이터받기위해 사용 : 컨트롤러에서 보낸 객체 받아오기!!!
커맨드객체 memberVO는 자동으로 데이터 가져와서 사용가능하다 -->
<!-- 단, th:field는 th:object와 반드시 함께 사용하는 애트리뷰트 -->
<!-- formx태그에 th:object로 넘어오는 커맨드객체를 지정할 경우
해당 폼 태그 내에서 th:field 속성으로 자동으로 id,name,value 값을 세팅할 수 있다. -->
<!-- 개발자모드 콘솔을 보면 th:feild속성값만으로 name,id,value값 모두 확인가능하다!!! -->
<!-- th:field 받지않고 원래 사용하던 방법 -->
<!-- id :<input type="text" name="memberId" id="memberId"> -->
<!-- th:field 받아와서 사용하는 방법 -->
<!-- *{memberId}라는 값으로 들어오는 데이터 받아와서 사용하겠다. -->
<div layout:fragment="content">
<!-- row justify-content-center : 가운데 정렬 -->
<div class="row justify-content-center">
<div class="col-6">
<form th:action="@{/member/join}" class="row g-3" method="post" th:object="${memberVO}">
<div class="col-12">
<label for="memberId" class="form-label"> ID </label>
<input type="text" class="form-control" th:field="*{memberId}">
<!-- 이 html파일에 위의 object의 해당하는 memberVO라는 객체를 던져줬기때문에 에러발생있는지 알수있는 것이다. -->
<!-- field 는 변수를 의미한다.-->
<!-- * 와 {} 표기 : object의 객체(memberVO에 담긴 데이터)로 인식한다. -->
<div class="hasError" th:if="${#fields.hasErrors('memberId')}" th:errors="*{memberId}"></div>
</div>
<div class="col-12">
<label for="memberPw" class="form-label">PASSWORD</label>
<input type="password" class="f orm-control" th:field="*{memberPw}">
<div class="hasError" th:if="${#fields.hasErrors('memberPw')}" th:errors="*{memberPw}"></div>
</div>
<div class="col-12">
<label for="memberName" class="form-label">NAME</label>
<input type="text" class="form-control" th:field="*{memberName}">
<div class="hasError" th:if="${#fields.hasErrors('memberName')}" th:errors="*{memberName}"></div>
</div>
<div class="col-4">
<!-- 연락처는 타임리프 사용하면 에러난다!!! -->
<label for="memberTells" class="form-label">TELL</label>
<select name="memberTells" class="form-select" >
<option value="010" selected>010</option>
<option value="">011</option>
</select>
</div>
<div class="col-4">
<label for="" class="form-label"> </label>
<input type="text" class="form-control" name="memberTells">
</div>
<div class="col-4">
<label for="" class="form-label"> </label>
<input type="text" class="form-control" name="memberTells">
</div>
<!-- div태그 밖으로 빼놓고 데이터를 memberTell 로 넣어준다 (memberTells 아님!! 주의) -->
<div class="hasError" th:if="${#fields.hasErrors('memberTell')}" th:errors="*{memberTell}"></div>
<div class="col-12 d-grid gap-2">
<button type="submit" class="btn btn-outline-success">JOIN</button>
</div>
</form>
</div>
</div>
</div>
</html>
- login
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<br>
<br>
<div class="row justify-content-center">
<div class="col-6">
<form th:action="@{/member/login}" class="row g-3" method="post" th:object="${memberVO}">
<!-- 컨트롤러에 반드시 매개변수로 memeberVO가 있어야 넘어오는 객체인 object에서 받아서 사용가능하다
그래서 반드시 object에서 만들어 사용하려는 커맨드객체는 컨트롤러에 매개변수로 반드시 받아와야 사용가능하다. -->
<div class="col-12">
<label for="memberId" class="form-label" >ID</label>
<!--th:object에서 받아온 커맨드객체 memebrVO가 있으면 th:field= 사용시 id,name,value(화면에 보이는)값 자동생성 -->
<input type="text" class="form-control" th:field="*{memberId}" >
<!-- value값에 ${memberVO.memberId} 가능 -->
</div>
<div class="col-12">
<label for="memberPw" class="form-label" >PW</label>
<input type="password" class="form-control" th:field="*{memberPw}" >
</div>
<div class="col-12 d-grid gap-2">
<button type="submit" class="btn btn-outline-success">로그인</button>
<button type="button" class="btn btn-outline-warning" onclick="location.href='/member/join';" >회원가입</button>
</div>
</form>
</div>
</div>
</div>
</html>
결과
기본경로: localhost:8081/board/list
첫화면: 게시글 목록 페이지
회원가입
db 확인
로그인
:가입한 데이터 입력후, top 변경+ '글쓰기'버튼 생성
로그아웃
:버튼 클릭시 로그인 후에서 첫 화면으로 원상복귀
글쓰기(글등록)
글등록 성공 alert
글등록 후 alert 확인 버튼 클릭시, 게시글 목록페이지 이동
: 게시글 등록 확인
글제목 클릭시, 상세보기
게시글 수정(전 / 후)
-수정완료 버튼 클릭시 alert 창
alert 확인 시 게시글 목록페이지 이동
: 수정된 게시글 확인 가능
게시글 삭제
: 수정된 게시글 제목 클릭시, 상세보기 이동 후, 삭제버튼 클릭
삭제확인 alert 창 확인
게시글 목록페이지로 이동 후, 게시글 삭제 확인 가능