properties
server.port=8085
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/team16
spring.datasource.username=root
spring.datasource.password=1234
logging.level.com.pro06=info
logging.level.org.hibernate.type.descriptor.sql=trace
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
modelMapper
package com.pro06.config;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
.setFieldMatchingEnabled(true)
.setMatchingStrategy(MatchingStrategies.STRICT);
return modelMapper;
}
}
DTO
package com.pro06.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.*;
@Getter @Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CourseDto extends BaseDto {
private Integer no;
@Size(max = 20)
@NotBlank
private String id;
@Size(max = 10)
@NotBlank
private String level;
@Size(max = 100)
@NotBlank
private String title;
@Size(max = 2000)
@NotNull
private String content;
@NotNull
private Integer cnt;
@NotNull
private Integer peo;
@NotBlank
private Integer peo_max;
}
Entity
package com.pro06.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
@Entity
@Getter
@Table(name="course")
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
public class Course extends BaseEntity{
@Id
@Column(name = "no")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer no;
@Column(length = 20, nullable = false)
private String id;
@Column(length = 10, nullable = false)
private String level;
@Column(length = 100, nullable = false)
private String title;
@Column(length = 2000, nullable = false)
private String content;
@ColumnDefault("0")
private Integer cnt;
@ColumnDefault("0")
private Integer peo;
@Column(nullable = false)
private Integer peo_max;
public void peoUp(){
this.peo = this.peo + 1;
}
public void change(String id, String level,
String title, String content) {
this.id = id;
this.level = level;
this.title = title;
this.content = content;
}
}
Repository
package com.pro06.repository.course;
import com.pro06.entity.Course;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CourseRepository extends JpaRepository<Course, Integer> {
@Query("select c from Course c where c.peo < c.peo_max")
List<Course> courseList();
}
Service
package com.pro06.service.course;
import com.pro06.dto.CourseDto;
import com.pro06.entity.Course;
import com.pro06.repository.course.CourseRepository;
import jakarta.transaction.Transactional;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Transactional
public class CourseServiceImpl {
@Autowired
private CourseRepository courseRepository;
@Autowired
private ModelMapper modelMapper;
public void courseInsert(CourseDto courseDto) {
Course course = modelMapper.map(courseDto, Course.class);
courseRepository.save(course);
}
public void courseUpdate(CourseDto courseDto) {
Optional<Course> course = courseRepository.findById(courseDto.getNo());
Course res = course.orElseThrow();
res.change(courseDto.getId(), courseDto.getLevel(),
courseDto.getTitle(), courseDto.getContent());
courseRepository.save(res);
}
public void courseDelete(Integer no) {
courseRepository.deleteById(no);
}
public List<CourseDto> admCourseList() {
List<Course> lst = courseRepository.findAll();
List<CourseDto> courseList = lst.stream().map(course ->
modelMapper.map(course, CourseDto.class))
.collect(Collectors.toList());
return courseList;
}
public List<CourseDto> courseList() {
List<Course> lst = courseRepository.courseList();
List<CourseDto> courseList = lst.stream().map(course ->
modelMapper.map(course, CourseDto.class))
.collect(Collectors.toList());
return courseList;
}
public CourseDto getCourse(Integer no) {
Optional<Course> course = courseRepository.findById(no);
CourseDto courseDto = modelMapper.map(course, CourseDto.class);
return courseDto;
}
public void setCoursePeo(Integer no) {
Optional<Course> course = courseRepository.findById(no);
Course res = course.orElseThrow();
res.peoUp();
courseRepository.save(res);
}
}
Controller
package com.pro06.controller.course;
import com.pro06.dto.CourseDto;
import com.pro06.dto.LectureDto;
import com.pro06.dto.MyCourseDto;
import com.pro06.service.UserService;
import com.pro06.service.course.CourseServiceImpl;
import com.pro06.service.course.LectureServiceImpl;
import com.pro06.service.course.MyCourseServiceImpl;
import lombok.extern.log4j.Log4j2;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
@Controller
@Log4j2
@RequestMapping("/course/*")
public class CourseController {
@Autowired
private CourseServiceImpl courseService;
@Autowired
private LectureServiceImpl lectureService;
@Autowired
private MyCourseServiceImpl myCourseService;
@Autowired
private UserService userService;
@GetMapping("list")
public String courseList(Principal principal, Model model) {
List<CourseDto> courseList = courseService.courseList();
List<String> checkList = new ArrayList<>();
// 로그인한 사람이면 강좌마다 수강신청 했는지 안했는지 검사
if(principal != null) {
String id = principal.getName();
for (CourseDto course: courseList) {
Integer cnt = myCourseService.getMyCourseCnt(id, course.getNo());
if(cnt > 0) {
checkList.add("y");
} else {
checkList.add("n");
}
}
}
model.addAttribute("checkList", checkList);
model.addAttribute("courseList", courseList);
return "course/courseList";
}
// 강좌 상세
@GetMapping("detail")
public String courseDetail(@RequestParam("no") Integer no, Model model) throws IOException {
// 강좌 상세
CourseDto course = courseService.getCourse(no);
model.addAttribute("course", course);
// 강의 목록
List<LectureDto> lectureList = lectureService.lectureCnoList(no);
model.addAttribute("lectureList", lectureList);
return "course/courseDetail";
}
// 강좌 수강신청
@GetMapping("apply")
public String courseApply(Principal principal, @RequestParam("cno") Integer cno) {
if(principal == null) {
log.error("로그인을 해야 수강신청을 할 수 있습니다.");
return "redirect:/";
}
String id = principal.getName();
Integer ck = myCourseService.getMyCourseCnt(id, cno);
if(ck > 0) {
log.error("이미 해당 강의를 수강하고 있습니다.");
return "redirect:/";
}
// 수강 신청 하기전에 인원이 다 찼는지 안 찼는지 확인
CourseDto course1 = courseService.getCourse(cno);
if(course1.getPeo() >= course1.getPeo_max() ) {
log.error("이미 수강생이 다 찼습니다.");
return "redirect:/";
}
CourseDto course = new CourseDto();
course.setNo(cno);
MyCourseDto myCourse = new MyCourseDto();
myCourse.setId(id);
myCourse.setCourse(course);
courseService.setCoursePeo(cno);
myCourseService.myCourseInsert(myCourse);
return "redirect:/"; // 인덱스 이동
}
}
view, thymeleaf
detail
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>강좌 상세</title>
<th:block th:replace="include/head :: head" ></th:block>
</head>
<body>
<th:block th:replace="include/header :: header" ></th:block>
<section class="table-list">
<div class="container">
<h2>강좌상세</h2>
<ul>
<li>강좌 이름 : [[${course.title}]]</li>
<li>작성자 : [[${course.id}]]</li>
<li>현재수강인원 : [[${course.peo}]]</li>
<li>최대수강인원 : [[${course.peo_max}]]</li>
</ul>
<h3>강의목록</h3>
<div class="search_from" style="text-align: center">
<select name="select_filter" class="select_filter">
<option value="1">제목</option>
</select>
<input type="text" name="search_filter" class="search_filter">
</div>
<table id="myTable" class="List-table">
<thead>
<tr>
<th>No</th>
<th>제목</th>
</tr>
</thead>
<tbody>
<th:block th:each="lecture, status : ${lectureList}">
<tr>
<td th:text="${status.count}"></td>
<td th:text="${lecture.title}"></td>
</tr>
</th:block>
</tbody>
</table>
</div>
</section>
<th:block th:replace="include/footer :: footer" ></th:block>
</body>
</html>
list
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>강좌목록</title>
<th:block th:replace="include/head :: head" ></th:block>
</head>
<body>
<th:block th:replace="include/header :: header" ></th:block>
<section class="table-list">
<div class="container">
<h2>강좌목록</h2>
<div class="search_from">
<select name="select_filter" class="select_filter">
<option value="1">제목</option>
<option value="2">학년</option>
<option value="4">등록일</option>
</select>
<input type="text" name="search_filter" class="search_filter">
</div>
<table id="myTable" class="List-table">
<thead>
<tr>
<th>No</th>
<th>제목</th>
<th>학년</th>
<th>수강현황</th>
<th>등록일</th>
<th></th>
</tr>
</thead>
<tbody>
<th:block th:each="course, status : ${courseList}">
<tr>
<td th:text="${status.count}"></td>
<td><a th:href="@{/course/detail(no=${course.no})}" th:text="${course.title}"></a></td>
<td th:text="${course.level}"></td>
<td th:text="${course.peo} + '/' + ${course.peo_max}"></td>
<td th:text="${#temporals.format(course.getCreatedTime(), 'yyyy-MM-dd hh:mm')}"></td>
<td>
<th:block sec:authorize="isAuthenticated()">
<th:block th:if="${checkList.get(status.index)} eq 'n'">
<a th:href="@{/course/apply(cno=${course.no})}">수강신청</a>
</th:block>
</th:block>
</td>
</tr>
</th:block>
</tbody>
</table>
</div>
</section>
<th:block th:replace="include/footer :: footer" ></th:block>
</body>
</html>
insert
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>강좌등록</title>
<th:block th:replace="/include/adminhead :: head"></th:block>
</head>
<body>
<div class="container-scroller">
<div class="container-fluid page-body-wrapper">
<th:block th:replace="/include/adminheader :: header"></th:block>
<div class="main-panel">
<div class="content-wrapper">
<h1>관리자페이지</h1>
<h2>강좌등록</h2>
<div class="card p-3">
<form th:action="@{/admin/courseInsert}" method="post" class="text-center mb-3 was-validated">
<div class="form-group">
<label for="id" class="form-label">아이디</label>
<input type="text" name="id" id="id" class="form-control" th:value="${id}" readonly>
</div>
<div class="form-group">
<label for="level" class="form-label">학년</label>
<select name="level" id="level" class="custom-select">
<option value="초등" selected>초등</option>
<option value="중학">중학</option>
<option value="수능">수능</option>
</select>
</div>
<!-- form-control을 적용한 input 바로 밑에
valid-feedback, invalid-feedback를 적용 시켜주면 주어진 pattern에 따라 검사를 진행한다. -->
<div class="form-group">
<label for="title" class="form-label" >제목</label>
<input type="text" name="title" id="title" class="form-control"
pattern="^[ㄱ-ㅎ가-힣0-9a-zA-Z\s]*$" maxlength="100" required>
<div class="valid-feedback">
형식에 알맞습니다.
</div>
<div class="invalid-feedback">
한글, 숫자, 영어대소문자만 가능합니다.
</div>
</div>
<div class="form-group">
<label for="content" class="form-label">내용</label>
<input type="text" name="content" id="content" class="form-control"
pattern="^[ㄱ-ㅎ가-힣0-9a-zA-Z\s]*$" maxlength="1000" required>
<div class="valid-feedback">
형식에 알맞습니다.
</div>
<div class="invalid-feedback">
한글, 숫자, 영어대소문자만 가능합니다.
</div>
</div>
<!-- 수강 최대 인원은 1~99 명까지 가능 -->
<div class="form-group">
<label for="peo_max" class="form-label">최대 수강인원</label>
<input type="text" name="peo_max" id="peo_max" class="form-control" value="10"
pattern="[1-9]|[1-9][0-9]" maxlength="2" required>
<div class="valid-feedback">
형식에 알맞습니다.
</div>
<div class="invalid-feedback">
1 ~ 99명 까지만 가능합니다.
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="등록">
<input type="reset" class="btn btn-danger" value="초기화">
</div>
</form>
</div>
</div>
<footer class="footer">
<div class="d-sm-flex justify-content-center justify-content-sm-between">
<span class="text-muted text-center text-sm-left d-block d-sm-inline-block">Copyright © 2021. Premium <a href="https://www.bootstrapdash.com/" target="_blank">Bootstrap admin template</a> from BootstrapDash. All rights reserved.</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Hand-crafted & made with <i class="ti-heart text-danger ml-1"></i></span>
</div>
</footer>
</div>
</div>
</div>
</body>
</html>
update
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>강좌수정</title>
<th:block th:replace="/include/adminhead :: head"></th:block>
</head>
<body>
<div class="container-scroller">
<div class="container-fluid page-body-wrapper">
<th:block th:replace="/include/adminheader :: header"></th:block>
<div class="main-panel">
<div class="content-wrapper">
<h1>관리자페이지</h1>
<h2>강좌수정</h2>
<div class="card p-3">
<form th:action="@{/admin/courseUpdate}" method="post" class="text-center mb-3 was-validated">
<div class="form-group">
<input type="hidden" name="no" id="no" th:value="${dto.no}">
</div>
<div class="form-group">
<label for="id">아이디</label>
<input type="text" name="id" id="id" class="form-control" th:value="${dto.id}" readonly>
</div>
<div class="form-group">
<label for="level">학년</label>
<select name="level" id="level" class="custom-select">
<th:block th:if="${dto.level eq '초등'}">
<option value="초등" selected>초등</option>
</th:block>
<th:block th:if="${dto.level ne '초등'}">
<option value="초등">초등</option>
</th:block>
<th:block th:if="${dto.level eq '중학'}">
<option value="중학" selected>중학</option>
</th:block>
<th:block th:if="${dto.level ne '중학'}">
<option value="중학">중학</option>
</th:block>
<th:block th:if="${dto.level eq '수능'}">
<option value="수능" selected>수능</option>
</th:block>
<th:block th:if="${dto.level ne '수능'}">
<option value="수능">수능</option>
</th:block>
</select>
</div>
<div class="form-group">
<label for="title">제목</label>
<input type="text" name="title" id="title" class="form-control" pattern="^[ㄱ-ㅎ가-힣0-9a-zA-Z\s]*$"
maxlength="100" th:value="${dto.title}" required>
<div class="valid-feedback">
형식에 알맞습니다.
</div>
<div class="invalid-feedback">
한글, 숫자, 영어대소문자만 가능합니다.
</div>
</div>
<div class="form-group">
<label for="content">내용</label>
<input type="text" name="content" id="content" class="form-control" pattern="^[ㄱ-ㅎ가-힣0-9a-zA-Z\s]*$"
maxlength="1000" th:value="${dto.content}" required>
<div class="valid-feedback">
형식에 알맞습니다.
</div>
<div class="invalid-feedback">
한글, 숫자, 영어대소문자만 가능합니다.
</div>
</div>
<div class="form-group">
<label for="peo_max">최대 수강인원</label>
<input type="text" name="peo_max" id="peo_max" value="10" class="form-control" maxlength="2" readonly>
</div>
<div>
<input type="submit" class="btn btn-primary" value="등록">
<input type="reset" class="btn btn-danger" value="초기화">
</div>
</form>
</div>
</div>
<footer class="footer">
<div class="d-sm-flex justify-content-center justify-content-sm-between">
<span class="text-muted text-center text-sm-left d-block d-sm-inline-block">Copyright © 2021. Premium <a href="https://www.bootstrapdash.com/" target="_blank">Bootstrap admin template</a> from BootstrapDash. All rights reserved.</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Hand-crafted & made with <i class="ti-heart text-danger ml-1"></i></span>
</div>
</footer>
</div>
</div>
</div>
</body>
</html>