목차 (클릭시 해당 목차로 이동)
* 현재 작성한 코드 기준이며 언제든지 코드는 바뀔 수 있습니다.
바뀐 코드는 아래 깃허브 주소에서 보실 수 있습니다.
github.com/dongho108/ClassFlix/tree/develop
dongho108/ClassFlix
회원등록, 강의정보등록, 후기등록이 가능한 웹사이트를 구축합니다. Contribute to dongho108/ClassFlix development by creating an account on GitHub.
github.com
도메인
Member
설계
Long | id | @Id, @GeneratedValue, @Column |
String | username | 필수 o |
int | age | 필수 o |
Gender | gender | 필수 o |
String | career | 필수 x |
List<Review> | reviews | @OneToMany |
코드
package dongho.classflix.domain;
import lombok.Getter;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String userName;
private int age;
@Enumerated(EnumType.STRING)
private Gender gender;
private String career;
@OneToMany(mappedBy = "member")
private List<Review> reviews = new ArrayList<>();
protected Member() {
}
public Member(String userName, int age, Gender gender) {
this.userName = userName;
this.age = age;
this.gender = gender;
}
public Member(String userName, int age, Gender gender, String career) {
this.userName = userName;
this.age = age;
this.gender = gender;
this.career = career;
}
}
- 필수인 것들을 저장하는 생성자와 필수가 아닌 것들도 저장하는 생성자 두개
(나중에 실제로 쓰이는지 확인 후 수정 필요)
Lecture
설계
Long | id | @Id, @GeneraedValue |
String | lectureName | 필수 o |
String | teacherName | 필수 o |
String | content | 필수 o |
byte[] | representImage | 필수 x |
String | siteName | 필수 x |
URI | uri | 필수 x |
double | averageRating | |
int | reviewNum | |
List<Review> | reviews | @OneToMany, cascade |
- lecture에 reivew 추가 로직
- lecture에 reivew 삭제 로직
- 평균 리뷰 별점 계산 로직
- 강의 수정을 위한 change로직
- 필수, 필수 아닌것들 생성자 따로 (후에 수정 필요할지도)
코드
package dongho.classflix.domain;
import lombok.Getter;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import java.awt.*;
import java.net.URI;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static javax.persistence.CascadeType.*;
@Entity
@Getter
public class Lecture {
@Id
@GeneratedValue
@Column(name = "lecture_id")
private Long id;
private String lectureName;
private String teacherName;
private String content;
private byte[] representImage;
private String siteName;
private URI uri;
private LocalDateTime lectureDateTime;
private double averageRating;
private int reviewNum;
@OneToMany(mappedBy = "lecture", cascade = ALL)
private List<Review> reviews = new ArrayList<>();
protected Lecture() {
}
public Lecture(String lectureName, String teacherName, String content, LocalDateTime lectureDateTime) {
this.lectureName = lectureName;
this.teacherName = teacherName;
this.content = content;
this.lectureDateTime = lectureDateTime;
}
public Lecture(String lectureName, String teacherName, String content, byte[] representImage, String siteName, URI uri) {
this.lectureName = lectureName;
this.teacherName = teacherName;
this.content = content;
this.representImage = representImage;
this.siteName = siteName;
this.uri = uri;
}
public void addReview(Integer rating) {
this.reviewNum += 1;
updateAverageRating(rating);
}
public void removeReview(Integer rating) {
int restReview = this.reviewNum - 1;
if (restReview < 0) {
throw new NotEnoughReviewException("review is empty");
}
this.reviewNum -= 1;
updateAverageRating(rating);
}
public void updateAverageRating(Integer rating) {
if (reviewNum == 0) {
this.averageRating = 0;
} else {
double average = (averageRating + rating) / reviewNum;
this.averageRating = Math.floor(average);
}
}
public void changeLectureData(String lectureName, String teacherName, String content, byte[] representImage, String siteName, URI uri) {
this.lectureName = lectureName;
this.teacherName = teacherName;
this.content = content;
this.representImage = representImage;
this.siteName = siteName;
this.uri = uri;
}
}
테스트코드
package dongho.classflix.domain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class LectureTest {
@Autowired
EntityManager em;
// 리뷰 개수와 평균별점
@Test
public void 강의의리뷰개수추가() throws Exception {
//given
Member member = new Member("dongho", 25, Gender.MALE);
em.persist(member);
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
em.persist(lecture);
Review review1 = new Review(member, "1234", "good", 4, lecture, LocalDateTime.now());
em.persist(review1);
Review review2 = new Review(member, "1234", "bad", 1, lecture, LocalDateTime.now());
em.persist(review2);
//when
lecture.addReview(review1.getRating());
lecture.addReview(review2.getRating());
//then
Assertions.assertThat(lecture.getReviewNum()).isEqualTo(2);
}
@Test
public void 강의의리뷰개수감소() throws Exception {
//given
Member member = new Member("dongho", 25, Gender.MALE);
em.persist(member);
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
em.persist(lecture);
Review review1 = new Review(member, "1234", "good", 4, lecture, LocalDateTime.now());
em.persist(review1);
Review review2 = new Review(member, "1234", "bad", 1, lecture, LocalDateTime.now());
em.persist(review2);
//when
lecture.addReview(review1.getRating());
lecture.addReview(review2.getRating());
lecture.removeReview(review1.getRating());
lecture.removeReview(review2.getRating());
//then
Assertions.assertThat(lecture.getReviewNum()).isEqualTo(0);
assertThrows(NotEnoughReviewException.class, () -> {
lecture.removeReview(1);
});
}
@Test
public void 강의평균별점() throws Exception {
//given
Member member = new Member("dongho", 25, Gender.MALE);
em.persist(member);
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
em.persist(lecture);
Review review1 = new Review(member, "1234", "good", 4, lecture, LocalDateTime.now());
em.persist(review1);
Review review2 = new Review(member, "1234", "bad", 1, lecture, LocalDateTime.now());
em.persist(review2);
//when
lecture.addReview(review1.getRating());
lecture.addReview(review2.getRating());
//then
Assertions.assertThat(lecture.getAverageRating()).isEqualTo(2);
}
}
Review
설계
Long | id | @Id, @GeneraedValue |
Member | member | @ManyToOne |
String | password | |
String | content | |
Integer | rating | |
Lecture | lecture | @ManyToOne |
LocalDateTime | reviewDate |
- 리뷰수정이 가능하도록 content, rating 수정 메소드
코드
package dongho.classflix.domain;
import lombok.Getter;
import javax.persistence.*;
import javax.swing.*;
import javax.validation.constraints.NotEmpty;
import java.time.LocalDateTime;
import static javax.persistence.FetchType.*;
@Entity
@Getter
public class Review {
@Id
@GeneratedValue
@Column(name = "review_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
private String password;
private String content;
private Integer rating;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "lecture_id")
private Lecture lecture;
private LocalDateTime reviewDate;
protected Review() {
}
public Review(Member member, String password, String content, Integer rating, Lecture lecture, LocalDateTime reviewDate) {
this.member = member;
this.password = password;
this.content = content;
this.rating = rating;
this.lecture = lecture;
this.reviewDate = reviewDate;
}
public void changeContentAndRating(String content, Integer rating) {
this.content = content;
this.rating = rating;
}
}
리포지토리
Member
설계
- 회원등록
- 회원 Id로 조회
- 회원 이름으로 조회 -> 검색, 중복확인용
- 회원 전부 조회
코드
package dongho.classflix.repository;
import dongho.classflix.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
// 회원등록
public Long save(Member member) {
em.persist(member);
return member.getId();
}
// 회원 조회
public Member findById(Long id) {
return em.find(Member.class, id);
}
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.userName = :name", Member.class)
.setParameter("name", name)
.getResultList();
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}
테스트코드
package dongho.classflix.repository;
import dongho.classflix.domain.Gender;
import dongho.classflix.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
@SpringBootTest
@Transactional
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
@Test
public void 회원등록() throws Exception {
//given
Member member = new Member("dongho", 25, Gender.MALE);
//when
Long savedId = memberRepository.save(member);
//then
Assertions.assertThat(member).isEqualTo(memberRepository.findById(savedId));
}
}
테스트코드
package dongho.classflix.repository;
import dongho.classflix.domain.Gender;
import dongho.classflix.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
@SpringBootTest
@Transactional
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
@Test
public void 회원등록() throws Exception {
//given
Member member = new Member("dongho", 25, Gender.MALE);
//when
Long savedId = memberRepository.save(member);
//then
Assertions.assertThat(member).isEqualTo(memberRepository.findById(savedId));
}
}
Lecture
설계
- 강의 등록
- 강의 Id 조회
- 강의 이름 조회 -> 검색, 중복확인용
- 강의 전체 조회
코드
package dongho.classflix.repository;
import dongho.classflix.domain.Lecture;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class LectureRepository {
private final EntityManager em;
// 강의 등록
public Long save(Lecture lecture) {
em.persist(lecture);
return lecture.getId();
}
// 강의 조회
public Lecture findById(Long id) {
return em.find(Lecture.class, id);
}
public List<Lecture> findAll() {
return em.createQuery("select l from Lecture l", Lecture.class)
.getResultList();
}
public List<Lecture> findByName(String name) {
return em.createQuery("select l from Lecture l where l.lectureName = :name", Lecture.class)
.setParameter("name", name)
.getResultList();
}
}
테스트코드
package dongho.classflix.repository;
import dongho.classflix.domain.Lecture;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class LectureRepositoryTest {
@Autowired
LectureRepository lectureRepository;
@Autowired
EntityManager em;
@Test
public void 강의저장조회() throws Exception {
//given
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
//when
Long savedId = lectureRepository.save(lecture);
//then
Assertions.assertThat(lecture).isEqualTo(lectureRepository.findById(savedId));
}
}
Review
설계
- 리뷰 저장
- 리뷰 id로 조회
- 리뷰 전체 조회
- 강의에 달린 리뷰 조회
코드
package dongho.classflix.repository;
import dongho.classflix.domain.Review;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class ReviewRepository {
private final EntityManager em;
// 리뷰 저장
public Long save(Review review) {
em.persist(review);
return review.getId();
}
// 리뷰조회
public Review findById(Long id) {
return em.find(Review.class, id);
}
public List<Review> findAll() {
return em.createQuery("select r from Review r", Review.class)
.getResultList();
}
// 강의에 달린 리뷰
public List<Review> findAllWithMemberLecture() {
return em.createQuery(
"select r from Review r" +
" join fetch r.lecture l", Review.class
).getResultList();
}
}
테스트코드
package dongho.classflix.repository;
import dongho.classflix.domain.*;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.swing.*;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class ReviewRepositoryTest {
@Autowired
EntityManager em;
@Autowired
ReviewRepository reviewRepository;
@Autowired
MemberRepository memberRepository;
@Autowired
LectureRepository lectureRepository;
@Test
public void 리뷰저장조회() throws Exception {
//given
Member member = new Member("dongho", 25, Gender.MALE);
em.persist(member);
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
em.persist(lecture);
Review review = new Review(member, "1234", "good", 4, lecture, LocalDateTime.now());
//when
Long savedId = reviewRepository.save(review);
//then
Assertions.assertThat(review).isEqualTo(reviewRepository.findById(savedId));
}
}
서비스
Member
설계
- 회원가입(중복회원은 거부)
- 회원조회
코드
package dongho.classflix.service;
import dongho.classflix.domain.Member;
import dongho.classflix.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* 회원가입
*/
public Long join(Member member) {
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
// 회원 전체 조회
@Transactional(readOnly = true)
public List<Member> findMembers() {
return memberRepository.findAll();
}
// 회원 하나 조회
@Transactional(readOnly = true)
public Member findOne(Long memberId) {
return memberRepository.findById(memberId);
}
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getUserName());
if (!findMembers.isEmpty()) {
throw new IllegalArgumentException("이미 존재하는 회원입니다.");
}
}
}
테스트코드
package dongho.classflix.service;
import dongho.classflix.domain.Gender;
import dongho.classflix.domain.Member;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class MemberServiceTest {
@Autowired
EntityManager em;
@Autowired
MemberService memberService;
@Test
public void 중복회원예외() throws Exception {
//given
Member member1 = new Member("dongho", 25, Gender.MALE);
Member member2 = new Member("dongho", 28, Gender.MALE);
//when
memberService.join(member1);
//then
assertThrows(IllegalStateException.class, () -> {
memberService.join(member2);
});
}
}
Lecture
설계
- 강의등록(중복강의거부 : 강의이름, 강의자이름 두개로)
- 강의업데이트
코드
package dongho.classflix.service;
import dongho.classflix.domain.Lecture;
import dongho.classflix.repository.LectureRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URI;
import java.util.List;
@Transactional
@Service
@RequiredArgsConstructor
public class LectureService {
private final LectureRepository lectureRepository;
// 조인
public Long join(Lecture lecture) {
validateDuplicateLecture(lecture);
return lectureRepository.save(lecture);
}
private void validateDuplicateLecture(Lecture lecture) {
List<Lecture> findLectures = lectureRepository.findByName(lecture.getLectureName(), lecture.getTeacherName());
if (!findLectures.isEmpty()) {
throw new IllegalStateException("이미 존재하는 강의입니다.");
}
}
// 업데이트
public void update(Long id, LectureDto lectureDto) {
Lecture findLecture = lectureRepository.findById(id);
findLecture.changeLectureData(lectureDto.getLectureName(), lectureDto.getTeacherName(), lectureDto.getContent(), lectureDto.getRepresentImage(), lectureDto.getSiteName(), lectureDto.getUri());
}
}
테스트코드
package dongho.classflix.service;
import dongho.classflix.domain.Lecture;
import dongho.classflix.repository.LectureRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class LectureServiceTest {
@Autowired
EntityManager em;
@Autowired
LectureService lectureService;
@Autowired
LectureRepository lectureRepository;
@Test
public void 중복강의거부() throws Exception {
//given
Lecture lecture1 = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
Lecture lecture2 = new Lecture("jpa", "김영한", "jpa강의입니다.", LocalDateTime.now());
//when
lectureService.join(lecture1);
//then
assertThrows(IllegalStateException.class, () -> {
lectureService.join(lecture2);
});
}
// 강의 업데이트
@Test
public void 강의수정() throws Exception {
//given
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
em.persist(lecture);
LectureDto lectureDto = new LectureDto();
lectureDto.setContent("data jpa 강의");
//when
lectureService.update(lecture.getId(), lectureDto);
//then
Assertions.assertThat(lecture.getContent()).isEqualTo("data jpa 강의");
}
}
Review
설계
- 리뷰 하나(id) 조회
- 리뷰 전체 조회
- 리뷰 수정 (비밀번호 매칭)
코드
package dongho.classflix.service;
import dongho.classflix.domain.Lecture;
import dongho.classflix.domain.Member;
import dongho.classflix.domain.Review;
import dongho.classflix.repository.LectureRepository;
import dongho.classflix.repository.MemberRepository;
import dongho.classflix.repository.ReviewRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
@RequiredArgsConstructor
public class ReviewService {
private final ReviewRepository reviewRepository;
private final MemberRepository memberRepository;
private final LectureRepository lectureRepository;
// 리뷰 등록
public Long create(Long memberId, Long lectureId, ReviewDto reviewDto) {
Member findMember = memberRepository.findById(memberId);
Lecture findLecture = lectureRepository.findById(lectureId);
Review newReview = new Review(findMember, reviewDto.getPassword(), reviewDto.getContent(), reviewDto.getRating(), findLecture, reviewDto.getReviewDate());
reviewRepository.save(newReview);
return newReview.getId();
}
// 하나 조회
@Transactional(readOnly = true)
public Review findOne(Long reviewId) {
return reviewRepository.findById(reviewId);
}
// 전체 조회
@Transactional(readOnly = true)
public List<Review> findAll() {
return reviewRepository.findAll();
}
// 리뷰 수정
public Long update(Long reviewId, String password, String content, Integer rating) {
Review findReview = reviewRepository.findById(reviewId);
if (!findReview.getPassword().equals(password)) {
throw new NotEqualPasswordException("password is wrong");
}
findReview.changeContentAndRating(content, rating);
return reviewId;
}
}
테스트코드
package dongho.classflix.service;
import dongho.classflix.domain.Lecture;
import dongho.classflix.repository.LectureRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class LectureServiceTest {
@Autowired
EntityManager em;
@Autowired
LectureService lectureService;
@Autowired
LectureRepository lectureRepository;
@Test
public void 중복강의거부() throws Exception {
//given
Lecture lecture1 = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
Lecture lecture2 = new Lecture("jpa", "김영한", "jpa강의입니다.", LocalDateTime.now());
//when
lectureService.join(lecture1);
//then
assertThrows(IllegalStateException.class, () -> {
lectureService.join(lecture2);
});
}
// 강의 업데이트
@Test
public void 강의수정() throws Exception {
//given
Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
em.persist(lecture);
LectureDto lectureDto = new LectureDto();
lectureDto.setContent("data jpa 강의");
//when
lectureService.update(lecture.getId(), lectureDto);
//then
Assertions.assertThat(lecture.getContent()).isEqualTo("data jpa 강의");
}
}
테스트 코드 실행 결과
'Project > ClassFlix' 카테고리의 다른 글
[ClassFlix] EP 6. view 페이지 제작과 컨트롤러 연결 - 2 (0) | 2021.04.29 |
---|---|
[ClassFlix] EP 5. view 페이지 제작과 컨트롤러 연결 - 1 (0) | 2021.04.27 |
[ClassFlix] EP 3. 도메인 설계와 테이블 생성 (0) | 2021.04.21 |
[ClassFlix] EP 2. 개발환경 세팅 (0) | 2021.04.21 |
[ClassFlix] EP 1. 개발 계획 (0) | 2021.04.14 |