13.1 강좌 entity 및 repository 구성
Ex)
- course 패키지 아래 Course 클래스 만들기
<hide/>
package com.zerobase.fastlms.course;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDate;
@Data
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String imagePath;;
String keyword;
String subject;
@Column(length = 1000)
String summary;
@Lob
String contents;
long price;
long salesPrice;
LocalDate salesEndDt;
}
- CourseRepository 인터페이스
<hide/>
package com.zerobase.fastlms.admin.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String categoryName;
int sortValue;
boolean usingYn; // 사용가능한지
}
- AdminCourseController
<hide/>
package com.zerobase.fastlms.course.controller;
import com.zerobase.fastlms.admin.service.CategoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
@RequiredArgsConstructor
public class AdminCourseController {
private final CategoryService categoryService;
@GetMapping("/admin/course/list.do")
public String list(Model model){
return "admin/course/list";
}
}
13.2 강좌 기능 심플화 등록 및 심플화 목록 구현
Ex)
- 컨트롤러
<hide/>
@GetMapping("/admin/course/add.do")
public String add(Model model){
return "admin/course/add";
}
- add 페이지를 만든다. (Cource 엔티티 클래스의 변수 이름을 보고 참고해야한다.)
<hide/>
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>관리자 화면</title>
<style>
.detail table{
width: 100%;
border-collapse: collapse;
}
.detail table th, .detail table td{
border: solid 1px #000;
}
.buttons{
margin-top: 20px;
text-align: center;
}
.buttons a, .buttons button{
border-width: 0;
background-color: transparent;
text-decoration: underline;
font-size: 12px;
line-height: 20px;
height: 20px;
cursor: pointer;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(document).ready(function (){
});
</script>
</head>
<body>
<h1>강좌 관리 - 강좌 등록</h1>
<!-- <div th:replace="/fragments/layout.html :: fragment-admin-body-menu" ></div>-->
<div class="detail">
<form method="post">
<table>
<tbody>
<tr>
<th>
강좌명:
</th>
<td>
<input type="text" name="subject" required placeholder="강좌명 입력"/>
</td>
</tr>
</tbody>
</table>
<div class="buttons">
<button type="submit">강좌 등록 하기</button>
<a href="/admin/course/list.do">목록으로 이동</a>
</div>
</form>
</div>
</body>
</html>
- 코스 서비스
<hide/>
package com.zerobase.fastlms.course.service;
import com.zerobase.fastlms.course.model.CourseInput;
public interface CourseService {
/**
* 강좌 등록
*/
boolean add(CourseInput parameter);
}
- 이를 구현한 서비스 임플
<hide/>
package com.zerobase.fastlms.course.service;
import com.zerobase.fastlms.course.entity.Course;
import com.zerobase.fastlms.course.model.CourseInput;
import com.zerobase.fastlms.course.repository.CourseRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class CourseServiceImpl implements CourseService{
private final CourseRepository courseRepository;
@Override
public boolean add(CourseInput parameter){
Course course = Course.builder()
.subject(parameter.getSubject())
.regDt(LocalDateTime.now())
.build();
courseRepository.save(course);
return true;
}
}
- 컨트롤러
<hide/>
@PostMapping("/admin/course/add.do")
public String addSubmit(Model model, CourseInput parameter){
boolean result = courseService.add(parameter);
return "redirect:/admin/course/list.do";
}
============================== 오류 ==============================
- 오류: 강좌 등록하기 버튼을 누르면
- 원인: add.html 페이지에서 <form method="post"> 라고 작성해야하는데 method 대신 action을 입력했다가 오류났다.
-> 수정 후 정상 화면
Ex) 강좌 목록 보여주기
- 마이바티스로 리스트를 구성해야한다.
- common param 만든다. 공통 속성
<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class CommonParam {
long pageIndex;
long pageSize;
String searchType;
String searchValue;
public long getPageStart() {
init();
return (pageIndex - 1) * pageSize;
}
public long getPageEnd() {
init();
return pageSize;
}
public void init() {
if (pageIndex < 1) {
pageIndex = 1;
}
if (pageSize < 10) {
pageSize = 10;
}
}
public String getQueryString() {
init();
StringBuilder sb = new StringBuilder();
if(searchType != null && searchType.length() > 0){
sb.append(String.format("searchType=%s", searchType));
}
if(searchValue != null && searchValue.length() > 0){
if(sb.length() > 0){
sb.append("&");
}
sb.append(String.format("searchValue=%s", searchValue));
}
return sb.toString();
}
}
- 바뀐 MemberParam
<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class MemberParam extends CommonParam {
String userId;
}
- courseParam
<hide/>
package com.zerobase.fastlms.course.model;
import com.zerobase.fastlms.admin.model.CommonParam;
import lombok.Data;
@Data
public class CourseParam extends CommonParam {
}
- courseDto
<hide/>
package com.zerobase.fastlms.course.dto;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class CourseDto {
Long id;
String imagePath;;
String keyword;
String subject;
String summary;
String contents;
long price;
long salesPrice;
LocalDate salesEndDt;
LocalDateTime regDt; // 등록일 - 추가 날짜
LocalDateTime udtDt; // 수정일 - 수정 날짜
long totalCount;
long seq;
}
- courseService
<hide/>
/**
* 강좌 목록
*/
List<CourseDto> list(CourseParam parameter);
- 컨트롤러
-> 자주 쓰이는 부분을 위해 베이스 컨트롤러도 만든다.
<hide/>
@Controller
@RequiredArgsConstructor
public class AdminCourseController extends BaseController{
private final CourseService courseService;
@GetMapping("/admin/member/list.do")
public String list(Model model, CourseParam parameter){
parameter.init(); // 유효한 값이 되도록 만든다.
List<CourseDto> courseList = courseService.list(parameter); // 값이 저장되면 토탈카운트가 존재한다.
long totalCount = 0;
if(CollectionUtils.isEmpty(courseList)){
totalCount = courseList.get(0).getTotalCount();
}
String queryString = parameter.getQueryString();
PageUtil pageUtil = new PageUtil(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);
model.addAttribute("list", courseList);
model.addAttribute("totalCount", totalCount); // 데이터를 반환
model.addAttribute("pager", pageUtil.pager()); // 데이터를 반환
return "admin/member/list";
}
// 생략
}
- 베이스
<hide/>
package com.zerobase.fastlms.course.controller;
import com.zerobase.fastlms.util.PageUtil;
public class BaseController {
public String getPaperHtml(long totalCount, long pageSize, long pageIndex, String queryString){
PageUtil pageUtil = new PageUtil(totalCount, pageSize, pageIndex, queryString);
return pageUtil.pager();
}
}
- AdminCourseCtrl => (여기서 if문에 !를 빼먹었다가 오류가 났다. - TempleteInputExcp)
<hide/>
@GetMapping("/admin/member/list.do")
public String list(Model model, CourseParam parameter){
parameter.init(); // 유효한 값이 되도록 만든다.
List<CourseDto> courseList = courseService.list(parameter); // 값이 저장되면 토탈카운트가 존재한다.
long totalCount = 0;
if(!CollectionUtils.isEmpty(courseList)){
totalCount = courseList.get(0).getTotalCount();
}
String queryString = parameter.getQueryString();
String paperHtml = getPaperHtml(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);
model.addAttribute("list", courseList);
model.addAttribute("totalCount", totalCount); // 데이터를 반환
model.addAttribute("pager", paperHtml); // 데이터를 반환
return "admin/member/list";
}
- AdminMemberCtrl도 변경 가능
<hide/>
@GetMapping("/admin/member/list.do")
public String list(Model model, MemberParam parameter){
parameter.init(); // 유효한 값이 되도록 만든다.
List<MemberDto> members = memberService.list(parameter); // 값이 저장되면 토탈카운트가 존재한다.
long totalCount = 0;
if(members != null && members.size() > 0){
totalCount = members.get(0).getTotalCount();
}
String queryString = parameter.getQueryString();
String pagerHtml = getPaperHtml(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);
model.addAttribute("list", members);
model.addAttribute("totalCount", totalCount); // 데이터를 반환
model.addAttribute("pager", pagerHtml); // 데이터를 반환
return "admin/member/list";
}
- course 내부 list
<hide/>
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>관리자 화면</title>
<style>
.list table{
width: 100%;
border-collapse: collapse;
}
.list table th, .list table td{
border: solid 1px #000;
}
p.nothing {
text-align: center;
padding: 100px;
}
.pager{
margin-top: 10px;
text-align: center;
}
.pager a.on{
font-weight: bold;
color: red;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(document).ready(function (){
});
</script>
</head>
<body>
<h1>강좌 관리</h1>
<!-- <div th:replace="/fragments/layout.html :: fragment-admin-body-menu" ></div>-->
<div>
<a href="/admin/course/add.do">강좌 등록</a>
</div>
<div class="list">
<table>
<thead>
<tr>
<th>ID</th>
<th>강좌명</th>
<th>등록일</th>
</tr>
</thead>
<tbody>
<tr th:each="x : ${list}">
<td th:text="${x.seq}">1</td>
<td>
<p th:text="${x.subject}"></p>
</td>
<td>
<p th:text="${x.regDt}">2022-01-01</p>
</td>
</tr>
</tbody>
</table>
<div class="pager" th:utext="${pager}"></div>
</div>
</body>
</html>
- CourseServiceImpl
<hide/>
package com.zerobase.fastlms.course.service;
import com.zerobase.fastlms.course.dto.CourseDto;
import com.zerobase.fastlms.course.entity.Course;
import com.zerobase.fastlms.course.mapper.CourseMapper;
import com.zerobase.fastlms.course.model.CourseInput;
import com.zerobase.fastlms.course.model.CourseParam;
import com.zerobase.fastlms.course.repository.CourseRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
public class CourseServiceImpl implements CourseService{
private final CourseRepository courseRepository;
private final CourseMapper courseMapper;
@Override
public boolean add(CourseInput parameter){
Course course = Course.builder()
.subject(parameter.getSubject())
.regDt(LocalDateTime.now())
.build();
courseRepository.save(course);
return true;
}
@Override
public List<CourseDto> list(CourseParam parameter) {
long totalCount = courseMapper.selectListCount(parameter);
List<CourseDto> list = courseMapper.selectList(parameter);
if(!CollectionUtils.isEmpty(list)){
int i = 0;
for(CourseDto x : list){
x.setTotalCount(totalCount); // 전체 개수를 하나씩 다 넣어준다.
x.setSeq(totalCount - parameter.getPageStart() - i);
++i;
}
}
return list;
}
}
- 코스 매퍼 인터페이스 만들기
-> 마이바티스의 매퍼 어노테이션
-> Mapper를 만들면 짝을 이루는 xml 파일이 존재해야한다.
<hide/>
package com.zerobase.fastlms.course.mapper;
import com.zerobase.fastlms.course.dto.CourseDto;
import com.zerobase.fastlms.course.model.CourseParam;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CourseMapper {
long selectListCount(CourseParam parameter);
List<CourseDto> selectList(CourseParam memberParam);
}
cf) SQL문을 DML태그에 추가 - 동일한 쿼리를 여러 번 삽입 가능하다.
<include refid="selectListWhere"></include>
- CouerserMapper.xml 파일
-> 아래 쪽의 <SELECT...> 뒤에 슬래시를 잘못 붙였다가 에러가 났다.
<hide/>
<?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.zerobase.fastlms.course.mapper.CourseMapper">
<sql id="selectListWhere">
<if test="searchValue != null and searchType != null">
<choose>
<when test="searchType == 'userId'">
and user_id like concat('%', #{searchValue}, '%')
</when>
<when test="searchType == 'userName'">
and user_name like concat('%', #{searchValue}, '%')
</when>
<when test="searchType == 'phone'">
and phone like concat('%', #{searchValue}, '%')
</when>
<otherwise>
and
(
user_id like concat('%', #{searchValue}, '%')
or
user_name like concat('%', #{searchValue}, '%')
or
phone like concat('%', #{searchValue}, '%')
)
</otherwise>
</choose>
</if>
</sql>
<select id="selectListCount" resultType="long">
SELECT COUNT(*)
FROM member
WHERE 1 = 1
<include refid="selectListWhere"></include>
</select>
<select id="selectList" resultType="com.zerobase.fastlms.course.dto.CourseDto">/
SELECT *
FROM member
WHERE 1 = 1
<include refid="selectListWhere"></include>
LIMIT #{pageStart} , #{pageEnd}
</select>
</mapper>
- 임플 list() 메서드
<hide/>
@Override
public List<CourseDto> list(CourseParam parameter) {
long totalCount = courseMapper.selectListCount(parameter);
List<CourseDto> list = courseMapper.selectList(parameter);
if(!CollectionUtils.isEmpty(list)){
int i = 0;
for(CourseDto x : list){
x.setTotalCount(totalCount); // 전체 개수를 하나씩 다 넣어준다.
x.setSeq(totalCount - parameter.getPageStart() - i);
++i;
}
}
return list;
}
- courseDto
<hide/>
package com.zerobase.fastlms.course.dto;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class CourseDto {
Long id;
String imagePath;;
String keyword;
String subject;
String summary;
String contents;
long price;
long salesPrice;
LocalDate salesEndDt;
LocalDateTime regDt; // 등록일 - 추가 날짜
LocalDateTime udtDt; // 수정일 - 수정 날짜
long totalCount;
long seq;
}
=============================== 오류 ===============================
- 오류: 다른 페이지는 잘 뜨는데 강좌 관리 페이지에서 SQL 예외가 발생한다. SQLSyntaxErrorException
- 원인: xml 파일에서 테이블 이름 course로 바꿈. 그니고 가장 마지막의 <SELECT ...> 뒤에 슬래시가 들어가는 바람에 SQL문법 오류가 났다. ..오타 그만 내기!!
Sol) xml 파일 (수정 후)
<hide/>
<?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.zerobase.fastlms.course.mapper.CourseMapper">
<sql id="selectListWhere">
<if test="searchValue != null and searchType != null">
<choose>
<when test="searchType == 'userId'">
and user_id like concat('%', #{searchValue}, '%')
</when>
<when test="searchType == 'userName'">
and user_name like concat('%', #{searchValue}, '%')
</when>
<when test="searchType == 'phone'">
and phone like concat('%', #{searchValue}, '%')
</when>
<otherwise>
and
(
user_id like concat('%', #{searchValue}, '%')
or
user_name like concat('%', #{searchValue}, '%')
or
phone like concat('%', #{searchValue}, '%')
)
</otherwise>
</choose>
</if>
</sql>
<select id="selectListCount"
resultType="long">
SELECT COUNT(*)
FROM course
WHERE 1 = 1
<include refid="selectListWhere"/>
</select>
<select id="selectList"
resultType="com.zerobase.fastlms.course.dto.CourseDto">
SELECT *
FROM course
WHERE 1 = 1
<include refid="selectListWhere"/>
LIMIT #{pageStart}, #{pageEnd}
</select>
</mapper>
Note) 실행 결과 - 정상 실행
========================= 오류 =========================
- 오류 :TemplateInputException
- 원인: AdminCourseController에서 list() 메서드 if문에 '!'를 빼먹었다.
<hide/>
if(!CollectionUtils.isEmpty(courseList)){
totalCount = courseList.get(0).getTotalCount();
}
-> 수정 후, 정상 화면
13.3 강좌 상세 정보 등록 및 수정 구현 (1)
Ex) 강좌 상세 정보 수정
- 리스트에 강좌명 하이퍼링크 추가
- 컨트롤러
<hide/>
@GetMapping(value = {"/admin/course/add.do" , "/admin/course/edit.do"})
public String add(Model model, HttpServletRequest request, CourseInput parameter){
boolean editMode = request.getRequestURI().contains("/edit.do");
if(editMode){
long id = parameter.getId(); // 데이터가 존재하는지 체크
CourseDto existCourse = courseService.getById(id);
if(existCourse == null){ // 수정할 데이터가 없는 경우 - 에러 처리
model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
return "common/error";
}
model.addAttribute("detail", existCourse);
}
return "admin/course/add";
}
- 서비스에 getById() 추가
<hide/>
/**
* 강좌 상세 정보
*/
CourseDto getById(long id);
- CourseServiceImpl - getById()
<hide/>
@Override
public CourseDto getById(long id) {
return courseRepository.findById(id).map(CourseDto::of).orElse(null);
}
- of() 메서드
<hide/>
public static CourseDto of(Course course) {
CourseDto courseDto = CourseDto.builder()
.id(course.getId())
.imagePath(course.getImagePath())
.keyword(course.getKeyword())
.subject(course.getSubject())
.summary(course.getSummary())
.contents(course.getContents())
.price(course.getPrice())
.salesPrice(course.getSalesPrice())
.salesEndDt(course.getSalesEndDt())
.regDt(course.getRegDt())
.udtDt(course.getUdtDt())
.build();
return courseDto;
}
- 에러 페이지
-> 방금 컨트롤러에서 model에 "massage"를 넣어서 변수를 이용 가능하다.
<hide/>
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>에러 화면</title>
</head>
<body>
<h1>에러 화면</h1>
<p th:text="${message}"></p>
<div>
<a href="/">홈으로 이동</a>
</div>
</body>
</html>
Note) 실행 결과 - 존재하지 않는 강좌 정보 페이지로 이동할 때
- 컨트롤러 수정
<hide/>
@GetMapping(value = {"/admin/course/add.do" , "/admin/course/edit.do"})
public String add(Model model, HttpServletRequest request, CourseInput parameter){
boolean editMode = request.getRequestURI().contains("/edit.do");
CourseDto detail = new CourseDto();
if(editMode){
long id = parameter.getId(); // 데이터가 존재하는지 체크
CourseDto existCourse = courseService.getById(id);
if(existCourse == null){ // 수정할 데이터가 없는 경우 - 에러 처리
model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
return "common/error";
}
detail = existCourse; // 널이 아닐 때 넣어준다.
}
model.addAttribute("editMode", editMode);
model.addAttribute("detail", detail);
return "admin/course/add";
}
- 이렇게 @PostMapping에 value를 넣어야 둘다 처리 가능하다.
<hide/>
@PostMapping(value={"/admin/course/add.do", "/admin/course/edit.do"})
public String addSubmit(Model model, HttpServletRequest request ,CourseInput parameter){
boolean editMode = request.getRequestURI().contains("/edit.do");
// 유효성 검사
if(editMode){
long id = parameter.getId(); // 데이터가 존재하는지 체크
CourseDto existCourse = courseService.getById(id);
if(existCourse == null){ // 수정할 데이터가 없는 경우 - 에러 처리
model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
return "common/error";
}
boolean result = courseService.set(parameter); // 수정 전용 메서드
}else{
boolean result = courseService.add(parameter);
}
return "redirect:/admin/course/list.do";
}
- 서비스 - set() 메서드
boolean set(CourseInput parameter);
- 임플에 set() 구현
<hide/>
@Override
public boolean set(CourseInput parameter) {
Optional<Course> optionalCourse = courseRepository.findById(parameter.getId());
if(!optionalCourse.isPresent()){ // 수정할 데이터가 없는 경우
return false;
}
Course course = optionalCourse.get();
course.setSubject(parameter.getSubject());
course.setUdtDt(LocalDateTime.now());
courseRepository.save(course);
return true;
}
Note) 실행 결과 - 수정
- 수정 후, 리스트로 이동한다.
Ex) 강좌 등록
- 컨트롤러 add()
<hide/>
@GetMapping(value = {"/admin/course/add.do" , "/admin/course/edit.do"})
public String add(Model model, HttpServletRequest request, CourseInput parameter){
// 카테고리 정보를 내려줘야한다.
model.addAttribute("category", categoryService.list()); // 카테고리가 클라이언트에 내려간다.
boolean editMode = request.getRequestURI().contains("/edit.do");
CourseDto detail = new CourseDto();
if(editMode){
long id = parameter.getId(); // 데이터가 존재하는지 체크
CourseDto existCourse = courseService.getById(id);
if(existCourse == null){ // 수정할 데이터가 없는 경우 - 에러 처리
model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
return "common/error";
}
detail = existCourse; // 널이 아닐 때 넣어준다.
}
model.addAttribute("editMode", editMode);
model.addAttribute("detail", detail);
return "admin/course/add";
}
- 강좌 add 페이지에 카테고리 옵션이 나오도록 한다.
<hide/>
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>관리자 화면</title>
<style>
.detail table{
width: 100%;
border-collapse: collapse;
}
.detail table th, .detail table td{
border: solid 1px #000;
}
.buttons{
margin-top: 20px;
text-align: center;
}
.buttons a, .buttons button{
border-width: 0;
background-color: transparent;
text-decoration: underline;
font-size: 12px;
line-height: 20px;
height: 20px;
cursor: pointer;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(document).ready(function (){
});
</script>
</head>
<body>
<h1>강좌 관리 - 강좌 등록</h1>
<!-- <div th:replace="/fragments/layout.html :: fragment-admin-body-menu" ></div>-->
<div class="detail">
<form method="post">
<table>
<tbody>
<tr>
<th>강좌 카테고리</th>
<td>
<select name="categoryId" required>
<option value="">카테고리 선택</option>
<option th:each="x : ${category}" th:value="${x.id}" th:text="${x.categoryName}">기획</option>
</select>
</td>
</tr>
<tr>
<th>
강좌명:
</th>
<td>
<input th:value="${detail.subject}" type="text" name="subject" required placeholder="강좌명 입력"/>
</td>
</tr>
</tbody>
</table>
<div class="buttons">
<button th:if="${editMode}" type="submit">강좌 수정 하기</button>
<button th:if="${!editMode}" type="submit">강좌 등록 하기</button>
<a href="/admin/course/list.do">목록으로 이동</a>
</div>
</form>
</div>
</body>
</html>
Note) 실행 결과
Ex) 수정한 카테고리 저장하기
- categoryId에 대해서 값을 Course가 저장해야한다. Course에 멤버 추가
long categoryId;
- 임플에 add()에 추가, set()에도 동일하게 추가한다.
<hide/>
@Override
public boolean add(CourseInput parameter){
Course course = Course.builder()
.categoryId(parameter.getCategoryId())
.subject(parameter.getSubject())
.regDt(LocalDateTime.now())
.build();
courseRepository.save(course);
return true;
}
Note) 실행 결과 - 업데이트 날짜가 뜬다.
13.4 강좌 상세 정보 등록 및 수정 구현 (2)
Ex)
- Category에 categoryId를 추가
- add
<hide/>
<tr>
<th>강좌 카테고리</th>
<td>
<select name="categoryId" required>
<option value="">카테고리 선택</option>
<option
th:selected="${detail.categoryId == x.id}"
th:each="x : ${category}" th:value="${x.id}" th:text="${x.categoryName}">기획</option>
</select>
</td>
</tr>
<tr>
<th>
강좌명:
</th>
<td>
<input th:value="${detail.subject}" type="text" name="subject" required placeholder="강좌명 입력"/>
</td>
</tr>
<tr>
<th>
키워드
</th>
<td>
<input th:value="${detail.keyword}" type="text" name="keyword" required placeholder="키워드 입력"/>
</td>
</tr>
<tr>
<th>
요약문구
</th>
<td>
<textarea th:text="${detail.summary}" name="summary" required placeholder="요약문구 입력"></textarea>
</td>
</tr>
<tr>
<th>
내용
</th>
<td>
<textarea th:text="${detail.contents}" name="contents" required placeholder="내용 입력"/>
</td>
</tr>
<tr>
<th>
정가
</th>
<td>
<input th:value="${detail.price}" name="price" required placeholder="정가 입력"></input>
</td>
</tr>
<tr>
<th>
판매가
</th>
<td>
<input th:value="${detail.salesPrice}" name="salesPrice" required placeholder="판매가 입력"></input>
</td>
</tr>
<tr>
<th>
할인 종료일
</th>
<td>
<input th:value="${detail.salesEndDt}" name="salesEndDt" required placeholder="할인 종료일 입력"></input>
</td>
</tr>
Note) 실행 결과
- 수정하기 누르고 다시 들어오면 저장한 값이 보인다.
- 데이터베이스에 잘 들어간다.
Ex) 할인 종료일
- 임플 클래스에 날짜 형식 바꾸는 메서드 getLocalDate()추가
<hide/>
private LocalDate getLocalDate(String value){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
try{
return LocalDate.parse(value, formatter);
}catch (Exception e){
}
return null;
}
- set(), add() 에 둘다 추가해서 course에 넣는다.
LocalDate saleEndDt = getLocalDate(parameter.getSaleEndDtText());
Note) 실행 결과 - 정상
13.5 강좌 전체 삭제 및 선택 삭제 구현
Ex) 강좌선택 삭제 구현하기
- 체크 박스 만들기
<hide/>
<thead>
<tr>
<th>
<input type="checkbox"/>
</th>
<th>NO</th>
<th>강좌명</th>
<th>등록일</th>
</tr>
</thead>
<tbody id ="dataList">
<tr th:each="x : ${list}">
<td>
<input type="checkbox" th:value="${x.id}" />
</td>
<td th:text="${x.seq}">1</td>
<td>
<p>
<a th:href="'edit.do?id=' + ${x.id}" th:text="${x.subject}">강좌명</a>
</p>
</td>
<td>
<p th:text="${x.regDt}">2022-01-01</p>
</td>
</tr>
</tbody>
- 버튼 삭제 자바스크립트
<hide/>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(document).ready(function (){
$('#deleteButton').on('click', function (){
var $checked = $('#dataList input[type=checkbox]:checked');
if($checked.length < 1){
alert("삭제할 데이터를 선택해주세요.")
return false;
}
if(!confirm('선택한 데이터를 삭제하겠습니까?')){ // 체크 박스를 누르고나서 선택 삭제를 누른 경우에만 실행된다.
return false;
}
var idList = [];
$.each($checked, function (k, v){
idList.push($(this).val());
});
console.log(idList);
console.log(idList.join(','));
var $deleteForm = $('form[name=deleteForm]');
$deleteForm.find('input[name=idList]').val(idList.join(','));
$deleteForm.submit(); // 서버로 보낸다.
});
});
</script>
- deleteForm에다 삭제한 정보를 채워서 course의 delete 적용시킨다.
<hide/>
<form name="deleteForm" method="post" action="/admin/course/delete.do">
<input type="hidden" name="idList"/>
</form>
- 컨트롤러에 삭제 구현
<hide/>
@PostMapping("/admin/course/delete.do")
public String del(Model model, HttpServletRequest request ,CourseInput parameter){
boolean result = courseService.del(parameter.getIdList());
return "redirect:/admin/course/list.do";
}
- courseService
<hide/>
/**
* 강좌 내용 삭제
*/
boolean del(String idList);
- 임플에 del() 구현
<hide/>
@Override
public boolean del(String idList) {
if(idList != null && idList.length() > 0){
String[] ids = idList.split(",");
for(String x : ids){
long id = 0L;
try{
id = Long.parseLong(x);
}catch (Exception e){
}
if(id > 0){
courseRepository.deleteById(id);
}
}
}
return true;
}
Note) 실행 결과
- 선택 삭제 누른 다음
Ex) 전체 삭제 구현
- 리스트에서 맨위 체크 박스에 selectAll이라는 이름을 준다.
<hide/>
<thead>
<tr>
<th>
<input id="selectAll" type="checkbox"/>
</th>
<th>NO</th>
<th>강좌명</th>
<th>등록일</th>
</tr>
</thead>
cf) .attr()과 .prop()의 차이는?
- .attr(): HTML의 속성을 취급한다.
- .prop(): JavaScript의 프로퍼티를 취급한다.
- prop() 로 체크한다.
<hide/>
<script>
$(document).ready(function (){
$('#selectAll').on('click', function (){
var checked = $(this).is(':checked');
$('#dataList input[type=checkbox]').each(function (k, v){
$(this).prop('checked', checked);
});
});
$('#deleteButton').on('click', function (){
var $checked = $('#dataList input[type=checkbox]:checked');
if($checked.length < 1){
alert("삭제할 데이터를 선택해주세요.")
return false;
}
if(!confirm('선택한 데이터를 삭제하겠습니까?')){ // 체크 박스를 누르고나서 선택 삭제를 누른 경우에만 실행된다.
return false;
}
var idList = [];
$.each($checked, function (k, v){
idList.push($(this).val());
});
console.log(idList);
console.log(idList.join(','));
var $deleteForm = $('form[name=deleteForm]');
$deleteForm.find('input[name=idList]').val(idList.join(','));
$deleteForm.submit(); // 서버로 보낸다.
});
});
</script>
Note) 실행 결과
- 맨 위 박스 누른 화면
- 선택 삭제 누른 다음 화면 => 10개가 삭제된다.
13.6 강좌 수정 화면 스마트에디터(SmartEditor) 기능 적용
Ex) 스마트 에디터(SmartEditor)
- https://github.com/naver/smarteditor2/releases 접속하여 2-2.10.0 다운로드
- 압축 해제하고 dist폴더를 static 하위로 이동시킨다.
- http://naver.github.io/smarteditor2/user_guide/2_install/setting.html 사이트의 2.0 버전 설치 탭의 코드 참고
-> 강좌 add 파일의 아래에 추가한다.
<script type="text/javascript" src="../se2/js/service/HuskyEZCreator.js" charset="utf-8"></script>
- 위 사이트의 4번 탭을 참고해서 강의 add파일에 자바스크립트 내용을 추가한다. (res앞에 슬래시 꼭 붙인다! 안 붙이면 오류난다.)
<hide/>
<script type="text/javascript" src="/res/se2/js/service/HuskyEZCreator.js" charset="utf-8"></script>
<script type="text/javascript">
var oEditors = [];
nhn.husky.EZCreator.createInIFrame({
oAppRef: oEditors,
elPlaceHolder: "contents",
sSkinURI: "/res/se2/SmartEditor2Skin.html",
fCreator: "createSEditor2"
});
</script>
- 시큐리티의 Configure() http.headers.frameOptions().sameOrigin()을 추가한다.
<hide/>
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // csrf: 토큰
http.headers().frameOptions().sameOrigin();
http.authorizeRequests()
.antMatchers(
"/"
, "/member/register"
, "/member/email-auth"
, "/member/find-password"
// , "/member/reset/password"
// , "/admin"
)
.permitAll();
http.authorizeRequests()
.antMatchers("/admin/**")
.hasAnyAuthority("ROLE_ADMIN");
http.formLogin()
.loginPage("/member/login")
.failureHandler(getFailureHandler())
.permitAll();
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
.logoutSuccessUrl("/") // 로그아웃 성공하면 메인페이지 이동
.invalidateHttpSession(true); // 로그아웃했으니 새션 초기화
// 추가 - 접근 제한
http.exceptionHandling()
.accessDeniedPage("/error/denied");
super.configure(http);
}
Note) 실행 결과
- 다음과 같이 입력 수정
'Spring Projcect > 학습 관리 시스템 & 백오피스 구축' 카테고리의 다른 글
Chapter 15. 백 오피스(강좌 신청 처리 구현) (0) | 2022.08.24 |
---|---|
Chapter 14. 스프링 부트(Spring Boot) (0) | 2022.08.24 |
Chapter 12. 카테고리 화면 (0) | 2022.08.22 |
Chapter 11. 회원 상세 및 상태 처리 (0) | 2022.08.21 |
Chapter 10. 회원 목록 (0) | 2022.08.20 |