문항의 개수가 미리 정해지지 않는 모델은 어떻게 데이터 베이스에 넣을 수 있을까??

코린이서현이·2024년 4월 23일
0

백엔드 공부

목록 보기
4/10

🎯 설문조사 플랫폼에 쓰일 수 있는 데이터 베이스 설계하기

😷들어가면서😷

설문조사 플랫폼을 개발하려고 하니, 데이터베이스 모델 설계를 하는 게 정말 걱정이 되었다...
나 왜 이렇게 초짜냐... 😂
해낼 수 있는 거 맞지...??ㅎ...

오늘의 미션은 🎯 설문조사 플랫폼에 쓰일 수 있는 데이터 베이스 만들기다!
(오늘안에 해결하고 팀원에게 또 말해줘야지.. 🤭🤭 사실 꼭 말해야하는 건 아닌데... 서로를 안심시키기 위한... 수작 🤭)

🤔 상황 분석

내가 만들건 설문조사 플랫폼이다.
그러니까 설문조사자 A가 설문지 a를 만들고, 또 다른 설문조사자 B는 설문지 b를 만든다.

만들기 전 고민했던 부분은 설문지 a와 설문지 b에 들어가는 문항의 개수가 모두 다를텐데 이를 어떻게 구현할지였다!!

생각보다 해답은 간단했다. (이 방식이 추천되는지는 모르겠지만...)

바로 설문지 테이블에 리스트로 문항을 넣는 것이다.

🛠️ 설문지와 개별 문항은 일대다의 관계

데이터 베이스 모델로 하면 설문지 : 설문지의 개별 문항 = 일대다의 관계이다.
하나의 설문지는 여러개의 개별 문항을 가지고, 하나의 개별 문항은 하나의 설문지만을 가지기 때문이다.

더 구체적인 상황 가정해보기

설문자가 어떤 JSON 파일을 줄 것인지 고민해보기

실제로는 더 복잡하지만

{
  "surveyTitle": "설문조사A",
  "questions": [
    {"questionText": "문항1"},
    {"questionText": "문항2"},
    {"questionText": "문항3"}
  ]
}

이렇게 생긴 객체를 받을 것이다.
그러니까 이 JSON 파일을 dto 객체에 넣고, 적절한 entity로 변환시켜서 repository에 넣으면 된다.

간단하죠? ㅎ

🛠️ 구현하기

지금은 자바 스프링 부트를 사용하고 있고, 연결하기 때문에 자동으로 DB에 올라가고 있다. 그렇지만 DB에서 스키마를 생성하고, 스프링 부트랑 연결하는 부분부터 다시 살펴보자

mysql 연결하기

데이터베이스의 유저 설정이 안되어있는 경우에만!!

create user '유저이름'@'%' identified by '비밀번호';
// 비밀번호과 유저 이름을 설정한 후 %을 통해 모든 호스트에 접속할 수 있는 권한을 부여한다.
GRANT ALL PRIVILEGES ON *.* TO '유저이름'@'%';
// 이 사용자에게 @를 통해 연결된 호스트에 대한 CRUD 작업의 권한을 부여한다. 

@ : 사용자이름과 호스트명을 연결하는 구분자로 사용한다.
% : 모든걸 포함한다.

데이터베이스 스키마 만들기

CREATE DATABASE (원하는 스키마명) CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
// 데이터 베이스를 생성한다. 
// CHARACTER SET utf8 기본 문자 집합으로 UTF-8을 사용한다.
// DEFAULT COLLATE utf8_general_ci; -> 대소문자를 구분하지 않는다.
use (원하는 스키마명);
//데이터베이스를 선택한다.

프로젝트의 application.yml 파일에서 연결

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/(데아터베이스명)?serverTimezone=Asia/Seoul
    username: 유저이름
    password: 비밀번호

엔티티 설계

설문지 코드 : Survey

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Survey {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String title;

  @OneToMany(mappedBy = "survey", cascade = CascadeType.ALL)
  private List<Question> questions = new ArrayList<>();
}

개별 문항 코드: Question

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Question {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String text;

  @ManyToOne
  @JoinColumn(name = "survey_id")
  private Survey survey;
  
}

어노테이션 살펴보기

  • @Data - lombok 라이브러리에서 제공하는 어노테이션으로
    @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 적용해준다.

  • @NoArgsConstructor - 매개변수가 없는 생성자를 만들어준다.

  • @AllArgsConstructor - 클래스의 모든 필드 값을 매개변수로 받아 초기화해주는 생성자를 만들어준다.

  • @Builder - 빌더 패턴을 만들어준다. 여기서 설명하기엔 긴 내용이다.

  • @Entity - 클래스를 DB의 엔티티로 올린다.

  • @Id - 해당 변수를 엔티티의 기본키로 설정한다.

  • @GeneratedValue- 값을 할당 전략을 설정해준다.
    여기서 사용한 속성인 (strategy = GenerationType.IDENTITY)은 연결된 DB의 넘버링 전략을 사용한다.

  • @Column : 해당 변수를 엔티티 속성으로 만든다.
    속성들에는 nullable = false null 값이 올 수 없음, length = 30 길이 제약등이 있다.

  • @ColumnDefault("'user'") 속성 기본 값 설정 (여기에는 등장하지 않기는 한다.)

  • @JoinColumn(name = "survey_id") - 아래 값으로 외래키를 설정하고, 현재 엔티티가 DB에 올라갈때 사용할 열의 이름을 지정한다. (이 부분은 따로 포스팅을 하겠다.)

  • @OneToMany(mappedBy = "survey") : mappedBy지정이 안되어있는 객체가 연관관계의 주인이라는 것을 나타낸다.

사실 @JoinColumn을 이해하기 위해서는 연관관계를 공부해야하기 때문에 빠른 시일내에 포스팅을 하겠다!!

데이터 베이스에 잘 올라갔는지도 살펴보면!



잘 올라갔다! 👍👍

파일을 받기 위한 DTO 설계

(자바는 왜 이렇게 복잡한 문법을 가지는가?)

SurveyDto

@Setter
@Getter
public class SurveyDto {
  private String surveyTitle;
  private List<QuestionDTO> questions;
}

QuestionDTO

@Setter
@Getter
public class QuestionDTO {
  private String questionText;
}

컨트롤러에서 JSON 파일 받기

여기서 다시 등장하는 @RequestBody..!! 😲 👉 예전 글
@RequestBody이 가장 중요한 내용인데 너무 간단하게 써줬군...!!

컨트롤러 코드 확인하기

@RestController
public class SurveyController {

  @Autowired
  private SurveyService surveyService;

  @PostMapping("/survey/test")
  public String surveyTest(@RequestBody SurveyDto surveyDto) {
    Survey survey = new Survey();
    survey.setTitle(surveyDto.getSurveyTitle());

    List<Question> questions = new ArrayList<>();
    surveyDto.getQuestions().forEach(questionDto -> {
      Question question = new Question();
      question.setText(questionDto.getQuestionText());
      questions.add(question);
    });

    surveyService.createSurvey(survey, questions);

    return "완료?!";
  }
}

@RequestBody는 어떻게 동작하는가??

꽤 기능도 여러개 있어보이고... 어떻게 surveyDto객체에 값이 들어가고, List<QuestionDTO> questions에 값이 들어갔을까??

바로 @RequestBody 어노테이션은 JSON 형식의 요청 데이터를 자바 객체로 변환시켜준다.
이 과정에서 MessageConverter의 Jackson라이브러리가 역직렬화를 통해서 변환을 수행한다.

@RequestBody은 기본 생성자와 멤버 변수가 private로 설정되어있을 경우 settergetter이 필요하다.
(모든 매개변수를 넣고 생성자를 만들경우에는 다음과 같은 오류가 난다.)

⚠️ 역직렬화가 불가능하다는 오류 메시지 👇

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.jshbank.demo.surveyTest.dto.QuestionDTO` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)]

이제 Json 데이터를 Dto 객체에 넣는 것까지 했으니까, Dto객체를 entity 객체로 변환하는 부분을 살펴보자

Dto객체를 entity 객체로 변환

내 코드에서는 생성을 한다음 setter을 통해서 값을 넣어주는 방식을 선택했다.
(이게 좋은 방식인지는 또 고민을 해봐야겠다.)

surveyDto에서 survey로 넣는건 어려운 코드가 아니니까 넘어가고, List와 람다식은 따로 포스팅을 하겠다.

하고 이제 만들어진 entity 객체를 service 단으로 넘겨준다.

Dto 객체는 service단으로까지 가져가지는 말자.
Dto는 전달만을 위한 객체이다. 별다른 로직을 넣지 말자.

Repository 설계

이 부분은 아주아주 간단하다.
JpaRepository 인터페이스를 확장하면 별다른 어노테이션을 달지 않아도 자동으로 만들어준다.

QuestionRepository 코드

public interface QuestionRepository extends JpaRepository<Question, Long> {
}

SurveyRepository 코드

public interface SurveyRepository extends JpaRepository<Survey, Long> {
}

서비스 단에서 Repository에 넣기

SurveyService 코드

@Service
public class SurveyService {

  @Autowired
  private SurveyRepository surveyRepository;

  @Autowired
  private QuestionRepository questionRepository;

  @Transactional
  public Survey createSurvey(Survey survey, List<Question> questions) {
    survey = surveyRepository.save(survey);

    for (Question question : questions) {
      question.setSurvey(survey);
      question = questionRepository.save(question);
    }

    return survey;
  }
}

사용해야할 Repository를 @Autowired를 통해 자동주입을 받고, @Transactional 어노테이션으로 영속화를 시킨 후 DB에 올린다.

그러면?!

잘 올라간 것을 확인할 수 있다. 🤭🤭

👍기억하자

📌 테이블에 배열을 쓰면 여러가지 테이블을 넣어놓 수 있다!
📌 @Transactional어노테이션을 사용해야 DB에 값이 올라간다.

🖐️마무리하면서🖐️

뭔가 글이 너무 길어져서 잘 정리가 되었는지 모르겠다..
하지만 위에서 이야기했던 연관관계, 역직렬화, 자바의 리스트와 배열등을 어서 포스팅해야겠다는 의지가 생겼다.!!

profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글