자바 코드를 작성할 때 일관성을 유지하고 가독성을 높이는 규칙
- 스프링부트를 사용하면 개발자가 보안 공격에 대한 코드를 짤 필요가 없다.
- 우리가 생각하는 왠만한 기능은 이미 스프링부트 프레임워크에 내재되어있다.
- 우리는 가져다가 사용하는 방법만 알면된다.
- 스프링부트는 톰캣 서버가 내장되어 있다.
- 스프링부트는 스프링의 복잡한 설정 과정을 단순화했다.
- 스프링부트는 재미있다?
- 질문 답변 게시판을 만들어야하는 상황
- 이와 관련된 데이터들을 생성, 조회, 수정, 삭제할 수 있어야 한다.
- 즉, 데이터 처리를 위한 데이터베이스를 사용해야 한다.
- 이때, ORM을 사용하면 자바 문법을 통해 쿼리를 직접 작성하지 않고 데이터 처리가 가능하다.
- ORM을 이용하면 내부에서 안전한 쿼리를 자동으로 생성해준다.
- 이는, 개발자가 바뀌어도 통일된 커리를 작성할 수 있다.
spring.jpa.hibernate.ddl-auto = []
update : 엔티티의 변경된 부분만 적용
create : 스프링부트 서버 재시작될때마다 테이블 드랍 및 생성
개발 환경에서는 보통 update 모드를 사용하고 운영환경에서는 none 또는 validate 모드를 사용한다.
Answer
객체의 리스트 answerList
이다.mappedBy
는 참조 엔티티의 속성 이름이다.CascadeType.REMOVE
@Getter
@Setter
@Entity
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String subject;
private String content;
private LocalDateTime createData;
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
}
@ManyToOne
을 통해 답변 엔티티에서 질문 엔티티를 참조할 수 있다.
@Getter
@Setter
@Entity
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String content;
private LocalDateTime createDate;
@ManyToOne
private Question question;
}
public interface QuestionRepository extends JpaRepository<Question, Long> {
Question findBySubject(String subject);
Question findBySubjectAndContent(String subject, String content);
List<Question> findBySubjectLike(String subject);
}
public interface AnswerRepository extends JpaRepository<Answer, Long> {
}
@Test
@DisplayName("DB에 데이터 저장하기")
void testJpa1() {
Question q1 = new Question();
q1.setSubject("sbb가 무엇인가요?");
q1.setContent("sbb에 대해 알고 싶습니다.");
q1.setCreateData(LocalDateTime.now());
questionRepository.save(q1);
Question q2 = new Question();
q2.setSubject("스프링부트 모델 질문입니다.");
q2.setContent("id는 자동으로 생성되나요?");
q2.setCreateData(LocalDateTime.now());
questionRepository.save(q2);
}
findAll
을 통해 question
테이블에 저장된 모든 데이터를 리스트로 조회 할 수 있다. (기본으로 사용 가능)findById
를 통해 엔티티의 Id
값으로 데이터를 조회할 수 있다. (기본으로 사용 가능)isPresent
를 통해 Optional
객체가 null
값인지 확인 가능QuestionRepository
에 추가한 findBySubject
기능을 사용하여 데이터 조회QuestionRepository
에 추가한 findBySubjectAndContent
기능을 사용하여 두 개의 속성을 and
조건으로 데이터 조회QuestionRepository
에 추가한 findBySubjectLike
기능을 사용하여 특정 문자열이 포함된 데이터 조회 가능 @Test
@DisplayName("데이터 조회하기 - 리스트")
void testJpa2() {
List<Question> questionList = questionRepository.findAll();
assertThat(questionList.size()).isEqualTo(2);
Question question = questionList.get(0);
assertThat(question.getSubject()).isEqualTo("sbb가 무엇인가요?");
}
@Test
@DisplayName("데이터 조회하기 - id")
void testJpa3() {
Optional<Question> id = questionRepository.findById(1L);
if (id.isPresent()) {
Question question = id.get();
assertThat(question.getSubject()).isEqualTo("sbb가 무엇인가요?");
}
}
@Test
@DisplayName("데이터 조회하기 - subject")
void testJpa4() {
Question question = questionRepository.findBySubject("sbb가 무엇인가요?");
assertThat(question.getSubject()).isEqualTo("sbb가 무엇인가요?");
}
@Test
@DisplayName("데이터 조회하기 - subject, content")
void testJpa5() {
Question question = questionRepository.findBySubjectAndContent("sbb가 무엇인가요?", "sbb에 대해 알고 싶습니다.");
assertThat(question.getId()).isEqualTo(1);
}
@Test
@DisplayName("데이터 조회하기 - subject 내용 포함")
void testJpa6() {
List<Question> questionList = questionRepository.findBySubjectLike("sbb%");
Question question = questionList.get(0);
assertThat(question.getSubject()).isEqualTo("sbb가 무엇인가요?");
}
findById
를 통해 찾은 객체를 set
을 통해 데이터 수정하여 다시 저장 @Test
@DisplayName("데이터 수정하기")
void testJpa7() {
Optional<Question> oq = questionRepository.findById(1L);
Question question = oq.get();
question.setSubject("수정된 제목");
questionRepository.save(question);
assertThat(question.getSubject()).isEqualTo("수정된 제목");
}
count()
메서드는 총 데이터 개수를 반환한다.JpaRepository
의 delete
메서드를 통해 데이터 삭제가 가능하다. @Test
@DisplayName("데이터 삭제하기")
void testJpa8() {
assertThat(questionRepository.count()).isEqualTo(2);
Optional<Question> oq = questionRepository.findById(1L);
Question question = oq.get();
questionRepository.delete(question);
assertThat(questionRepository.count()).isEqualTo(1);
}
@Test
@DisplayName("답변 데이터 생성 후 저장하기")
void testJpa9() {
Optional<Question> oq = this.questionRepository.findById(2L);
Question q = oq.get();
Answer a = new Answer();
a.setContent("네 자동으로 생성됩니다.");
a.setQuestion(q);
a.setCreateDate(LocalDateTime.now());
this.answerRepository.save(a);
}
question
속성이 있어 매우 쉽다.answerList
를 통해 연결된 답변을 찾을 수 있다.Question
리포지터리가 findById
를 호출하여 객체를 조회하고 나면 DB 세션이 끊어진다.getAnswerList()
는 세션이 종료되어 오류 발생@Transactional
어노테이션을 붙인다.@Transactional
을 사용하면 메서드가 종료될 때까지 DB 세션이 유지된다. @Test
@DisplayName("질문에서 답변 리스트 조회하기")
@Transactional
void testJpa11() {
Optional<Question> oq = questionRepository.findById(2L);
Question question = oq.get();
List<Answer> answerList = question.getAnswerList();
assertThat(answerList.size()).isEqualTo(1);
assertThat(answerList.get(0).getContent()).isEqualTo("네 자동으로 생성됩니다.");
}