3/20 TIL - 점프투스프링부트 따라하기1

큰모래·2023년 3월 20일
0
post-custom-banner

자바 코드 컨벤션


자바 코드를 작성할 때 일관성을 유지하고 가독성을 높이는 규칙


규칙

  • 클래스 : 대문자로 시작, 명사로 작성
  • 메서드 : 소문자로 시작, 동사로 작성
  • 변수 : 소문자로 시작, camelCase 적용 ( 예 : firstName, customerList)
  • 상수 : 모두 대문자로 작성, 단어 간 밑줄(_)로 구분
  • 주석 : 과도한 주석은 피해야 함, 메서드와 클래스 설명은 javadoc 스타일로 작성
  • 공백 : 연산자 주변에 공백을 사용하여 가독성을 높인다, 쉼표 및 세미콜론 뒤에 공백 사용

점프투스프링부트 따라하기


1-01. 필자가 생각한 스프링부트

  • 스프링부트를 사용하면 개발자가 보안 공격에 대한 코드를 짤 필요가 없다.
  • 우리가 생각하는 왠만한 기능은 이미 스프링부트 프레임워크에 내재되어있다.
    • 우리는 가져다가 사용하는 방법만 알면된다.
  • 스프링부트는 톰캣 서버가 내장되어 있다.
  • 스프링부트는 스프링의 복잡한 설정 과정을 단순화했다.
  • 스프링부트는 재미있다?

2-03. JPA

  • 질문 답변 게시판을 만들어야하는 상황
  • 이와 관련된 데이터들을 생성, 조회, 수정, 삭제할 수 있어야 한다.
  • 즉, 데이터 처리를 위한 데이터베이스를 사용해야 한다.
  • 이때, ORM을 사용하면 자바 문법을 통해 쿼리를 직접 작성하지 않고 데이터 처리가 가능하다.
  • ORM을 이용하면 내부에서 안전한 쿼리를 자동으로 생성해준다.
  • 이는, 개발자가 바뀌어도 통일된 커리를 작성할 수 있다.

spring.jpa.hibernate.ddl-auto = []
update : 엔티티의 변경된 부분만 적용
create : 스프링부트 서버 재시작될때마다 테이블 드랍 및 생성

개발 환경에서는 보통 update 모드를 사용하고 운영환경에서는 none 또는 validate 모드를 사용한다.


2-04. 엔티티

  • 질문과 답변이 가능한 게시판 서비스를 구현해야 한다.
  • 이를 위해, 질문 및 답변 엔티티를 만들어야 함.

Question

  • 질문 하나에 답변이 여러개 달릴 수 있다. (1대N 관계)
  • @OneToMany 를 통해 질문 엔티티에서 답변 엔티티를 참조할 수 있다.
  • 이 때, 참조 객체는 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;

}

Answer

  • 답변은 하나의 질문에 여러개 달릴 수 있다. (N대1 관계)
  • @ManyToOne을 통해 답변 엔티티에서 질문 엔티티를 참조할 수 있다.
  • 질문과 답변은 서로 양방향 관계이다. ( ORM의 장점)

@Getter
@Setter
@Entity
public class Answer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String content;

    private LocalDateTime createDate;

		@ManyToOne
    private Question question;
}

2-05. 리포지터리

  • 엔티티만으로는 DB에 데이터를 저장하거나 조회할 수 없다.
  • 데이터 처리를 위해서는 DB와 연동되는 JPA 리포지터리가 필요하다.

QuestionRepository


public interface QuestionRepository extends JpaRepository<Question, Long> {
    Question findBySubject(String subject);

    Question findBySubjectAndContent(String subject, String content);

    List<Question> findBySubjectLike(String subject);

}

AnswerRepository


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() 메서드는 총 데이터 개수를 반환한다.
  • JpaRepositorydelete 메서드를 통해 데이터 삭제가 가능하다.
	@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("네 자동으로 생성됩니다.");
	}
profile
큰모래
post-custom-banner

0개의 댓글