
@Entity // JPA가 Entity로 인식
@Getter
@Setter
public class Question(){
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // 자동증가
private Integer id;
@Column(length = 200) // 길이 제한
private String subject;
@Column(columnDefinition = "TEXT") // 글자수 제한 x
private String content;
private LocalDateTime createDate; // 작성날짜
@OntToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
// 질문객체에서 답변을 참조할 시 question.answerList()를 호출하면된다
// mappedBy는 참조 Entity 속성을 의미한다
// CascadeType.REMOVE : 질문 하나에는 여러개의 답변이 작성가능이 된다 이때 질문을 삭제하면 그에 달린 답변들도 모두 함께 삭제가 되는 속성이다
}
@SpringBootTest
class SbbApplicationTests {
@Autowired
private QuestionRepository questionRepository;
@Test
void testJpa() {
Question q1 = new Question();
q1.setSubject("sbb가 무엇인가요?");
q1.setContent("sbb에 대해서 알고 싶습니다.");
q1.setCreateDate(LocalDateTime.now());
this.questionRepository.save(q1); // 첫번째 질문 저장
Question q2 = new Question();
q2.setSubject("스프링부트 모델 질문입니다.");
q2.setContent("id는 자동으로 생성되나요?");
q2.setCreateDate(LocalDateTime.now());
this.questionRepository.save(q2); // 두번째 질문 저장
}
}
// @SpringBootTest 애너테이션은 SbbApplicationTests 클래스가 스프링부트 테스트 클래스임을 의미한다.
// @Autowired 애너테이션은 스프링의 DI 기능으로 questionRepository 객체를 스프링이 자동으로 생성해 준다.
그러면 db에서 조회하면 질문이 생긴것을 볼수가 있다
db에 질문이 있는 것을 이제는 html로 보이게 하기 전에 controller를 작성해서 매핑을 먼저 하자
@Controller
public class QuestionController{
@GetMapping("/question/list")
@ResponseBody
public String list(){
return "question list";
}
}
이렇게 작성을 하면 @ResponseBody로 인해 question list라는 문자열이 출력이 된다.
우리가 원하는 건 문자열이 아닌 html파일은 반환하는 것이기 때문에 수정을 해야된다.
수정을 하기전 html을 작성을 먼저 해야된다.
<h2>열심히 하자! </h2>
@Controller
public class QuestionController{
@GetMapping("/question/list")
public String list(){
return "question_list";
}
}
이렇게 ResponseBody를 빼고 return하고 싶은 html 이름을 적으면
question_list에서 작성한 열심히 하자!가 보일 것이다.
이제는 데이터를 조회해 화면에 보이게 해보자.
질문 목록을 조회하기 위해서는 QuestionRepostiory를 사용해야 한다.
QuestionRepository로 조회한 질문 목록은 Model 클래스를 사용하여 템플릿에 전달이 가능한다.
QuestionController을 다시 수정을 해보자
@RequiredArgsConstrictor // final이 붙은 속성을 포함하는 생성자를 자동 생성하는 역할
@Controller
public class QuestionController{
private final QuestionRepository questionRepository; // 의존성 주입
@GetMapping("/question/list")
public String list(Model model){
List<Question> questionList = this.questionRepository.findAll();
model.addAttribute("questionList", questionList);
return "question_list";
}
}
스프링의 의존성 주입(Dependency Injection) 방식 3가지
@Autowired 속성 - 속성에 @Autowired 애너테이션을 적용하여 객체를 주입하는 방식
생성자 - 생성자를 작성하여 객체를 주입하는 방식 (권장하는 방식)
Setter - Setter 메서드를 작성하여 객체를 주입하는 방식 (메서드에 @Autowired 애너테이션 적용이 필요하다.)
이제는 html을 수정을 해 템플릿에 데이터가 보이게 해보자
<table>
<thead>
<tr>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
<tr th:each="question : ${questionList}">
<td th:text="${question.subject}"></td>
<td th:text="${question.createDate}"></td>
</tr>
</tbody>
</table>
th로 시작하는 속성은 타임리프 템플릿 엔진이 사용하는 속성이고 자바 코드와 연결이 된다.
<tr th:each="question : ${questionList}">는 QuestionController의 list메서드,에서 조회한 질문 목록 데이터를 questionList라는 이름으로 Model 객체에 저장했다.
타임리프는 Model 객체에 저장된 값을 읽을 수 있으므로 템플릿에서 questionList를 사용할 수 있게 되는것이고
questionList의 갯수만큼 반복하여 출력하는 역할을 하게 된다.
또한 qquestionList에 저장된 데이터를 하나씩 꺼내 question객체에 대입하여 반복구간 내에서 사용할 수 있게 된다.
<td th:text="${question.subject}"></td> : question의 subject를 th:text를 이용해 화면에 출력을 한다
작성을 하고나면 화면에는 아래 링크처럼 나올 것이다.
questionList
타임리프의 자주 사용하는 속성에는 다음 3가지 유형이 있다. 이 3가지 유형만 알아도 여러 기능을 충분히 만들 수 있다.
1. 분기문 속성
분기문 속성은 다음과 같이 사용한다.
th:if="${question != null}"
위의 경우 question 객체가 null 이 아닌 경우에 해당 엘리먼트가 표시된다.
2. 반복문 속성
반복문은 반복횟수만큼 해당 엘리먼트를 반복하여 표시한다. 반복문 속성은 자바의 for each 문과 유사하다.
th:each="question : ${questionList}"
반복문은 다음과 같이 사용할 수도 있다.
th:each="question, loop : ${questionList}"
추가한 loop 객체를 이용하여 루프 내에서 다음과 같은 속성을 사용할수 있다.
loop.index - 반복 순서, 0부터 1씩 증가
loop.count - 반복 순서, 1부터 1씩 증가
loop.size - 반복 객체의 요소 갯수 (예: questionList의 요소 갯수)
loop.first - 루프의 첫번째 순서인 경우 true
loop.last - 루프의 마지막 순서인 경우 true
loop.odd - 루프의 홀수번째 순서인 경우 true
loop.even - 루프의 짝수번째 순서인 경우 true
loop.current - 현재 대입된 객체 (예: 위의 경우 question과 동일)
3. 텍스트 속성
th:text=값 속성은 해당 엘리먼트의 텍스트로 "값"을 출력한다.
th:text="${question.subject}"
텍스트는 th:text 속성 대신에 다음처럼 대괄호를 사용하여 값을 직접 출력할수 있다.
<tr th:each="question : ${questionList}">
<td>[[${question.subject}]]</td>
<td>[[${question.createDate}]]</td>
</tr>
QuestionController을 보면 바로 QuestionReposiotry로 가는데 보안상의 이유로 Service로 통해서 Repository로 가는게 안전하기 때문에 QuestionController를 수정해보고 QuestionService를 작성해보자.
@RequiredArgsConstructor
@Service // JPA에 서비스임을 인식
public class QuestionService {
private final QuestionRepository questionRepository;
public List<Question> getList() {
return this.questionRepository.findAll(); // 모든 질문목록 데이터 조회
}
}
public class QuestionController {
private final QuestionService questionService; // Service로 통해서 Repository로 감
@GetMapping("/question/list")
public String list(Model model) {
List<Question> questionList = this.questionService.getList();
model.addAttribute("questionList", questionList);
return "question_list";
}
}
UI 꾸미는 것은 개인 취향이기 때문에 나중에 꾸며보기!