|
| 1 | +# 프로젝트 설계 중점 |
| 2 | + |
| 3 | +늘어나는 문제의 성격과 분야를 염두한 확장성, 유지보수성 높은 설계 |
| 4 | + |
| 5 | +# 설계 방법 |
| 6 | + |
| 7 | +### 1. Entity간의 상속 관계를 DB에서 구현시 Join 전략을 사용 |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +### 2. QueryDsl을 사용 |
| 12 | + |
| 13 | +#### 공통된 조건으로 조회시에도 문제마다 중복되는 쿼리를 작성해야하는 문제점 |
| 14 | + |
| 15 | +- **전공 객관식** 문제를 카테고리로 조회 |
| 16 | + |
| 17 | +```java |
| 18 | + |
| 19 | +@Query("SELECT DISTINCT nq FROM MajorMultipleChoiceQuestion nq " |
| 20 | + + "LEFT JOIN FETCH nq.questionChoices " |
| 21 | + + "WHERE nq.questionCategory IN :questionCategories ") |
| 22 | +List<MajorMultipleChoiceQuestion> findAllByQuestionCategoriesFetchChoices( |
| 23 | + @Param("questionCategories") List<QuestionCategory> questionCategories |
| 24 | +); |
| 25 | +``` |
| 26 | + |
| 27 | +- **자격증 객관식** 문제를 카테고리로 조회 |
| 28 | + |
| 29 | +```java |
| 30 | + |
| 31 | +@Query("SELECT DISTINCT lnq FROM LicenseMultipleChoiceQuestion lnq " |
| 32 | + + "LEFT JOIN FETCH lnq.questionChoices " |
| 33 | + + "WHERE lnq.questionCategory IN :questionCategories ") |
| 34 | +List<LicenseMultipleChoiceQuestion> findAllByQuestionCategoriesFetchChoices( |
| 35 | + @Param("questionCategories") List<QuestionCategory> questionCategories); |
| 36 | +``` |
| 37 | + |
| 38 | + |
| 39 | +#### QueryDsl로 해결 |
| 40 | +- 문제를 조회하는 **공통된 조건** (조회할 테이블에 Where 조건을 적용) |
| 41 | + |
| 42 | +```java |
| 43 | +public interface SelectWhereCategoriesAndLevels<T extends Question> { |
| 44 | + |
| 45 | + default JPAQuery<T> selectWhereQuestionCategories(List<QuestionCategory> questionCategories) { |
| 46 | + return getQuestions() |
| 47 | + .where( |
| 48 | + whereCategories( |
| 49 | + getQuestionQClass(), |
| 50 | + questionCategories) |
| 51 | + ); |
| 52 | + } |
| 53 | + |
| 54 | + //구현체에서 Select할 실제 문제 테이블을 따로 구현 |
| 55 | + JPAQuery<T> getQuestions(); |
| 56 | + |
| 57 | +``` |
| 58 | +- 실제 구현체에서는 **조회할 테이블**만을 설정해준다. |
| 59 | +```java |
| 60 | +@Override |
| 61 | + public JPAQuery<LicenseMultipleChoiceQuestion> getQuestions() { |
| 62 | + return jpaQueryFactory.selectFrom( |
| 63 | +QLicenseMultipleChoiceQuestion.licenseMultipleChoiceQuestion); |
| 64 | + } |
| 65 | +``` |
| 66 | +- Repository에서 검색 조건을 정의하는 인터페이스를 상속하여 문제의 종류마다 어떤 조건으로 검색될 수 있는지도 쉽게 알 수 있음 |
| 67 | +```java |
| 68 | +@Repository |
| 69 | +public class LicenseMultipleChoiceQuestionDslRepository |
| 70 | + extends QueryDslJpaQueryMaker<LicenseMultipleChoiceQuestion> |
| 71 | + implements SelectWhereCategoriesAndLevels<LicenseMultipleChoiceQuestion>, |
| 72 | + SelectWhereContent~~(추가될 수 있는 검색 조건) |
| 73 | +``` |
| 74 | + |
| 75 | + |
| 76 | +### 3. 문제의 성격에 따라 인터페이스를 상속하고 행동을 정의 |
| 77 | +- **객관식 문제**의 공통된 행동 정의 |
| 78 | +```java |
| 79 | +public interface ChoiceBehavior { |
| 80 | + List<? extends QuestionChoice> getQuestionChoices(); |
| 81 | +} |
| 82 | +``` |
| 83 | +```java |
| 84 | +public class LicenseMultipleChoiceQuestion extends Question implements ChoiceBehavior |
| 85 | + |
| 86 | +@OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) |
| 87 | +private List<LicenseQuestionChoice> questionChoices; |
| 88 | +//LicenseQuestionChoice, MajorQuestionChoice를 반환하도록 구현 |
| 89 | +@Override |
| 90 | + public List<LicenseQuestionChoice> getQuestionChoices() { |
| 91 | + return questionChoices; |
| 92 | + } |
| 93 | +``` |
| 94 | +- **객관식 문제**의 공통된 Response Dto에서의 사용 |
| 95 | +```java |
| 96 | +public static <Q extends Question & ChoiceBehavior> |
| 97 | +ResponseMultipleChoiceQuestionDto from(Q question) { |
| 98 | + List<ResponseQuestionChoiceDto> questionChoices = |
| 99 | + question.getQuestionChoices() |
| 100 | + .stream() |
| 101 | + ... |
| 102 | + } |
| 103 | +``` |
0 commit comments