설문조사 플랫폼을 개발하려고 하니, 데이터베이스 모델 설계를 하는 게 정말 걱정이 되었다...
나 왜 이렇게 초짜냐... 😂
해낼 수 있는 거 맞지...??ㅎ...
오늘의 미션은 🎯 설문조사 플랫폼에 쓰일 수 있는 데이터 베이스 만들기다!
(오늘안에 해결하고 팀원에게 또 말해줘야지.. 🤭🤭 사실 꼭 말해야하는 건 아닌데... 서로를 안심시키기 위한... 수작 🤭)
내가 만들건 설문조사 플랫폼이다.
그러니까 설문조사자 A가 설문지 a를 만들고, 또 다른 설문조사자 B는 설문지 b를 만든다.
만들기 전 고민했던 부분은 설문지 a와 설문지 b에 들어가는 문항의 개수가 모두 다를텐데 이를 어떻게 구현할지였다!!
생각보다 해답은 간단했다. (이 방식이 추천되는지는 모르겠지만...)
바로 설문지 테이블에 리스트로 문항을 넣는 것이다.
데이터 베이스 모델로 하면 설문지 : 설문지의 개별 문항 = 일대다의 관계이다.
하나의 설문지는 여러개의 개별 문항을 가지고, 하나의 개별 문항은 하나의 설문지만을 가지기 때문이다.
실제로는 더 복잡하지만
{
"surveyTitle": "설문조사A",
"questions": [
{"questionText": "문항1"},
{"questionText": "문항2"},
{"questionText": "문항3"}
]
}
이렇게 생긴 객체를 받을 것이다.
그러니까 이 JSON 파일을 dto 객체에 넣고, 적절한 entity로 변환시켜서 repository에 넣으면 된다.
간단하죠? ㅎ
지금은 자바 스프링 부트를 사용하고 있고, 연결하기 때문에 자동으로 DB에 올라가고 있다. 그렇지만 DB에서 스키마를 생성하고, 스프링 부트랑 연결하는 부분부터 다시 살펴보자
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: 비밀번호
@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<>();
}
@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을 이해하기 위해서는 연관관계를 공부해야하기 때문에 빠른 시일내에 포스팅을 하겠다!!
잘 올라갔다! 👍👍
(자바는 왜 이렇게 복잡한 문법을 가지는가?)
@Setter
@Getter
public class SurveyDto {
private String surveyTitle;
private List<QuestionDTO> questions;
}
@Setter
@Getter
public class QuestionDTO {
private String questionText;
}
여기서 다시 등장하는
@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
로 설정되어있을 경우 setter
와 getter
이 필요하다.
(모든 매개변수를 넣고 생성자를 만들경우에는 다음과 같은 오류가 난다.)
⚠️ 역직렬화가 불가능하다는 오류 메시지 👇
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 객체로 변환하는 부분을 살펴보자
내 코드에서는 생성을 한다음 setter을 통해서 값을 넣어주는 방식을 선택했다.
(이게 좋은 방식인지는 또 고민을 해봐야겠다.)
surveyDto에서 survey로 넣는건 어려운 코드가 아니니까 넘어가고, List와 람다식은 따로 포스팅을 하겠다.
하고 이제 만들어진 entity 객체를 service 단으로 넘겨준다.
Dto 객체는 service단으로까지 가져가지는 말자.
Dto는 전달만을 위한 객체이다. 별다른 로직을 넣지 말자.
이 부분은 아주아주 간단하다.
JpaRepository
인터페이스를 확장하면 별다른 어노테이션을 달지 않아도 자동으로 만들어준다.
public interface QuestionRepository extends JpaRepository<Question, Long> {
}
public interface SurveyRepository extends JpaRepository<Survey, Long> {
}
@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에 값이 올라간다.
뭔가 글이 너무 길어져서 잘 정리가 되었는지 모르겠다..
하지만 위에서 이야기했던 연관관계, 역직렬화, 자바의 리스트와 배열등을 어서 포스팅해야겠다는 의지가 생겼다.!!