Spring Boot - JPA와 ORM

HI_DO·2024년 6월 17일
post-thumbnail

JPA로 데이터베이스 사용

JPA란?

  • 자바 애플리케이션에서 관계형 데이터베이스와 상호작용하기 위한 표준 API
  • 스프링부트는 JPA(Jav Persistence API)를 사용하여 데이터베이스를 관리한다.
  • 스프링부트는 JPA를 ORM의 기술 표준으로 사용한다.
  • JPA는 인터페이스 모음이므로 이 인터페이스를 구현한 실제 클레스가 필요하다.
  • JPA를 구현한 실제 클래스에는 대표적으로 하이버네이트(Hibernate)가 있다.
  • 하이버네이트는 JPA의 인터페이스를 구현한 실제 클래스이자 자바의 ORM 프레임워크로 부트에서 데이터베이스를 관리하기 쉽게 도와준다.

ORM이란?

  • ORM은 SQL을 사용하지 않고 데이터베이스를 관리할 수 있는 도구
  • ORM은 데이터베이스의 테이블을 자바 클래스로 만들어 관리할 수 있다.

SQL로 작성시

insert into question (id, subject, content) values (1, '안녕하세요', '가입 인사드립 니다 ^^);
insert into question (id, subject, content) values (2, '질문 있습니다', 'ORM이 궁금합니다');

ORM으로 작성시

Question q1 = new Question();
q1.setId(1);
q1.setSubject("안녕하세요");
q1.setContent("가입 인사드립니다 ^^);
this.questionRepository.save(q1);
Question g2 = new Question();
q2.setId(2);
q2.setSubject("질문 있습니다");
q2.setContent("ORM이 궁금합니다"); this.questionRepository.save(q2);

ORM의 장점

  • ORM을 이용하면 MySQL, 오라클, db, MSSQL과 같은 DBMS의 종류에 관계없이 일관된 자바코드로 사용할 수 있어서 프로그램의 유지보수가 편리하다.

DBMS란?

  • DBMS(Database Management System) 데이터베이스를 관리하는 소프트웨어이다.
  • DB와 DBMS를 구분하지 않고 사용하는 경우가 많은데 엄밀히 말하면 DB는 데이터를 담은 통이라고 할 수 있고, DBMS는 이 통을 관리하는 소프트웨어이다.

방문자들이 질문과 답변을 남길수 있는 게시판을 제작

  • 사용자의 질문이나 답변을 작성하면 데이터가 생성되는데 이러한 데이터를 관리하려면, 저장, 조회, 수정하는 등의 기능을 구현해야 한다.
    -> 데이터를 모으고 관리하는 저장소를 데이터베이스라고 한다.
  • 데이터베이스를 관리하려면 SQL이라는 언어를 사용해야 한다.
    -> 스프링부트와 달리 데이터베이스는 자바를 이해하지 못한다.
    -> 하지만 ORM(Object Relational Mapping)이라는 도구를 사용하면 자바 문법으로도 데이터베이스를 다룰 수 있다. ORM을 사용하면 개발자는 SQL을 직접 작성하지 않아도 데이터베이스의 데이터를 처리할 수 있다.
  • build.gradle에
runtimeOnly 'com.h2database:h2'

추가하고 build.gradle refresh하기

  • application.properties 수정
spring.application.name=sbb
# DATABASE
#h2콘솔로 접속할 것인지 여부
spring.h2.console.enabled=true
#h2콜솔로 접속하기 위한 경로
spring.h2.console.path=/h2-console
#데이터베이스에 접속하기 위한 경로
spring.datasource.url=jdbc:h2:~/local
#데이터베이스에 접속할때 사용하는 드라이버 클래스 명
spring.datasource.driver-class-name=org.h2.Driver
#데이터베이스에 접속할때 사용하는 아이디
spring.datasource.username=sa
#데이터베이스에 접속할때 사용하는 패스워드 
spring.datasource.password=
  • database 만들기
  • build.gradle refresh
  • 결과
  • 변경 후 연결 클릭
  • 데이터베이스 연결된 결과
  • build.gradle에
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

-> 추가후 refreash

  • application.properties 에 JPA추가
spring.application.name=sbb
# DATABASE
#h2콘솔로 접속할 것인지 여부
spring.h2.console.enabled=true
#h2콜솔로 접속하기 위한 경로
spring.h2.console.path=/h2-console
#데이터베이스에 접속하기 위한 경로
spring.datasource.url=jdbc:h2:~/local
#데이터베이스에 접속할때 사용하는 드라이버 클래스 명
spring.datasource.driver-class-name=org.h2.Driver
#데이터베이스에 접속할때 사용하는 아이디
spring.datasource.username=sa
#데이터베이스에 접속할때 사용하는 패스워드 
spring.datasource.password=
# JPA
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

spring.jpa.hibernate.ddl-auto의 규칙

  • none: 엔티티가 변경되더라도 데이터베이스를 변경하지 않는다.
  • update: 엔티티의 변경된 부분만 데이터베이스에 적용한다.
  • validate: 엔티티와 테이블 간에 차이점이 있는지 검사만 한다.
  • create: 스프링 부트 서버를 시작할 때 테이블을 모두 삭제한 후 다시 생성한다.
  • create-drop: create와 동일하지만 스프링 부트 서버를 종료할 때에도 테이블을 모두 삭제한다.

Entitiy구성

Question(id, subject, content, createDate)
Answer

  • Question.java 생성
package com.mysite.sbb;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Question {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;	
	@Column(length = 200)
	private String subject;
	@Column(columnDefinition = "TEXT")
	private String content;	
	private LocalDateTime createDate;
}
  • Answer.java 생성
package com.mysite.sbb;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Answer {
	@Id		// 기본키. 각 데이터들을 구분하는 유효한 값.(중복 불가능)
	@GeneratedValue(strategy = GenerationType.IDENTITY)	//고유한 번호를 생성하는 방법.
	private Integer id;
    //열 이름 텍스트를 열 데이터로 넣을 수 있다. 글자수를 제한할 수 없는 경우에 사용.
	@Column(columnDefinition = "TEXT")
	private String content;
	private LocalDateTime createDate;
    // Question을 참조해야 하기 위해.
    @ManyToOne
	private Question question;
}
  • 결과

답변을 통해 질문의 제목을 알고 싶다

  • answer.getQuestion().getSubject() -> 접근할 수 있다.
@ManyToOne
private Question question;

-> 하나의 질문에 답변은 여러개가 달릴 수 있다.
따라서 답변은 Many가 되고 질문은 One이 된다.
@ManyToOne을 통해서 N:1관계를 나타낼 수 있다.
이렇게 @ManyToOne을 설정하면 Answer 엔티티의 question속성과 Question 엔티티가 서로 연결된다(실제 데이터베이스에서는 (외래키_foreign key)관계가 생성된다.)

@ManyToOne은 부모 자식 관계를 갖는 구조에서 사용한다.

  • 여기서 부모는 Question, 자식은 Answer라고 할 수 있다.
    -> 외래키란 테이블과 테이블 사이의 관계를 구성할때 연결되는 열을 의미한다.

반대로 질문에서 답변을 참조할 수 없을까? -> 있다

  • 답변과 질문이 N:1관계라면 질문과 답변은 1:N관계라고 할 수 있다.
    -> @OneToMany를 사용하면 된다
    질문 하나에 답변은 여러개 이므로 Question 추가할 Answer속성 타입은 List형태로 구성해야 한다.
  • Question.java에
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;

추가
Qustion.java

package com.mysite.sbb;
import java.time.LocalDateTime;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Question {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;	
	@Column(length = 200)
	private String subject;	
	@Column(columnDefinition = "TEXT")
	private String content;	
	private LocalDateTime createDate;	
	@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
	private List<Answer> answerList;	// question.getAnswerList()
}

-> 질문에 답변을 참조하려면 question.getAnswerList()를 호출하면 된다.
@OneToMany에 사용된 mappedBy 속성은 참조 엔티티의 속성명을 정의한다.
-> 즉, Answer 엔티티에서 Question 엔티티를 참조한 속성인 question을 mappedBy에 전달해야 한다.

  • Cascade = CascadeType.REMOVE
    게시판에서는 질문 하나에 답변이 여러개 작성될 수 있다. 그런데 보통 게시판 서비스에서는 질문을 삭제하면 그에 달린 답변들도 함께 삭제된다. 그렇게 만들기 위해 적용하는 속성이다.

repository로 데이터베이스 관리

  • entity만으로는 테이블의 데이터를 저장, 조회, 수정, 삭제 등을 할 수 없다.
    이런 데이터를 관리하려면 데이터베이스와 연동하는 JPA repository가 있어야 한다.
  • QuestionRepository.java(interface) 생성
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
// CRUD함수를 JpaRepository가 들고 있음.
// @Repository라는 에너테이션이 없어도 IoC된다. 이유는 JpaRepository를 상속했기 때문
public interface QuestionRepository  extends JpaRepository<Question, Integer>{
}

-> Question 엔티티를 리포지터리로 생성한다는 의미. Question 엔티티의 기본키가 integer타입.

  • AnswerRepository.java(interface) 생성
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AnswerRepository extends JpaRepository<Answer, Integer>{
}
  • build.gradle에
testImplementation 'org.junit.jupiter:junit-jupiter' 
   testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

추가 후 refresh

  • IoC(Inversoin of Control) : Spring에서 객체의 생성과 관리를 Spring 컨테이너가 담당
    (이전에는 개발자가 객체의 생성과 생명주기를 직접 관리)
  • DI(Dependency Injection) : 필요로하는 객체를 생성하거나 검색하지 않고 외부로부터 주입.
    Spring의 IoC컨테이너는 애플리케이션의 객체를 생성하고 관리하는데, 이때 의존성주입(DI)를 통해 필요한 Bean을 주입한다.
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@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);
	}
}



-> 서버가 이미 켜져있기 때문에 에러

-> 서버 종료 후 다시 실행하면 에러 없음 -> JUnit Test 실행 후 서버 다시 실행

  • 입력 및 실행 결과
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {	
	@Autowired
	private QuestionRepository questionRepository;
	@Test
	void testJpa() {
			List<Question> all = this.questionRepository.findAll();
			// assertEquals(기댓값, 실제값) 동일한지 조사.
		assertEquals(2, all.size());		// 데이터개수		
		Question q = all.get(0);		// 첫번째
		assertEquals("sbb가 무엇인가요?", q.getSubject());
		}		
	}
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {	
	@Autowired
	private QuestionRepository questionRepository;
	@Test
	void testJpa() {
		// 값이 있는지 없는지에 대한 처리를 확인하는 클래스. (null값을 유연하게 처리)
		// isPresent로 값이 존재하는지 확인할 수 있다.
		// isPresent를 통해 값이 존재한다면 get()을 통해 실제 Question의 값을 얻는다.
		Optional<Question> oq = this.questionRepository.findById(1);
		if(oq.isPresent()) {
			Question q = oq.get();
			assertEquals("sbb가 무엇인가요?", q.getSubject());
		}
		}		
	}
  • QuestionRepository.java (interface) 수정
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
// CRUD함수를 JpaRepository가 들고 있음.
// @Repository라는 에너테이션이 없어도 IoC된다. 이유는 JpaRepository를 상속했기 때문
public interface QuestionRepository  extends JpaRepository<Question, Integer>{
	// findBy~
	// select * from question where subject = ?
	Question findBySubject(String subject);	//Jpa Query methods
}
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {	
	@Autowired
	private QuestionRepository questionRepository;
	@Test
	void testJpa() {
		Question q = this.questionRepository.findBySubject("sbb가 무엇인가요?");
		assertEquals(1, q.getId());
		}
	}
  • application.properties에
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
spring.application.name=sbb
# DATABASE
#h2콘솔로 접속할 것인지 여부
spring.h2.console.enabled=true
#h2콜솔로 접속하기 위한 경로
spring.h2.console.path=/h2-console
#데이터베이스에 접속하기 위한 경로
spring.datasource.url=jdbc:h2:~/local
#데이터베이스에 접속할때 사용하는 드라이버 클래스 명
spring.datasource.driver-class-name=org.h2.Driver
#데이터베이스에 접속할때 사용하는 아이디
spring.datasource.username=sa
#데이터베이스에 접속할때 사용하는 패스워드 
spring.datasource.password=
# JPA
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

추가 후 결과 -> console창에 결과가 나옴

  • subject와 content를 함께 조회
    -> findBySubjectAndContent
  • QuestionRepository.java(interface) 수정
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
// CRUD함수를 JpaRepository가 들고 있음.
// @Repository라는 에너테이션이 없어도 IoC된다. 이유는 JpaRepository를 상속했기 때문
public interface QuestionRepository  extends JpaRepository<Question, Integer>{
	// findBy~
	// select * from question where subject = ?
	Question findBySubject(String subject);	//Jpa Query methods
	Question findBySubjectAndContent(String subject, String content);
}
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {	
	@Autowired
	private QuestionRepository questionRepository;
	@Test
	void testJpa() {
		Question q = this.questionRepository
				.findBySubjectAndContent("sbb가 무엇인가요?", "sbb에 대해서 알고 싶습니다.");
		assertEquals(1, q.getId());
		}
	}
  • subject 열값들 중에 특정 문자열을 포함하는 데이터를 조회
    -> Like를 사용
    List[Question] findBySubjectLike(String subject);
  • QuestionRepository.java(interface) 수정
package com.mysite.sbb;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
// CRUD함수를 JpaRepository가 들고 있음.
// @Repository라는 에너테이션이 없어도 IoC된다. 이유는 JpaRepository를 상속했기 때문
public interface QuestionRepository  extends JpaRepository<Question, Integer>{
	// findBy~
	// select * from question where subject = ?
	Question findBySubject(String subject);	//Jpa Query methods
	Question findBySubjectAndContent(String subject, String content);
	List<Question> findBySubjectLike(String subject);
}
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {	
	@Autowired
	private QuestionRepository questionRepository;
	@Test
	void testJpa() {
		List<Question> qList = this.questionRepository.findBySubjectLike("sbb%");
		Question q = qList.get(0);
		assertEquals("sbb가 무엇인가요?", q.getSubject());
		}
	}
  • 실행 및 결과

    -> q1_0.subject like ? escape'\'
    escape'\' 구문은 SQL쿼리에서 사용되는 LIKE 연산자와 함께 쓰이는데, 특정 문자가 와일드 카드로 작동하는 것을 방지하기 위해 사용된다. "sbb%" % 기호가 텍스트로 들어가는 것을 방지하기 위해.
  • sbb% : sbb로 시작하는 문자열
  • %sbb : sbb로 끝나는 문자열
  • %sbb% : sbb를 포함하는 문자열
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {	
	@Autowired
	private QuestionRepository questionRepository;
	@Test
	void testJpa() {
		Optional<Question> oq = this.questionRepository.findById(1);
		assertTrue(oq.isPresent());	//괄호 안의 값이 TRUE인지를 테스트
		Question q = oq.get();
		q.setSubject("수정된 제목");
		this.questionRepository.save(q);
		}
	}
  • 실행 및 결과
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDateTime;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {
   @Autowired
   private QuestionRepository questionRepository;   
   @Autowired
   private AnswerRepository answerRepository;
   @Test
   void testJpa() {
      Optional<Question> oq = this.questionRepository.findById(2);
      assertTrue(oq.isPresent());
      Question q = oq.get();  
      Answer a = new Answer();
      a.setContent("네 자동으로 생성됩니다.");
      a.setQuestion(q);
      a.setCreateDate(LocalDateTime.now());
      this.answerRepository.save(a);
      }
   }
  • 실행 및 결과

  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SbbApplicationTests {
   @Autowired
   private QuestionRepository questionRepository;  
   @Autowired
   private AnswerRepository answerRepository;
   @Test
   void testJpa() {
     Optional<Answer> oq = this.answerRepository.findById(1);
     assertTrue(oq.isPresent());
     Answer a = oq.get();
     assertEquals(2, a.getQuestion().getId());
      }
   }
  • 실행 및 결과

    -> left join : 연관된 데이터를 접근하기 위한 방법
    Answer와 Question은 데이터베이스 내에서 연관관계를 가지고 있다.
    Answer가 Question을 참조하는 외래키(question_id)를 포함한다. left join을 사용하면 Answer객체를 조회할때 연관된 Question 객체의 정보도 함께 불러올 수 있다.

답변 데이터를 통해 질문 데이터 찾기 vs 질문 데이터를 통해 답변 데이터 찾기

  • a.getQuesetion() : 답변을 통해서 질문 찾기

질문 데이터를 통해 답변 찾기 -> answerList를 사용

  • org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.mysite.sbb.Question.answerList: could not initialize proxy - no Session
    -> QuestionRepository가 findById메서드를 통해 Question 객체를 조회하면 DB세션이 끊어진다.
    == DB세션이란 스프링부트 애플리케이션과 데이터베이스 간의 연결
  • answerList는 앞서 q객체를 조회할때가 아니라 q.getAnswerList()메서드를 호출하는 시점에 가져오기 때문에 이와 같은 오류가 발생하는 것이다.
    == 이렇게 데이터를 필요한 시점에 가져오는 방식을 지연(Lazy)방식이라고 한다.
  • 이와 반대로 q객체를 조회할 때 미리 answer 리스트를 모두 가져오는 방식은 즉시(Eager)방식이라고 한다.
    @OneToMany, @ManyToOne 옵션으로 fetch=FetchType.Lazy 또는 fetch=Fetchtype.EAGER 가져오는 방식을 설정할 수 있다.
    -> 이러한 문제는 테스트 코드에서만 발생한다. 실제 서버에서 JPA프로그램들을 실행할때는 DB세션이 종료되지 않아 오류가 발생하지 않는다.
    테스트 코드를 수행할때 이런 오류를 방지할 수 있는 방법은 @Transactional 사용하면 된다.
  • @Transactional은 메서드가 종료될 때까지 DB세션을 유지한다.
  • SbbApplicationTests.java 수정
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import jakarta.transaction.Transactional;
@SpringBootTest
class SbbApplicationTests {   
   @Autowired
   private QuestionRepository questionRepository;  
   @Autowired
   private AnswerRepository answerRepository;
   @Transactional
   @Test
   void testJpa() {
     Optional<Question> oq = this.questionRepository.findById(2);
     assertTrue(oq.isPresent());
     Question q = oq.get();    
     List<Answer> answerList = q.getAnswerList();
     assertEquals(1, answerList.size());
     assertEquals("네 자동으로 생성됩니다.", answerList.get(0).getContent());
      }
   }
  • 실행 및 결과
  • QuestionController.java 생성
package com.mysite.sbb.question;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class QuestionController {  
   @GetMapping("/question/list")
   public String list() {
      return "question_list";
   }
}
  • build.gradle에
   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
   implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

추가 후 refresh

profile
하이도의 BackEnd 입문

0개의 댓글